From d6c372671903dfd6f46744e22feb5a4a0eec2012 Mon Sep 17 00:00:00 2001
From: RikaCelery <94585272+RikaCelery@users.noreply.github.com>
Date: Thu, 27 Jul 2023 14:18:52 +0800
Subject: [PATCH] add cli->re converter

---
 cli-to-re-map                           |  33 ++++
 cli2re.html                             | 120 ++++++++++++
 cli2re.js                               | 245 ++++++++++++++++++++++++
 current-help-cli                        |  41 ++++
 current-help.txt => current-help-re.txt |   0
 darkmode.js                             |  31 +++
 generator.js                            |  31 ---
 help-20230628.txt                       |  68 -------
 index.html                              |   8 +-
 style.css                               |  26 ++-
 10 files changed, 495 insertions(+), 108 deletions(-)
 create mode 100644 cli-to-re-map
 create mode 100644 cli2re.html
 create mode 100644 cli2re.js
 create mode 100644 current-help-cli
 rename current-help.txt => current-help-re.txt (100%)
 create mode 100644 darkmode.js
 delete mode 100644 help-20230628.txt

diff --git a/cli-to-re-map b/cli-to-re-map
new file mode 100644
index 0000000..676ee07
--- /dev/null
+++ b/cli-to-re-map
@@ -0,0 +1,33 @@
+workDir                      -> save-dir
+saveName                     -> save-name
+baseUrl                      -> base-url
+headers(key:value|key:value) -> header
+maxThreads                   -> <unusable>
+minThreads                   -> thread-count
+retryCount                   -> download-retry-count
+timeOut                      -> <unusable>
+muxSetJson                   -> ? maybe mux-import
+useKeyFile                   -> custom-hls-key
+useKeyBase64                 -> custom-hls-key
+useKeyIV                     -> custom-hls-iv
+downloadRange                -> custom-range <need-convert>
+liveRecDur                   -> live-record-limit
+stopSpeed                    -> <unusable>
+maxSpeed                     -> <unusable>
+proxyAddress                 -> custom-proxy
+enableDelAfterDone           -> del-after-done
+enableMuxFastStart           -> <unusable>
+enableBinaryMerge            -> binary-merge
+enableParseOnly              -> skip-download + skip-merge
+enableAudioOnly              -> drop-audio .* + drop-subtitle .*
+disableDateInfo              -> no-date-info
+disableIntegrityCheck        -> check-segments-count False
+noMerge                      -> skip-merge
+noProxy                      -> use-system-proxy False
+registerUrlProtocol          -> <unusable>
+unregisterUrlProtocol        -> <unusable>
+enableChaCha20               -> ?
+chaCha20KeyBase64            -> ?
+chaCha20NonceBase64          -> ?
+help                         -> help
+version                      -> version
\ No newline at end of file
diff --git a/cli2re.html b/cli2re.html
new file mode 100644
index 0000000..45aa320
--- /dev/null
+++ b/cli2re.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <title>Command Generator</title>
+    <link rel="stylesheet"
+        href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,700;1,400&amp;display=swap.css">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="stylesheet" href="style.css">
+    <link rel="stylesheet" href="button-style.css">
+</head>
+<script type="module" src="darkmode.js"></script>
+<script type="module" src="cli2re.js"></script>
+<script src="cli2re.js"></script>
+
+<body class="dark">
+    <div id="DarkMode">
+        <div class="container-switch">
+            <div class="switch">
+                <div class="toggle-button">
+                    <div class="toggle"></div>
+                    <div class="moon-mask"></div>
+                    <div class="circles-wrapper">
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                        <div class="circle"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="text">
+            </div>
+        </div>
+    </div>
+    <section class="container">
+        <a class="link" href="/">RE命令行生成</a>
+        <header>CLI -> RE</header>
+        <div id="output">转换后的RE命令行将会显示在这里</div>
+        <div class="form" action="#">
+            <div class="input-box">
+                <label for="input">CLI</label>
+                <textarea id="input" rows="20" placeholder="请输入完整的CLI命令" required></textarea>
+            </div>
+        </div>
+        <div id="info-box" class="info-box">
+            <!-- <div class="info">
+                <div class="arg-name">
+                    --headers
+                </div>
+                <div class="arg-option">
+                    "Cookie: None"
+                </div>
+            </div>
+
+            <div class="info error">
+                <div class="arg-name">
+                    --headers
+                </div>
+                <div class="arg-option">
+                    "Cookie: None"
+                </div>
+            </div> -->
+        </div>
+    </section>
+</body>
+<script>
+    document.getElementById('input').addEventListener("input", function (e) {
+        document.getElementById('output').innerText = convert(e.target.value)
+    })
+</script>
+<style>
+    textarea {
+        word-wrap: break-word;
+        color: var(--main-text-color);
+        background: none;
+    }
+    .info-box{
+        display: flex;
+        flex-direction: column;
+        row-gap: 5px;
+
+        margin-top: 3px;
+    }
+    .info {
+        display: flex;
+        flex-direction: row;
+        column-gap: 20px;
+        width: 100%;
+        border-radius: 5px;
+        box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.192);
+        /* border: solid 1px var(--border-color); */
+        background-color: var(--background-color);
+        color: var(--main-text-color);
+    }
+    .info .arg-name{
+        font-weight: 500;
+    }
+    .info .arg-option{
+        font-weight: 300;
+    }
+    .info.ok{
+        background-color: var(--info-ok-color);
+    }
+    .info.error{
+        background-color: var(--info-error-color);
+    }
+    .info.drop{
+        text-decoration: line-through;
+        color: var(--info-drop-color);
+    }
+    .info.unknown{
+        color: red;
+    }
+</style>
\ No newline at end of file
diff --git a/cli2re.js b/cli2re.js
new file mode 100644
index 0000000..3d8c25b
--- /dev/null
+++ b/cli2re.js
@@ -0,0 +1,245 @@
+/**
+ * 
+ * @param {String} prefix 
+ * @returns {String}
+ */
+String.prototype.removePrefix = function (prefix) {
+    if (this.match('^' + prefix + '.*$')) {
+        return this.substring(prefix.length)
+    } else {
+        return this.toString()
+    }
+}
+/**
+ * 
+ * @param {String} suffix 
+ * @returns {String}
+ */
+String.prototype.removeSuffix = function (suffix) {
+    if (this.match('^.*' + suffix + '$')) {
+        return this.substring(0, prefix.length)
+    } else {
+        return this.toString()
+    }
+}
+
+
+function logOut(...any) {
+    console.log(...any);
+}
+
+/**
+ * @param commandLine{String}
+ */
+function convert(commandLine) {
+    cleanInfoBox()
+    const args = {
+        prefix: "",
+        /** 
+         * @type {String[]}
+        */
+        options: [
+
+        ]
+    }
+    /**
+     * @type {String[]}
+     */
+    let firstArgPos = commandLine.indexOf(' ', commandLine.indexOf('N_M3U8DL-CLI'))
+    args.prefix = commandLine.substring(0, firstArgPos)
+    let options = Array.from(commandLine.substring(firstArgPos).matchAll('(?:((?:[^"]|^)[\\S\s]+(?:[^"]|$)))|(?:"([^"]+)")')).map((e) => {
+        return (e[2] ? e[2] : e[1]).trim().removePrefix('--')
+    })
+    logOut(options);
+
+    class Element {
+        hasArg = false
+        _drop = false
+        replacer = (old) => ''
+        indexInc = (old) => { if (this.hasArg) return 1; else return 0 }
+        /**
+         * @param {Boolean} hasArg  
+         * @param {Boolean|(old:String)=>Boolean} drop  
+         * @param {(old:String)=>String|String} replacer  
+         * @param {(old:String)=>number|undefined} inc  
+         * */
+        constructor(hasArg, drop, replacer, inc) {
+            this.hasArg = hasArg
+            this._drop = drop
+            this.replacer = replacer
+            if (inc) {
+                this.indexInc = inc
+            }
+        }
+        drop(old) {
+            if (typeof this._drop == "boolean") {
+                return this._drop
+            } else
+                return this._drop(this.replacer(old))
+        }
+    }
+    let map = {
+        "workDir": new Element(
+            true, false, (old) => 'save-dir'
+        ),
+        saveName: new Element(
+            true, false, (old) => 'save-name'
+        ),
+        baseUrl: new Element(
+            true, false, (old) => 'base-url'
+        ),
+        headers: new Element(
+            true, false, (old) => {
+                return old.split('|').map((kv) => {
+                    return `--header ${kv.substring(0, kv.indexOf(':'))}: ${kv.substring(kv.indexOf(':'))}`
+                }).join(' ')
+            }
+        ),
+        maxThreads: new Element(
+            true, (old) =>
+            args.options.includes('--thread-count')
+            , (old) => 'thread-count'
+        ),
+        minThreads: new Element(
+            true, (old) =>
+            args.options.includes('--thread-count')
+            , (old) => 'thread-count'
+        ),
+        retryCount: new Element(
+            true, false, (old) => 'download-retry-count'
+        ),
+        timeOut: new Element(
+            true, true
+        ),
+        muxSetJson: new Element(
+            true, true, (old) => 'mux-import'
+        ),
+        useKeyFile: new Element(
+            true, false, (old) => 'custom-hls-key'
+        ),
+        useKeyBase64: new Element(
+            true, false, (old) => 'custom-hls-key'
+        ),
+        useKeyIV: new Element(
+            true, false, (old) => 'custom-hls-iv'
+        ),
+        downloadRange: new Element(
+            true, false, (old) => 'custom-range'
+        ),
+        liveRecDur: new Element(
+            true, false, (old) => 'live-record-limit'
+        ),
+        stopSpeed: new Element(
+            true, true
+        ),
+        maxSpeed: new Element(
+            true, true
+        ),
+        proxyAddress: new Element(
+            true, false, (old) => 'custom-proxy'
+        ),
+        enableDelAfterDone: new Element(
+            false, false, (old) => 'del-after-done'
+        ),
+        enableMuxFastStart: new Element(
+            false, true, (old) => ''
+        ),
+        enableBinaryMerge: new Element(
+            false, false, (old) => 'binary-merge'
+        ),
+        enableParseOnly: new Element(
+            false, false, (old) => 'drop-audio .* drop-subtitle .*'
+        ),
+        disableDateInfo: new Element(
+            false, false, (old) => 'no-date-info'
+        ),
+        disableIntegrityCheck: new Element(
+            false, false, (old) => 'check-segments-count False'
+        ),
+        noMerge: new Element(
+            false, false, (old) => 'skip-merge'
+        ),
+        noProxy: new Element(
+            false, false, (old) => 'use-system-proxy False'
+        ),
+        chaCha20KeyBase64: new Element(
+            true, true, (old) => ''
+        ),
+        chaCha20NonceBase64: new Element(
+            true, true, (old) => ''
+        ),
+    }
+    for (let i = 0; i < options.length; i++) {
+        const opt = options[i];
+        logOut(opt)
+        /**
+         * @type {Element}
+         */
+        const ele = map[opt]
+        if (!ele) {
+            logOut(`${opt}(unknown)`)
+            writeInfo('--' + opt,'',"unknown",'unknown')
+            continue
+        }
+        let drop = ele.drop(opt)
+        if (drop) {
+            logOut(` --${opt}(drop)`);
+        } else {
+            args.options.push(`--${ele.replacer(opt)}`)
+            logOut(`--${opt} => --${ele.replacer(opt)}`);
+        }
+
+        logOut(`  hasArg ${ele.hasArg}`);
+        logOut(`  indexInc ${ele.indexInc(opt)}`);
+        let option = ""
+        for (let j = i + 1; j <= i + ele.indexInc(opt); j++) {
+            const element = options[j];
+            if (!drop) {
+                option+=element
+                args.options.push(`"${element}"`)
+                logOut(`    arg: "${element}"`);
+            } else {
+                logOut(`    arg: ${element}(drop)`);
+            }
+        }
+        if(drop){
+            writeInfo('--' + opt,option,"drop","droped")
+        }else{
+            writeInfo('--' + opt,option,"ok")
+        }
+
+        i += ele.indexInc(opt)
+
+    }
+
+
+
+    if (args.prefix + ' ' + args.options.join(' ').length == 0)
+        return '转换后的RE命令行将会显示在这里'
+    return (args.prefix + ' ' + args.options.join(' '));
+}
+
+function cleanInfoBox() {
+    document.getElementById('info-box').innerHTML = ''
+
+}
+
+function writeInfo(argName, argOption, type, details) {
+    let box = document.getElementById('info-box')
+    let info = document.createElement('div')
+    info.classList.add("info")
+    info.classList.add(type)
+    let name = document.createElement('div')
+    name.classList.add('arg-name')
+    if (details) {
+        name.innerText = argName + `(${details})`
+    }else{
+        name.innerText = argName
+    }
+    let option = document.createElement('div')
+    option.classList.add('arg-option')
+    option.innerText = argOption
+    info.appendChild(name)
+    info.appendChild(option)
+    box.appendChild(info)
+}
\ No newline at end of file
diff --git a/current-help-cli b/current-help-cli
new file mode 100644
index 0000000..bff16ba
--- /dev/null
+++ b/current-help-cli
@@ -0,0 +1,41 @@
+N_m3u8DL-CLI 3.0.2.0
+
+USAGE:
+
+  N_m3u8DL-CLI <URL|JSON|FILE> [OPTIONS]
+
+OPTIONS:
+
+  --workDir                  设定程序工作目录
+  --saveName                 设定存储文件名(不包括后缀)
+  --baseUrl                  设定Baseurl
+  --headers                  设定请求头,格式 key:value 使用|分割不同的key&value
+  --maxThreads               (Default: 32) 设定程序的最大线程数
+  --minThreads               (Default: 16) 设定程序的最小线程数
+  --retryCount               (Default: 15) 设定程序的重试次数
+  --timeOut                  (Default: 10) 设定程序网络请求的超时时间(单位为秒)
+  --muxSetJson               使用外部json文件定义混流选项
+  --useKeyFile               使用外部16字节文件定义AES-128解密KEY
+  --useKeyBase64             使用Base64字符串定义AES-128解密KEY
+  --useKeyIV                 使用HEX字符串定义AES-128解密IV
+  --downloadRange            仅下载视频的一部分分片或长度
+  --liveRecDur               直播录制时,达到此长度自动退出软件(HH:MM:SS)
+  --stopSpeed                当速度低于此值时,重试(单位为KB/s)
+  --maxSpeed                 设置下载速度上限(单位为KB/s)
+  --proxyAddress             设置HTTP/SOCKS5代理, 如 http://127.0.0.1:8080
+  --enableDelAfterDone       开启下载后删除临时文件夹的功能
+  --enableMuxFastStart       开启混流mp4的FastStart特性
+  --enableBinaryMerge        开启二进制合并分片
+  --enableParseOnly          开启仅解析模式(程序只进行到meta.json)
+  --enableAudioOnly          合并时仅封装音频轨道
+  --disableDateInfo          关闭混流中的日期写入
+  --disableIntegrityCheck    不检测分片数量是否完整
+  --noMerge                  禁用自动合并
+  --noProxy                  不自动使用系统代理
+  --registerUrlProtocol      注册m3u8dl链接协议
+  --unregisterUrlProtocol    取消注册m3u8dl链接协议
+  --enableChaCha20           enableChaCha20
+  --chaCha20KeyBase64        ChaCha20KeyBase64
+  --chaCha20NonceBase64      ChaCha20NonceBase64
+  --help                     Display this help screen.
+  --version                  Display version information.
\ No newline at end of file
diff --git a/current-help.txt b/current-help-re.txt
similarity index 100%
rename from current-help.txt
rename to current-help-re.txt
diff --git a/darkmode.js b/darkmode.js
new file mode 100644
index 0000000..047353a
--- /dev/null
+++ b/darkmode.js
@@ -0,0 +1,31 @@
+function changeColor() {
+    document.body.classList.toggle('dark')
+    return
+}
+const animate = gsap.timeline({ paused: true });
+const animateBackground = new TimelineMax({ paused: true });
+let toggle = true;
+
+animateBackground
+    .set(".switch", { boxShadow: "0 0 10px rgba(255, 255, 255, 0.2)" })
+    .to(".text p", 0.1, { color: "#FFF" }, 0.1);
+
+animate
+    .set(".circle", { backgroundColor: "rgba(0,0,0,0)" }, 0.2)
+    .to(".toggle-button", 0.1, { scale: 0.7 }, 0)
+    .set(".toggle", { backgroundColor: "#FFF" })
+    .to(".moon-mask", 0.2, { translateY: 20, translateX: -10 }, 0.3)
+    .to(".toggle-button", 0.1, { translateY: 49 }, 0.1)
+    .to(".toggle-button", 0.1, { scale: 0.9 })
+
+document.getElementsByClassName("switch")[0].addEventListener("click", () => {
+    if (toggle) {
+        animate.restart();
+        animateBackground.restart();
+    } else {
+        animate.reverse();
+        animateBackground.reverse();
+    }
+    toggle = !toggle;
+    changeColor()
+});
diff --git a/generator.js b/generator.js
index 50077fd..2410cac 100644
--- a/generator.js
+++ b/generator.js
@@ -1,34 +1,3 @@
-function changeColor() {
-    document.body.classList.toggle('dark')
-    return
-}
-const animate = gsap.timeline({ paused: true });
-const animateBackground = new TimelineMax({ paused: true });
-let toggle = true;
-
-animateBackground
-    .set(".switch", { boxShadow: "0 0 10px rgba(255, 255, 255, 0.2)" })
-    .to(".text p", 0.1, { color: "#FFF" }, 0.1);
-
-animate
-    .set(".circle", { backgroundColor: "rgba(0,0,0,0)" }, 0.2)
-    .to(".toggle-button", 0.1, { scale: 0.7 }, 0)
-    .set(".toggle", { backgroundColor: "#FFF" })
-    .to(".moon-mask", 0.2, { translateY: 20, translateX: -10 }, 0.3)
-    .to(".toggle-button", 0.1, { translateY: 49 }, 0.1)
-    .to(".toggle-button", 0.1, { scale: 0.9 })
-
-document.getElementsByClassName("switch")[0].addEventListener("click", () => {
-    if (toggle) {
-        animate.restart();
-        animateBackground.restart();
-    } else {
-        animate.reverse();
-        animateBackground.reverse();
-    }
-    toggle = !toggle;
-    changeColor()
-});
 
 function insert(pos, id, placeholder) {
     let input = document.createElement('input')
diff --git a/help-20230628.txt b/help-20230628.txt
deleted file mode 100644
index 3b516b9..0000000
--- a/help-20230628.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-Description:
-  N_m3u8DL-RE (Beta version) 20230628
-
-Usage:
-  N_m3u8DL-RE <input> [options]
-
-Arguments:
-  <input>  链接或文件
-
-Options:
-  --tmp-dir <tmp-dir>                      设置临时文件存储目录
-  --save-dir <save-dir>                    设置输出目录
-  --save-name <save-name>                  设置保存文件名
-  --base-url <base-url>                    设置BaseURL
-  --thread-count <number>                  设置下载线程数 [default: 16]
-  --download-retry-count <number>          每个分片下载异常时的重试次数 [default: 3]
-  --auto-select                            自动选择所有类型的最佳轨道 [default: False]
-  --skip-merge                             跳过合并分片 [default: False]
-  --skip-download                          跳过下载 [default: False]
-  --check-segments-count                   检测实际下载的分片数量和预期数量是否匹配 [default: True]
-  --binary-merge                           二进制合并 [default: False]
-  --del-after-done                         完成后删除临时文件 [default: True]
-  --no-date-info                           混流时不写入日期信息 [default: False]
-  --no-log                                 关闭日志文件输出 [default: False]
-  --write-meta-json                        解析后的信息是否输出json文件 [default: True]
-  --append-url-params                      将输入Url的Params添加至分片, 对某些网站很有用, 例如 kakao.com [default: False]
-  -mt, --concurrent-download               并发下载已选择的音频、视频和字幕 [default: False]
-  -H, --header <header>                    为HTTP请求设置特定的请求头, 例如:
-                                           -H "Cookie: mycookie" -H "User-Agent: iOS"
-  --sub-only                               只选取字幕轨道 [default: False]
-  --sub-format <SRT|VTT>                   字幕输出类型 [default: SRT]
-  --auto-subtitle-fix                      自动修正字幕 [default: True]
-  --ffmpeg-binary-path <PATH>              ffmpeg可执行程序全路径, 例如 C:\Tools\ffmpeg.exe
-  --log-level <DEBUG|ERROR|INFO|OFF|WARN>  设置日志级别 [default: INFO]
-  --ui-language <en-US|zh-CN|zh-TW>        设置UI语言
-  --urlprocessor-args <urlprocessor-args>  此字符串将直接传递给URL Processor
-  --key <key>                              设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:
-                                           --key KID1:KEY1 --key KID2:KEY2
-  --key-text-file <key-text-file>          设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件)
-  --decryption-binary-path <PATH>          MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe
-  --use-shaka-packager                     解密时使用shaka-packager替代mp4decrypt [default: False]
-  --mp4-real-time-decryption               实时解密MP4分片 [default: False]
-  -M, --mux-after-done <OPTIONS>           所有工作完成时尝试混流分离的音视频. 输入 "--morehelp mux-after-done" 以查看详细信息
-  --custom-hls-method <METHOD>             指定HLS加密方式
-                                           (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)
-  --custom-hls-key <FILE|HEX|BASE64>       指定HLS解密KEY. 可以是文件, HEX或Base64
-  --custom-hls-iv <FILE|HEX|BASE64>        指定HLS解密IV. 可以是文件, HEX或Base64
-  --use-system-proxy                       使用系统默认代理 [default: True]
-  --custom-proxy <URL>                     设置请求代理, 如 http://127.0.0.1:8888
-  --custom-range <RANGE>                   仅下载部分分片. 输入 "--morehelp custom-range" 以查看详细信息
-  --task-start-at <yyyyMMddHHmmss>         在此时间之前不会开始执行任务
-  --live-perform-as-vod                    以点播方式下载直播流 [default: False]
-  --live-real-time-merge                   录制直播时实时合并 [default: False]
-  --live-keep-segments                     录制直播并开启实时合并时依然保留分片 [default: True]
-  --live-pipe-mux                          录制直播并开启实时合并时通过管道+ffmpeg实时混流到TS文件 [default: False]
-  --live-fix-vtt-by-audio                  通过读取音频文件的起始时间修正VTT字幕 [default: False]
-  --live-record-limit <HH:mm:ss>           录制直播时的录制时长限制
-  --live-wait-time <SEC>                   手动设置直播列表刷新间隔
-  --mux-import <OPTIONS>                   混流时引入外部媒体文件. 输入 "--morehelp mux-import" 以查看详细信息
-  -sv, --select-video <OPTIONS>            通过正则表达式选择符合要求的视频流. 输入 "--morehelp select-video" 以查看详细信息
-  -sa, --select-audio <OPTIONS>            通过正则表达式选择符合要求的音频流. 输入 "--morehelp select-audio" 以查看详细信息
-  -ss, --select-subtitle <OPTIONS>         通过正则表达式选择符合要求的字幕流. 输入 "--morehelp select-subtitle" 以查看详 细信息
-  -dv, --drop-video <OPTIONS>              通过正则表达式去除符合要求的视频流.
-  -da, --drop-audio <OPTIONS>              通过正则表达式去除符合要求的音频流.
-  -ds, --drop-subtitle <OPTIONS>           通过正则表达式去除符合要求的字幕流.
-  --morehelp <OPTION>                      查看某个选项的详细帮助信息
-  --version                                Show version information
-  -?, -h, --help                           Show help and usage information
\ No newline at end of file
diff --git a/index.html b/index.html
index 2a78491..a2fef5a 100644
--- a/index.html
+++ b/index.html
@@ -12,6 +12,7 @@
     <link rel="stylesheet" href="button-style.css">
 </head>
 <script type="module" src="generator.js"></script>
