Последняя активность 1 month ago

将 TOTP 二维码转换为 Markdown 表格

jetsung ревизий этого фрагмента 7 months ago. К ревизии

Без изменений

jetsung ревизий этого фрагмента 7 months ago. К ревизии

2 files changed, 2 insertions, 2 deletions

totp2md.py

@@ -3,7 +3,7 @@
3 3 #============================================================
4 4 # File: totp2md.py
5 5 # Description: 将 TOTP 二维码转换为 Markdown 表格
6 - # URL: https://s.fx4.cn/
6 + # URL: https://fx4.cn/
7 7 # ORIGIN: https://gist.asfd.cn/jetsung/totp2md/raw/HEAD/totp2md.py
8 8 # Author: Jetsung Chan <[email protected]>
9 9 # Version: 0.1.0

totp2md.sh

@@ -3,7 +3,7 @@
3 3 #============================================================
4 4 # File: totp2md.sh
5 5 # Description: 将 TOTP 二维码转换为 Markdown 表格
6 - # URL: https://s.fx4.cn/
6 + # URL: https://fx4.cn/
7 7 # ORIGIN: https://gist.asfd.cn/jetsung/totp2md/raw/HEAD/totp2md.sh
8 8 # Author: Jetsung Chan <[email protected]>
9 9 # Version: 0.1.0

jetsung ревизий этого фрагмента 9 months ago. К ревизии

1 file changed, 1 insertion

totp2md.sh

@@ -86,6 +86,7 @@ find "$TOTP_DIR" -type f \( -name "*.svg" -o -name "*.png" \) | while read -r fi
86 86 echo "text: $text"
87 87 echo
88 88
89 + # shellcheck disable=SC2006
89 90 echo "| $issuer_str | $username | $secret | `$text` | |" >> "$OUTPUT_FILE"
90 91 done
91 92

jetsung ревизий этого фрагмента 9 months ago. К ревизии

2 files changed, 288 insertions

totp2md.py(файл создан)

@@ -0,0 +1,186 @@
1 + #!/usr/bin/env python3
2 +
3 + #============================================================
4 + # File: totp2md.py
5 + # Description: 将 TOTP 二维码转换为 Markdown 表格
6 + # URL: https://s.fx4.cn/
7 + # ORIGIN: https://gist.asfd.cn/jetsung/totp2md/raw/HEAD/totp2md.py
8 + # Author: Jetsung Chan <[email protected]>
9 + # Version: 0.1.0
10 + # CreatedAt: 2025-08-18
11 + # UpdatedAt: 2025-08-18
12 + #============================================================
13 +
14 + import os
15 + import re
16 + import subprocess
17 + import urllib.parse
18 + from pathlib import Path
19 + import argparse
20 +
21 +
22 + def decode_qr_image(file_path):
23 + """使用 zbarimg 读取二维码内容"""
24 + try:
25 + result = subprocess.run(
26 + ['zbarimg', str(file_path)],
27 + stdout=subprocess.PIPE,
28 + stderr=subprocess.PIPE,
29 + text=True,
30 + check=True
31 + )
32 + lines = result.stdout.strip().splitlines()
33 + if lines:
34 + qr_text = lines[0].strip()
35 + if qr_text.startswith("QR-Code:"):
36 + qr_text = qr_text[len("QR-Code:"):]
37 + return qr_text.strip()
38 + except subprocess.CalledProcessError:
39 + pass # zbarimg 无法识别
40 + except FileNotFoundError:
41 + print("错误: 找不到 zbarimg,请确保已安装 ZBar 并加入 PATH。")
42 + exit(1)
43 + return None
44 +
45 +
46 + def parse_otpauth_uri(uri):
47 + """解析 otpauth://totp/ URI"""
48 + sitename = ""
49 + username = ""
50 + secret = ""
51 + issuer = ""
52 +
53 + try:
54 + from urllib.parse import urlparse, parse_qs
55 + parsed = urlparse(uri)
56 + query = parse_qs(parsed.query, keep_blank_values=True)
57 + secret = query.get('secret', [None])[0] or ""
58 +
59 + label = parsed.path.lstrip('/') # e.g., Google:[email protected]
60 + label = urllib.parse.unquote(label)
61 +
62 + issuer = query.get('issuer', [None])[0] or ""
63 + if issuer:
64 + issuer = urllib.parse.unquote(issuer)
65 +
66 + if ':' in label:
67 + parts = label.split(':', 1)
68 + sitename = parts[0].strip()
69 + username = parts[1].strip()
70 + else:
71 + username = label
72 +
73 + if not issuer:
74 + issuer = sitename or ""
75 +
76 + except Exception as e:
77 + print(f"解析 otpauth 失败: {e}")
78 + return sitename, username, secret, issuer
79 +
80 +
81 + def parse_ms_msa_uri(uri):
82 + """解析 ms-msa:// URI,提取 uaid 和 code"""
83 + username = ""
84 + secret = ""
85 + try:
86 + query_string = uri.split('//', 1)[1]
87 + # 使用 parse_qs 解析
88 + query = urllib.parse.parse_qs(query_string, keep_blank_values=True)
89 +
90 + username = query.get('uaid', [None])[0] or ""
91 + secret = query.get('code', [None])[0] or ""
92 +
93 + # 返回值:sitename, username, secret, issuer
94 + return "", username, secret, "Microsoft"
95 + except Exception as e:
96 + print(f"解析 ms-msa 失败: {e}")
97 + return "", "", "", "Microsoft"
98 +
99 +
100 + def main(input_dir, output_file):
101 + file_dir = Path(input_dir)
102 + if not file_dir.exists():
103 + print(f"目录不存在: {file_dir}")
104 + exit(1)
105 +
106 + # 获取所有 .svg 和 .png 文件
107 + image_files = []
108 + image_files.extend(file_dir.rglob("*.svg"))
109 + image_files.extend(file_dir.rglob("*.png"))
110 +
111 + # 按路径排序
112 + image_files.sort(key=lambda x: str(x).lower())
113 +
114 + # 写入 Markdown 表头
115 + with open(output_file, 'w', encoding='utf-8') as f:
116 + f.write("| SiteName | Username | Secret | Text | Mark |\n")
117 + f.write("|:---|:---|:---|:---|:---|\n")
118 +
119 + for file_path in image_files:
120 + qr_text = decode_qr_image(file_path)
121 + if not qr_text:
122 + print(f"无法读取二维码: {file_path}")
123 + continue
124 +
125 + # 转义 Markdown 中的 |
126 + text_display = qr_text.replace('|', r'\|')
127 +
128 + sitename = ""
129 + username = ""
130 + secret = ""
131 + issuer = ""
132 +
133 + if qr_text.startswith("otpauth://totp/"):
134 + sitename, username, secret, issuer = parse_otpauth_uri(qr_text)
135 + if not issuer:
136 + issuer = sitename or file_path.stem
137 + elif qr_text.startswith("ms-msa://"):
138 + sitename, username, secret, issuer = parse_ms_msa_uri(qr_text)
139 + else:
140 + # 其他类型
141 + sitename = file_path.stem
142 + username = ""
143 + secret = ""
144 + issuer = file_path.stem
145 +
146 + # 构造 SiteName 列:[issuer](相对路径)
147 + relative_path = file_path.as_posix()
148 + issuer_str = f"[{issuer}]({relative_path})"
149 +
150 + # 输出调试信息
151 + print(f"filename: {file_path}")
152 + print(f"sitename: {issuer}")
153 + print(f"username: {username}")
154 + print(f"secret: {secret}")
155 + print(f"text: {qr_text}")
156 + print()
157 +
158 + # 写入表格行
159 + f.write(f"| {issuer_str} | {username} | {secret} | `{text_display}` | |\n")
160 +
161 + print(f"✅ Markdown 表格已生成: {output_file}")
162 +
163 +
164 + if __name__ == "__main__":
165 + parser = argparse.ArgumentParser(description="从 TOTP 二维码图片生成 Markdown 表格")
166 + parser.add_argument(
167 + '-i', '--input',
168 + default="TOTP",
169 + help="二维码图片目录 (默认: TOTP)"
170 + )
171 + parser.add_argument(
172 + '-o', '--output',
173 + default="qr_codes.md",
174 + help="输出 Markdown 文件路径 (默认: qr_codes.md)"
175 + )
176 +
177 + args = parser.parse_args()
178 + main(args.input, args.output)
179 +
180 + ###
181 + #
182 + # 依赖 zbarimg
183 + # Windows:https://zbar.sourceforge.net/
184 + # Linux:https://github.com/mchehab/zbar
185 + #
186 + ###