+<script type="module" src="darkmode.js"></script>
 
 <body class="dark">
     <div id="DarkMode">
@@ -37,6 +38,7 @@
         </div>
     </div>
     <section class="container">
+        <a class="link" href="/cli2re.html">CLI命令转RE</a>
         <header>Generator</header>
         <div id="output"></div>
         <form action="#" class="form" id="generator_body">
@@ -170,7 +172,8 @@
                 <div id="header" class="input-box ">
                     <div class="row">
                         <label>header</label><label class="button"
-                            onclick="insert('#header','header','为HTTP请求设置特定的请求头, 例如: -H &#x0022;Cookie: mycookie&#x0022; -H &#x0022;User-Agent: iOS&#x0022;')">+</label><label class="button" onclick="removeLast('#header')">-</label>
+                            onclick="insert('#header','header','为HTTP请求设置特定的请求头, 例如: -H &#x0022;Cookie: mycookie&#x0022; -H &#x0022;User-Agent: iOS&#x0022;')">+</label><label
+                            class="button" onclick="removeLast('#header')">-</label>
                         <input type="text" id="header"
                             placeholder="为HTTP请求设置特定的请求头, 例如: -H &#x0022;Cookie: mycookie&#x0022; -H &#x0022;User-Agent: iOS&#x0022;">
                     </div>
@@ -180,7 +183,8 @@
                 <div id="key" class="input-box ">
                     <div class="row">
                         <label>key</label><label class="button"
-                            onclick="insert('#key','key','设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式: --key KID1:KEY1 --key KID2:KEY2')">+</label><label class="button" onclick="removeLast('#key')">-</label>
+                            onclick="insert('#key','key','设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式: --key KID1:KEY1 --key KID2:KEY2')">+</label><label
+                            class="button" onclick="removeLast('#key')">-</label>
                         <input type="text" id="key"
                             placeholder="设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式: --key KID1:KEY1 --key KID2:KEY2">
                     </div>
diff --git a/style.css b/style.css
index d8ef43c..3e925dc 100644
--- a/style.css
+++ b/style.css
@@ -10,6 +10,9 @@ header,.description,.button{
     --backgrond-color-hover: var(rgb(81, 57, 202));
     --input-color-hover: rgba(130, 106, 251, 0.501);
     --button-color-nohover: rgb(130, 106, 251);
+    --info-ok-color: #a3ecae;
+    --info-error-color: #eca3a3;
+    --info-drop-color: #888;
     --button-color: #fff;
     --main-text-color: rgb(0, 0, 0);
     --description-text-color: rgb(120, 120, 120);
@@ -23,6 +26,8 @@ header,.description,.button{
     --backgrond-color-hover: rgb(80, 64, 168);
     --input-color-hover: rgba(81, 57, 202, 0.688);
     --button-color-nohover: rgb(52, 52, 81);
+    --info-ok-color: #273929;
+    --info-error-color: #411111;
     --button-color: #fff;
     --main-text-color: rgb(205, 205, 205);
     --description-text-color: rgb(120, 120, 120);
@@ -49,10 +54,14 @@ body {
     padding-top: 40px;
     background: var(--backgrond-color);
 }
-
+a{
+    position: absolute;
+    color: var(--main-text-color);
+}
 .container {
     max-height: 100%;
     max-width: 1000px;
+    width: 100%;
     display: block;
     overflow: scroll;
     padding: 25px;
@@ -92,7 +101,7 @@ body {
     color: var(--main-text-color);
 }
 
-.form :where(.input-box input, .select-box) {
+.form :where(.input-box input, .input-box textarea,.select-box) {
     position: relative;
     width: 100%;
     outline: none;
@@ -102,13 +111,16 @@ body {
     border: 1px solid var(--border-color);
     padding: 0 15px;
 }
-
+.form textarea{
+    height: 150px;
+    resize: none;
+}
 .form .column {
     display: flex;
     column-gap: 15px;
 }
 
-.form .column input {
+.form .column input ,.form .column textarea{
     position: relative;
     width: 100%;
     outline: none;
@@ -174,7 +186,7 @@ body {
     margin-top: 1px;
 }
 
-.form :where(.check input, .check label) {
+.form :where(.check input, .check textarea,.check label) {
     accent-color: var(--accent-color);
     cursor: pointer;
 }
@@ -218,11 +230,11 @@ body {
 }
 
 
-.form input {
+.form input ,.form textarea{
     color: var(--main-text-color);
 }
 
-.form input:focus {
+.form input:focus ,.form textarea:focus {
     background-color: var(--input-color-hover);
 }