totp2md.sh(файл создан)

@@ -0,0 +1,102 @@
1 + #!/usr/bin/env bash
2 +
3 + #============================================================
4 + # File: totp2md.sh
5 + # Description: 将 TOTP 二维码转换为 Markdown 表格
6 + # URL: https://s.fx4.cn/
7 + # ORIGIN: https://gist.asfd.cn/jetsung/totp2md/raw/HEAD/totp2md.sh
8 + # Author: Jetsung Chan <[email protected]>
9 + # Version: 0.1.0
10 + # CreatedAt: 2025-08-18
11 + # UpdatedAt: 2025-08-18
12 + #============================================================
13 +
14 +
15 + if [[ -n "${DEBUG:-}" ]]; then
16 + set -eux
17 + else
18 + set -euo pipefail
19 + fi
20 +
21 + TOTP_DIR="${1:-TOTP}"
22 + OUTPUT_FILE="${2:-qr_codes.md}"
23 +
24 + # Initialize Markdown table header
25 + echo "| SiteName | Username | Secret | Text | Mark |" > "$OUTPUT_FILE"
26 + echo "|:---|:---|:---|:---|:---|" >> "$OUTPUT_FILE"
27 +
28 + # Find all SVG and PNG files in the specified directory
29 + find "$TOTP_DIR" -type f \( -name "*.svg" -o -name "*.png" \) | while read -r file; do
30 + qr_text=$(zbarimg "$file" | head -n 1 2>/dev/null)
31 + qr_text=${qr_text/QR-Code:/}
32 + # qr_text=$(qrtool decode "$file" 2>/dev/null)
33 + # qr_text=$(totp-qr --uri "$file" 2>/dev/null)
34 +
35 + filename="$file"
36 +
37 + sitename=""
38 + username=""
39 + secret=""
40 + text="$qr_text"
41 + issuer=""
42 +
43 + # # Handle otpauth URLs
44 + if [[ "$qr_text" =~ ^otpauth://totp/ ]]; then
45 + label=$(echo "$qr_text" | grep -oP '(?<=totp/).*?(?=\?)')
46 + label=$(printf '%b' "${label//%/\\x}")
47 +
48 + issuer=$(echo "$qr_text" | grep -oP 'issuer=\K[^&]*' | head -1)
49 + issuer=$(printf '%b' "${issuer//%/\\x}")
50 +
51 + if [ -n "$label" ]; then
52 + if [[ "$label" =~ ^([^:]+):(.+)$ ]]; then
53 + sitename="${BASH_REMATCH[1]}"
54 + username="${BASH_REMATCH[2]}"
55 + else
56 + username="$label"
57 + fi
58 + fi
59 +
60 + if [ -z "$issuer" ]; then
61 + issuer="$sitename"
62 + fi
63 +
64 + secret=$(echo "$qr_text" | grep -oP 'secret=\K[^&]*' | head -1)
65 +
66 + elif [[ "$qr_text" =~ ^ms-msa:// ]]; then
67 + username=$(echo "$qr_text" | grep -oP 'uaid=\K[^&]*' | head -1)
68 + secret=$(echo "$qr_text" | grep -oP 'code=\K[^&]*' | head -1)
69 + else
70 + sitename="$filename"
71 + username=""
72 + secret=""
73 + fi
74 +
75 + text=${text//|/\\|}
76 +
77 + if [ -z "$issuer" ]; then
78 + issuer=$(basename "$filename")
79 + fi
80 + issuer_str="[$issuer]($filename)"
81 +
82 + echo "filename: $filename"
83 + echo "sitename: $issuer"
84 + echo "username: $username"
85 + echo "secret: $secret"
86 + echo "text: $text"
87 + echo
88 +
89 + echo "| $issuer_str | $username | $secret | `$text` | |" >> "$OUTPUT_FILE"
90 + done
91 +
92 + echo "Markdown table generated in $OUTPUT_FILE"
93 +
94 + ###
95 + #
96 + # 参数1: TOTP 目录
97 + # 参数2: 输出文件
98 + #
99 + # 示例:
100 + # ./totp2md.sh TOTP qr_codes.md
101 + #
102 + ###
Новее Позже