🔄 卡若AI 同步 2026-03-21 12:19 | 更新:Cursor规则、金仓、水桥平台对接、水溪整理归档、卡木、总索引与入口、运营中枢、运营中枢工作台 | 排除 >20MB: 11 个
This commit is contained in:
3
.cursor/skills/douyin-unban-appeal/SKILL.md
Normal file
3
.cursor/skills/douyin-unban-appeal/SKILL.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 抖音账号申诉(已合并)
|
||||
|
||||
→ Read **`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**(第二节 抖音)。
|
||||
7
.cursor/skills/platform-account-appeal/SKILL.md
Normal file
7
.cursor/skills/platform-account-appeal/SKILL.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 平台账号申诉 / 解封(Soul · 抖音 · 小红书)
|
||||
|
||||
**完整渠道、脚本命令、话术原则** → Read:
|
||||
|
||||
`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`
|
||||
|
||||
**触发词**:Soul 解封、抖音解封、小红书解封、账号申诉、视频违规、人工复核。
|
||||
3
.cursor/skills/soul-unban-appeal/SKILL.md
Normal file
3
.cursor/skills/soul-unban-appeal/SKILL.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Soul 账号申诉(已合并)
|
||||
|
||||
→ Read **`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**(第一节 Soul)。
|
||||
@@ -1,7 +1,31 @@
|
||||
{
|
||||
"updated": "2026-03-20T15:22:36.863623+00:00",
|
||||
"updated": "2026-03-21T04:19:00.079701+00:00",
|
||||
"conversations": [
|
||||
{
|
||||
"对话ID": "7911abf7-8c1b-447d-acfa-834c85079e8b",
|
||||
"名称": "科室 API 接口配置",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "那个我需要一个接口,这个可以直接用科室的这个现在本机电脑这个科室,那我直接可以用变成一个接口,并且可以直接调用科室的相应的接口的一个功能来回复这个事情,那我可以再做一个尝试,比如在那个都可以直接用科室的这一个功能。可以直接在终端或者其他的那个 tray 或者其他的地方直接可以配置这个 API,以及有 API 地址,有 key 地址也知道消耗多少 TOKEN",
|
||||
"创建时间": "2026-03-21T04:15:58.097000+00:00",
|
||||
"消息数量": 55
|
||||
},
|
||||
{
|
||||
"对话ID": "8eb0e1d1-6c55-4727-b7b5-eb4f39faec33",
|
||||
"名称": "注册信用卡技能和信息",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "那个告诉我的注册信用卡的 skill,然后把这个我最近注册的卡发给我",
|
||||
"创建时间": "2026-03-20T23:40:45.815000+00:00",
|
||||
"消息数量": 13
|
||||
},
|
||||
{
|
||||
"对话ID": "93c2142a-5a2a-46c4-ba71-a09d3b195215",
|
||||
"名称": "AI Brain project exploration",
|
||||
"项目": "微信管理",
|
||||
"首条消息": "I need to explore the AI Brain related code and scripts in the work phone project. Please find and summarize:\n\n1. All files related to AI Brain in the project at /Users/karuo/Documents/开发/2、私域银行/工作手机/\n2. All skill files under sdk/agent/skills/ - what skills exist and their actions\n3. The ai_brain module if it exists under sdk/agent/\n4. The index.html smart engine / AI Brain section (search for \"智能引擎\" or \"AI Brain\" in the static files)\n5. Any existing scripts or automation modules\n6. The wechat_h",
|
||||
"创建时间": "2026-03-20T16:31:04.926000+00:00",
|
||||
"消息数量": 1
|
||||
},
|
||||
{
|
||||
"对话ID": "f756e455-b371-44e7-841e-ada153aefeee",
|
||||
"名称": "WeChat account and device management UI",
|
||||
"项目": "微信管理",
|
||||
|
||||
7
02_卡人(水)/水桥_平台对接/Douyin账号申诉解封/SKILL.md
Normal file
7
02_卡人(水)/水桥_平台对接/Douyin账号申诉解封/SKILL.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 抖音账号申诉(已合并)
|
||||
|
||||
Soul / 抖音 / 小红书 **统一规范与脚本** 已合并至:
|
||||
|
||||
**`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**
|
||||
|
||||
请直接打开该文件执行;抖音发信:`运营中枢/scripts/send_douyin_appeal_mail.py`。
|
||||
7
02_卡人(水)/水桥_平台对接/Soul账号申诉解封/SKILL.md
Normal file
7
02_卡人(水)/水桥_平台对接/Soul账号申诉解封/SKILL.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Soul 账号申诉(已合并)
|
||||
|
||||
Soul / 抖音 / 小红书 **统一规范与脚本** 已合并至:
|
||||
|
||||
**`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**
|
||||
|
||||
请直接打开该文件执行;Soul 发信:`运营中枢/scripts/send_soul_appeal_mail.py`。
|
||||
84
02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md
Normal file
84
02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 平台账号申诉 / 解封(Soul · 抖音 · 小红书)水桥
|
||||
|
||||
> **统一触发词**:Soul 解封、Soul 申诉、抖音解封、抖音申诉、小红书解封、小红书申诉、账号封禁、账号限制、视频违规、人工复核、换绑手机、feedback@douyin、soul@soulapp、service@xiaohongshu
|
||||
> **归属**:卡若AI · 水组 · 水桥_平台对接(面向各 App **官方公示渠道**,非各项目后端代码)
|
||||
|
||||
---
|
||||
|
||||
## 〇、总则(必读)
|
||||
|
||||
1. **不存在「一定解封」**的话术、邮箱或付费捷径;结果由平台审核决定。
|
||||
2. **各平台首选 App 内**:安全中心 / 帮助与客服 / 申诉工单,邮件多为**补充**。
|
||||
3. **SMTP**:本机 QQ 邮箱授权码,读 `运营中枢/scripts/karuo_ai_gateway/.env`(`SMTP_USER` / `SMTP_PASS`)。勿把授权码写入对话或提交 Git。
|
||||
4. **脚本路径**(均相对卡若AI仓库根):
|
||||
- Soul:`运营中枢/scripts/send_soul_appeal_mail.py`
|
||||
- 抖音:`运营中枢/scripts/send_douyin_appeal_mail.py`
|
||||
- 小红书:`运营中枢/scripts/send_xiaohongshu_appeal_mail.py`
|
||||
5. **职能/商务邮箱**:与账号处罚不对口时**不抄送**(避免无效与反感);下表已标注。
|
||||
|
||||
---
|
||||
|
||||
## 一、Soul
|
||||
|
||||
| 渠道 | 内容 |
|
||||
|:---|:---|
|
||||
| 邮件 | `soul@soulapp.cn`(主);用户曾要求时脚本会一并抄送 `hr@` / `ad@` / `commercial-b@` / `pc@`(官网「联系我们」公示) |
|
||||
| 电话 | `400-9030057` 客户服务;`400-9030142` 为不良信息举报专线 |
|
||||
| 官网 | <https://www.soulapp.cn/contact> |
|
||||
|
||||
**发信**(11 位手机号为绑定号,两版话术):
|
||||
`python3 运营中枢/scripts/send_soul_appeal_mail.py 15210897710`
|
||||
`python3 运营中枢/scripts/send_soul_appeal_mail.py 13779954946`
|
||||
|
||||
---
|
||||
|
||||
## 二、抖音
|
||||
|
||||
| 渠道 | 内容 |
|
||||
|:---|:---|
|
||||
| 邮件(协议公示) | **`feedback@douyin.com`** ——《「抖音」用户服务协议》1.5 条 |
|
||||
| App | 反馈与帮助、抖音安全中心、违规详情与申诉入口 |
|
||||
| 说明 | `qinquan@bytedance.com` 等为**侵权举报指引**中的权利人投诉通道,**不作为**本人账号社区处罚申诉的主收件人 |
|
||||
|
||||
**发信**:`python3 运营中枢/scripts/send_douyin_appeal_mail.py Lkdie001`(抖音号可换)
|
||||
|
||||
---
|
||||
|
||||
## 三、小红书
|
||||
|
||||
**官网「关于我们 / 合作邮箱」公示**(<https://www.xiaohongshu.com/contact>,以页面更新为准):
|
||||
|
||||
| 邮箱 | 用途(公示语义) | 与账号申诉相关性 |
|
||||
|:---|:---|:---|
|
||||
| **service@xiaohongshu.com** | 客服反馈 | **高,主投** |
|
||||
| **community@xiaohongshu.com** | 社区反馈 | **高,与社区处罚相关** |
|
||||
| **app_feedback@xiaohongshu.com** | 产品反馈 | 中,账号/登录/功能异常可抄送 |
|
||||
| **shuduizhang@xiaohongshu.com** | 薯队长 | 中,社区侧形象入口 |
|
||||
| **ceo@xiaohongshu.com** | 重大疑难与建议(页面要求写清身份、联系方式、描述) | **低频次**,多次无果再斟酌,勿滥用 |
|
||||
| copyright@ / 各 bd_* / career@ / media@ 等 | 侵权、商务、招聘、媒体 | **不用于**普通账号解封群发 |
|
||||
|
||||
网传客服电话 **400-680-9966** 等,**以 App 内及官网最新公示为准**。
|
||||
|
||||
**发信**(绑定手机,大白话+正式混合):
|
||||
`python3 运营中枢/scripts/send_xiaohongshu_appeal_mail.py 15880802661`
|
||||
|
||||
脚本默认收件人:`service@` + `community@` + `app_feedback@` + `shuduizhang@`(提高触达社区与客服链路的概率,**非**官方承诺解封)。
|
||||
|
||||
---
|
||||
|
||||
## 四、申诉写法共性
|
||||
|
||||
- 写清账号标识(Soul 手机、抖音号、小红书绑定手机等)。
|
||||
- 说明处罚现象与**是否看不清具体规则/条目**。
|
||||
- 态度配合、愿整改;少辱骂、少重复刷屏。
|
||||
- 身份证等敏感材料按**平台或回信要求**再提供。
|
||||
|
||||
---
|
||||
|
||||
## 五、维护
|
||||
|
||||
- Soul:<https://www.soulapp.cn/contact>
|
||||
- 抖音协议:<https://www.douyin.com/agreements/?id=6773906068725565448>
|
||||
- 小红书联系页:<https://www.xiaohongshu.com/contact>
|
||||
|
||||
子目录 `Soul账号申诉解封/`、`Douyin账号申诉解封/` 内 SKILL 已改为**跳转本文件**,避免重复维护。
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"access_token": "u-elJqv3tWdeGrDSyG63Up4Vlh1KzxghqXN0GaENk0260E",
|
||||
"refresh_token": "ur-eNhTsg0X9f5a7kk6iFBlqDlh1e1xghOrNwGaJB40261Y",
|
||||
"access_token": "u-cBzPn.j5p4MaG8NPGyESV.lh1A91ghOhVMGaVxg023gE",
|
||||
"refresh_token": "ur-fdo6mIqCldgWDbYpFbRKailh3eb1ghoPp0GaYM40230F",
|
||||
"name": "飞书用户",
|
||||
"auth_time": "2026-03-20T21:02:49.258622"
|
||||
"auth_time": "2026-03-21T12:19:03.132157"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
2026-03-20
|
||||
2026-03-21
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"updated": "2026-03-20 11:26:57",
|
||||
"date": "2026-03-20",
|
||||
"updated": "2026-03-21 12:18:44",
|
||||
"date": "2026-03-21",
|
||||
"scan_total": 0,
|
||||
"copied_new": 0,
|
||||
"skipped_idempotent": 0,
|
||||
|
||||
@@ -119,20 +119,46 @@ def build_typewriter_subtitle_images(
|
||||
max_steps_per_line=28,
|
||||
):
|
||||
"""
|
||||
将每条字幕拆成多帧:同一时间段内前缀逐字(逐段)变长,读起来更顺、更像跟读语音。
|
||||
长句按步数上限均分字符,避免单条 concat 段过多。
|
||||
逐词/逐字渐显:
|
||||
- 若字幕带 word_times(whisper word-level SRT),按词的真实开始时间逐词追加,与人声严格同步;
|
||||
- 否则按字符数等分句子时长(兜底)。
|
||||
subtitle_overlay_start:最早显示字幕的时间轴(秒),须 ≥ 封面结束 + 留白。
|
||||
"""
|
||||
sub_images = []
|
||||
img_idx = 0
|
||||
|
||||
for sub in subtitles:
|
||||
safe_text = improve_subtitle_punctuation(_improve_subtitle_text(sub["text"]))
|
||||
if not safe_text or not safe_text.strip():
|
||||
continue
|
||||
s, e = float(sub["start"]), float(sub["end"])
|
||||
s = max(s, subtitle_overlay_start)
|
||||
if s >= e - 0.02:
|
||||
continue
|
||||
|
||||
word_times = sub.get("word_times")
|
||||
|
||||
# ── 路径 A:word-level 时间轴(精准逐词) ────────────────────────────
|
||||
if word_times and len(word_times) > 1:
|
||||
accumulated = ""
|
||||
for wi, wt in enumerate(word_times):
|
||||
w_start = max(float(wt["start"]), subtitle_overlay_start)
|
||||
if wi + 1 < len(word_times):
|
||||
w_end = float(word_times[wi + 1]["start"])
|
||||
else:
|
||||
w_end = e
|
||||
w_end = max(w_start + min_step_sec, w_end)
|
||||
accumulated += wt["word"]
|
||||
clean = improve_subtitle_punctuation(_improve_subtitle_text(accumulated))
|
||||
if not (clean or "").strip():
|
||||
continue
|
||||
img_path = os.path.join(temp_dir, f"sub_{img_idx:04d}.png")
|
||||
create_subtitle_image(clean, out_w, out_h, img_path)
|
||||
sub_images.append({"path": img_path, "start": w_start, "end": w_end})
|
||||
img_idx += 1
|
||||
continue
|
||||
|
||||
# ── 路径 B:按字符数等分(兜底) ─────────────────────────────────────
|
||||
safe_text = improve_subtitle_punctuation(_improve_subtitle_text(sub["text"]))
|
||||
if not safe_text or not safe_text.strip():
|
||||
continue
|
||||
dur = e - s
|
||||
chars = list(safe_text)
|
||||
n = len(chars)
|
||||
@@ -142,7 +168,6 @@ def build_typewriter_subtitle_images(
|
||||
sub_images.append({"path": img_path, "start": s, "end": e})
|
||||
img_idx += 1
|
||||
continue
|
||||
# 步数:不超过 max_steps,且每步至少 min_step_sec(极短句仍保证可见)
|
||||
num_steps = min(max_steps_per_line, n)
|
||||
num_steps = max(2, num_steps)
|
||||
step_dur = dur / num_steps
|
||||
@@ -161,6 +186,7 @@ def build_typewriter_subtitle_images(
|
||||
create_subtitle_image(partial, out_w, out_h, img_path)
|
||||
sub_images.append({"path": img_path, "start": t0, "end": t1})
|
||||
img_idx += 1
|
||||
|
||||
return sub_images
|
||||
|
||||
# 繁转简(OpenCC 优先,否则用映射)
|
||||
@@ -554,6 +580,28 @@ def _improve_subtitle_text(text: str) -> str:
|
||||
return t
|
||||
|
||||
|
||||
def _detect_word_level_srt(srt_path: str) -> bool:
|
||||
"""检测 SRT 是否为 whisper word-level 输出(每条时长 <= 1.0s 且文字极短)。"""
|
||||
try:
|
||||
with open(srt_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
blocks = [b.strip() for b in content.strip().split('\n\n') if b.strip()]
|
||||
if len(blocks) < 20:
|
||||
return False
|
||||
short = 0
|
||||
for b in blocks[:60]:
|
||||
lines = b.splitlines()
|
||||
if len(lines) < 3:
|
||||
continue
|
||||
text = ' '.join(lines[2:]).strip()
|
||||
m = re.match(r'\d{2}:\d{2}:\d{2},\d{3} --> (\d{2}:\d{2}:\d{2},\d{3})', lines[1])
|
||||
if m and len(text) <= 6 and not ' ' in text:
|
||||
short += 1
|
||||
return short >= 25 # 大多数条目都是单词/单字
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def parse_srt_for_clip(srt_path, start_sec, end_sec, delay_sec=None):
|
||||
"""解析SRT,提取指定时间段的字幕。
|
||||
|
||||
@@ -561,8 +609,9 @@ def parse_srt_for_clip(srt_path, start_sec, end_sec, delay_sec=None):
|
||||
1. 字幕延迟补偿(delay_sec):补偿 FFmpeg input seeking 关键帧偏移(2s 默认)
|
||||
2. 噪声行过滤:去掉单字母 L / Agent 等 ASR 幻觉行
|
||||
3. 文字质量提升:纠错 + 违禁词替换 + 通畅度修正
|
||||
4. 合并过短字幕:相邻 <1.5s 时自动合并,减少闪烁
|
||||
5. 最小显示时长:每条至少 1.5s,避免一闪而过
|
||||
4. whisper word-level SRT 自动识别:把单字/词条目先聚合成完整句,再用词时间轴做逐词显示
|
||||
5. 合并过短字幕:相邻 <1.5s 且总长 <28字自动合并,减少闪烁
|
||||
6. 最小显示时长:每条至少 1.5s,避免一闪而过
|
||||
"""
|
||||
if delay_sec is None:
|
||||
delay_sec = SUBTITLE_DELAY_SEC
|
||||
@@ -578,6 +627,66 @@ def parse_srt_for_clip(srt_path, start_sec, end_sec, delay_sec=None):
|
||||
parts = t.split(':')
|
||||
return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
|
||||
|
||||
# --- word-level SRT:聚合后附带词时间轴 ---
|
||||
is_word_level = _detect_word_level_srt(srt_path)
|
||||
if is_word_level:
|
||||
# 收集时间窗口内的所有单词条目
|
||||
word_entries = []
|
||||
for match in matches:
|
||||
ws = time_to_sec(match[1])
|
||||
we = time_to_sec(match[2])
|
||||
text = match[3].strip()
|
||||
if _is_noise_line(text):
|
||||
continue
|
||||
if we > start_sec and ws < end_sec + 2:
|
||||
word_entries.append({
|
||||
'abs_start': ws,
|
||||
'abs_end': we,
|
||||
'word': text,
|
||||
})
|
||||
|
||||
# 将连续词按句子边界聚合(间隔 > 0.7s 切句)
|
||||
SENT_GAP = 0.7
|
||||
MAX_SENT_CHARS = 22
|
||||
sentences = []
|
||||
cur_words = []
|
||||
for w in word_entries:
|
||||
if cur_words:
|
||||
gap = w['abs_start'] - cur_words[-1]['abs_end']
|
||||
cur_len = sum(len(x['word']) for x in cur_words) + len(w['word'])
|
||||
if gap > SENT_GAP or cur_len > MAX_SENT_CHARS:
|
||||
sentences.append(cur_words)
|
||||
cur_words = []
|
||||
cur_words.append(w)
|
||||
if cur_words:
|
||||
sentences.append(cur_words)
|
||||
|
||||
# 每句生成一条带词时间轴的字幕
|
||||
result_subs = []
|
||||
for sent_words in sentences:
|
||||
full_text = ''.join(x['word'] for x in sent_words)
|
||||
improved = _improve_subtitle_text(full_text)
|
||||
if not improved or len(improved) < 2:
|
||||
continue
|
||||
rel_start = max(0, sent_words[0]['abs_start'] - start_sec + delay_sec)
|
||||
rel_end = sent_words[-1]['abs_end'] - start_sec + delay_sec
|
||||
if rel_start >= rel_end:
|
||||
rel_end = rel_start + max(1.5, len(improved) * 0.12)
|
||||
result_subs.append({
|
||||
'start': rel_start,
|
||||
'end': rel_end,
|
||||
'text': improved,
|
||||
'word_times': [
|
||||
{
|
||||
'word': w['word'],
|
||||
'start': max(0, w['abs_start'] - start_sec + delay_sec),
|
||||
'end': w['abs_end'] - start_sec + delay_sec,
|
||||
}
|
||||
for w in sent_words
|
||||
],
|
||||
})
|
||||
return result_subs
|
||||
|
||||
raw_subs = []
|
||||
for match in matches:
|
||||
sub_start = time_to_sec(match[1])
|
||||
@@ -1392,28 +1501,53 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
print(f" ⚠ 未烧录字幕:解析后无有效字幕(请用 MLX Whisper 重新生成 transcript.srt)", flush=True)
|
||||
print(f" [3/5] 字幕跳过", flush=True)
|
||||
|
||||
# 5.3 加速10% + 音频同步(成片必做)
|
||||
print(f" [4/5] 加速 10% + 去语助词(已在上步字幕解析中清理)…", flush=True)
|
||||
# 5.3 加速10% + 音频增强 + 同步(成片必做)
|
||||
print(f" [4/5] 加速 10% + 音频清晰化…", flush=True)
|
||||
speed_output = os.path.join(temp_dir, 'speed.mp4')
|
||||
atempo = 1.0 / SPEED_FACTOR # 音频需要反向调整
|
||||
|
||||
|
||||
# 音频处理链:高通去低频噪声 → 动态降噪 → 人声压缩增益 → 音量归一化
|
||||
audio_enhance = (
|
||||
"highpass=f=120," # 去掉 120Hz 以下的低频噪声/嗡嗡声
|
||||
"lowpass=f=10000," # 去掉 10kHz 以上的高频噪声
|
||||
"afftdn=nf=-30," # FFT 降噪(-30dBFS 噪底)
|
||||
"compand=0.02|0.02:0.05|0.05:-60/-60|-30/-15|-20/-10|0/-3:6:0:0:0.02," # 动态压缩:抬升安静部分
|
||||
"loudnorm=I=-16:LRA=7:TP=-1.5" # EBU R128 响度归一化
|
||||
)
|
||||
# 加速 + 音频增强 合并成一次 ffmpeg
|
||||
cmd = [
|
||||
'ffmpeg', '-y', '-i', current_video,
|
||||
'-filter_complex', f"[0:v]setpts={1/SPEED_FACTOR}*PTS[v];[0:a]atempo={SPEED_FACTOR}[a]",
|
||||
'-filter_complex',
|
||||
f"[0:v]setpts={1/SPEED_FACTOR}*PTS[v][v];"
|
||||
f"[0:a]atempo={SPEED_FACTOR},{audio_enhance}[a]",
|
||||
'-map', '[v]', '-map', '[a]',
|
||||
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
|
||||
'-c:a', 'aac', '-b:a', '128k',
|
||||
'-c:a', 'aac', '-b:a', '192k',
|
||||
speed_output
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode == 0 and os.path.exists(speed_output):
|
||||
current_video = speed_output
|
||||
print(f" ✓ 加速 10% 完成", flush=True)
|
||||
print(f" ✓ 加速 10% + 音频增强完成", flush=True)
|
||||
else:
|
||||
print(f" ⚠ 加速步骤失败,沿用当前视频继续", file=sys.stderr)
|
||||
if result.stderr:
|
||||
print(f" {str(result.stderr)[:300]}", file=sys.stderr)
|
||||
print(f" ⚠ 加速步骤失败,尝试仅加速(跳过音频增强)", file=sys.stderr)
|
||||
# 降级:只做加速,不做音频增强
|
||||
cmd_fallback = [
|
||||
'ffmpeg', '-y', '-i', current_video,
|
||||
'-filter_complex', f"[0:v]setpts={1/SPEED_FACTOR}*PTS[v];[0:a]atempo={SPEED_FACTOR}[a]",
|
||||
'-map', '[v]', '-map', '[a]',
|
||||
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
|
||||
'-c:a', 'aac', '-b:a', '128k',
|
||||
speed_output
|
||||
]
|
||||
result2 = subprocess.run(cmd_fallback, capture_output=True, text=True)
|
||||
if result2.returncode == 0 and os.path.exists(speed_output):
|
||||
current_video = speed_output
|
||||
print(f" ✓ 加速完成(降级版,无音频增强)", flush=True)
|
||||
else:
|
||||
print(f" ⚠ 加速步骤失败,沿用当前视频继续", file=sys.stderr)
|
||||
if result2.stderr:
|
||||
print(f" {str(result2.stderr)[:300]}", file=sys.stderr)
|
||||
|
||||
# 5.4 输出:竖条(宽由 vf)或全画面 letterbox
|
||||
if vertical and not vertical_fit_full:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 卡若AI 技能注册表(Skill Registry)
|
||||
|
||||
> **一张表查所有技能**。任何 AI 拿到这张表,就能按关键词找到对应技能的 SKILL.md 路径并执行。
|
||||
> 75 技能 | 15 成员 | 5 负责人
|
||||
> 版本:5.5 | 更新:2026-03-13
|
||||
> 76 技能 | 15 成员 | 5 负责人
|
||||
> 版本:5.8 | 更新:2026-03-21
|
||||
>
|
||||
> **技能配置、安装、删除、掌管人登记** → 见 **`运营中枢/工作台/01_技能控制台.md`**。
|
||||
|
||||
@@ -91,13 +91,14 @@
|
||||
| W08 | 智能纪要 | 水桥 | 会议纪要、产研纪要、**飞书妙记、飞书链接、妙记下载、第几场、指定场次、批量下载妙记、cunkebao.feishu.cn、meetings.feishu.cn/minutes** | `02_卡人(水)/水桥_平台对接/智能纪要/SKILL.md` | 会议录音转结构化纪要;飞书妙记识别与下载(单条/批量),完毕用复盘格式回复 |
|
||||
| W09 | 小程序管理 | 水桥 | 小程序、微信小程序 | `02_卡人(水)/水桥_平台对接/小程序管理/SKILL.md` | 微信小程序发布与维护 |
|
||||
| W10 | Soul创业实验 | 水桥 | **Soul创业实验、写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则、Soul文章上传、第9章上传、soul上传、写soul文章、运营报表、派对填表、派对纪要** | `02_卡人(水)/水桥_平台对接/Soul创业实验/SKILL.md` | 写作+上传+运营报表统一入口;第9章规范与小程序上传见本 Skill 子类 |
|
||||
| W10b | **平台账号申诉解封** | 水桥 | **Soul解封、Soul申诉、抖音解封、抖音申诉、小红书解封、小红书申诉、账号封禁、视频违规、人工复核、换绑手机、soul@soulapp、feedback@douyin、service@xiaohongshu** | `02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md` | Soul/抖音/小红书官方渠道+三 SMTP 脚本;子目录 SKILL 已跳转本文件 |
|
||||
| W11 | Soul派对运营报表 | 水桥 | **运营报表、派对填表、派对截图填表发群、派对纪要、智能纪要、106场、107场、本月运营数据** | `02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md` | 派对截图+TXT→飞书运营报表→智能纪要→飞书群推送,含Token自刷新与写入校验 |
|
||||
| W11a | Soul发到素材库 | 水桥 | **Soul发到素材库、成片发飞书、切片发飞书、视频分发飞书、发到素材库** | `02_卡人(水)/水桥_平台对接/飞书管理/Soul发到素材库_SKILL.md` | 成片→飞书内容看板,含附件+多平台描述,可打包基因胶囊 |
|
||||
| W12 | MCP 搜索与连接 | 水桥 | **MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现、查MCP、装MCP** | `02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md` | 搜索 5000+ MCP 服务器→生成安装配置→写入 Cursor/Claude 等 |
|
||||
| W13 | Excel表格与日报 | 水桥 | **Excel写飞书、Excel导入飞书、批量写飞书表格、飞书表格导入、CSV写飞书、日报图表发飞书、表格日报** | `02_卡人(水)/水桥_平台对接/飞书管理/Excel表格与日报_SKILL.md` | 本地 Excel/CSV→飞书表格→自动日报图表→发飞书群 |
|
||||
| W14 | **卡猫复盘** | 水桥 | **卡猫复盘、婼瑄复盘、卡猫今日复盘、婼瑄今日、复盘到卡猫、发卡猫群** | `02_卡人(水)/水桥_平台对接/飞书管理/卡猫复盘/SKILL.md` | 婼瑄目录→目标=今年总目标+完成%+人/事/数具体→飞书+卡猫群 |
|
||||
| W15 | **接收短信** | 水桥 | **接收短信、收短信、receivesms、接码、临时号码、获取短信、拿短信、等刷新拿短信** | `02_卡人(水)/水桥_平台对接/接收短信/SKILL.md` | receivesms.co 取英国临时号→命令行抓该号最新一条短信(可 --wait 等刷新);输出号码+短信,含「要获取的网站短信类型」说明 |
|
||||
|| W16 | **飞书JSON格式** | 水桥 | **飞书json、飞书json格式、飞书block、飞书块格式、飞书文档格式、json上传飞书、飞书格式怎么写、block_type、飞书块类型、飞书callout、飞书高亮块、飞书代码块** | `02_卡人(水)/水桥_平台对接/飞书管理/飞书JSON格式_SKILL.md` | 飞书文档 JSON 格式速查/编写/上传;block_type 全覆盖、Markdown 转换对照、API 一站式参考 |
|
||||
| W16 | **飞书JSON格式** | 水桥 | **飞书json、飞书json格式、飞书block、飞书块格式、飞书文档格式、json上传飞书、飞书格式怎么写、block_type、飞书块类型、飞书callout、飞书高亮块、飞书代码块** | `02_卡人(水)/水桥_平台对接/飞书管理/飞书JSON格式_SKILL.md` | 飞书文档 JSON 格式速查/编写/上传;block_type 全覆盖、Markdown 转换对照、API 一站式参考 |
|
||||
| W17 | **项目管理中枢** | **水岸** | **项目管理、水岸、项目总览、管理项目、新建项目、项目列表、卡若创业派对、Soul项目管理、派对全流程** | `02_卡人(水)/水岸_项目管理/SKILL.md` | 通用项目管理:每项目独立目录(人设+技能+凭证+流程),跨组调度五行资源;首个项目=卡若创业派对 |
|
||||
|
||||
## 木组 · 卡木(产品内容创造)
|
||||
|
||||
@@ -112,6 +112,25 @@ curl -s "http://127.0.0.1:8000/v1/skills" \
|
||||
curl -s "http://127.0.0.1:8000/v1/health"
|
||||
```
|
||||
|
||||
#### 4.4 /v1/usage(科室 TOKEN 累计)
|
||||
|
||||
本进程内按 **tenant(科室 Key)** 累计 `prompt_tokens` / `completion_tokens` / `total_tokens` 与请求次数;**重启网关会清零**。
|
||||
|
||||
```bash
|
||||
curl -s "http://127.0.0.1:8000/v1/usage" \
|
||||
-H "X-Karuo-Api-Key: <dept_key>"
|
||||
# 或与 Cursor 一致:
|
||||
curl -s "http://127.0.0.1:8000/v1/usage" \
|
||||
-H "Authorization: Bearer <dept_key>"
|
||||
```
|
||||
|
||||
单次对话消耗:
|
||||
|
||||
- `POST /v1/chat` 的 JSON 里带 `usage`、`usage_estimated`(`true` 表示上游未返回 usage,为网关按字符粗略估算)。
|
||||
- `POST /v1/chat/completions`(OpenAI 兼容)的 JSON 里带标准字段 `usage`;若为估算则额外有 `karuo_usage_estimated: true`。
|
||||
|
||||
**说明**:Cursor 编辑器本身的订阅用量请在 Cursor 账户里查看;此处统计的是 **网关 → 你配置的 OPENAI 兼容上游** 的 token(或估算值)。
|
||||
|
||||
## Cursor 配置(OpenAI 兼容)
|
||||
|
||||
如果你希望在 Cursor 的「API Keys」里把卡若AI网关当成一个 OpenAI 兼容后端:
|
||||
|
||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
import hashlib
|
||||
@@ -14,6 +15,8 @@ import smtplib
|
||||
from email.message import EmailMessage
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
_usage_lock = threading.Lock()
|
||||
|
||||
import yaml
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, StreamingResponse
|
||||
@@ -206,12 +209,69 @@ class ChatResponse(BaseModel):
|
||||
skill_id: str = ""
|
||||
matched_skill: str
|
||||
skill_path: str
|
||||
# 本轮大模型消耗(上游返回则为准;否则为按字符粗略估算)
|
||||
usage: Optional[Dict[str, int]] = None
|
||||
usage_estimated: bool = False
|
||||
|
||||
|
||||
def _llm_settings(cfg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return (cfg or {}).get("llm") or {}
|
||||
|
||||
|
||||
def _extract_usage_from_openai_json(data: Dict[str, Any]) -> Optional[Dict[str, int]]:
|
||||
u = data.get("usage")
|
||||
if not isinstance(u, dict):
|
||||
return None
|
||||
try:
|
||||
pt = u.get("prompt_tokens")
|
||||
ct = u.get("completion_tokens")
|
||||
tt = u.get("total_tokens")
|
||||
pt_i = int(pt) if pt is not None else None
|
||||
ct_i = int(ct) if ct is not None else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if pt_i is None and ct_i is None:
|
||||
return None
|
||||
pt_i = int(pt_i or 0)
|
||||
ct_i = int(ct_i or 0)
|
||||
tt_i = int(tt) if tt is not None else (pt_i + ct_i)
|
||||
return {"prompt_tokens": pt_i, "completion_tokens": ct_i, "total_tokens": tt_i}
|
||||
|
||||
|
||||
def _rough_token_estimate(text: str) -> int:
|
||||
"""中英混合粗略折算(上游未返回 usage 时使用,仅作观测)。"""
|
||||
if not text:
|
||||
return 0
|
||||
return max(1, len(text) // 3)
|
||||
|
||||
|
||||
def _estimate_usage_tokens(prompt: str, reply: str) -> Dict[str, int]:
|
||||
pt = _rough_token_estimate(prompt)
|
||||
ct = _rough_token_estimate(reply)
|
||||
return {"prompt_tokens": pt, "completion_tokens": ct, "total_tokens": pt + ct}
|
||||
|
||||
|
||||
def _get_or_create_usage_stats() -> Dict[str, Any]:
|
||||
if not hasattr(app.state, "_usage_stats") or app.state._usage_stats is None:
|
||||
app.state._usage_stats = {"started_at": int(time.time()), "by_tenant": {}}
|
||||
return app.state._usage_stats
|
||||
|
||||
|
||||
def _record_gateway_usage(tenant_id: str, usage: Dict[str, int]) -> None:
|
||||
tid = (tenant_id or "").strip() or "_open"
|
||||
with _usage_lock:
|
||||
st = _get_or_create_usage_stats()
|
||||
row = st["by_tenant"].setdefault(
|
||||
tid,
|
||||
{"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "requests": 0},
|
||||
)
|
||||
row["prompt_tokens"] += int(usage.get("prompt_tokens", 0) or 0)
|
||||
row["completion_tokens"] += int(usage.get("completion_tokens", 0) or 0)
|
||||
row["total_tokens"] += int(usage.get("total_tokens", 0) or 0)
|
||||
row["requests"] += 1
|
||||
st["last_ts"] = int(time.time())
|
||||
|
||||
|
||||
def _split_csv_env(value: str) -> List[str]:
|
||||
if not value:
|
||||
return []
|
||||
@@ -509,11 +569,17 @@ def _send_provider_alert(cfg: Dict[str, Any], errors: List[str], prompt: str, ma
|
||||
s.send_message(msg)
|
||||
|
||||
|
||||
def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, skill_path: str) -> str:
|
||||
"""调用 LLM 生成回复(OpenAI 兼容)。未配置则返回模板回复。"""
|
||||
def build_reply_with_llm(
|
||||
prompt: str, cfg: Dict[str, Any], matched_skill: str, skill_path: str
|
||||
) -> Tuple[str, Dict[str, int], bool]:
|
||||
"""
|
||||
调用 LLM 生成回复(OpenAI 兼容)。未配置则返回模板回复。
|
||||
返回:(reply, usage_tokens, usage_estimated)
|
||||
"""
|
||||
direct = _direct_reply_for_simple_prompt(prompt)
|
||||
if direct:
|
||||
return direct
|
||||
u = _estimate_usage_tokens(prompt, direct)
|
||||
return direct, u, True
|
||||
|
||||
system = (
|
||||
"【强制身份】你是「卡若AI」,卡若的私域运营与项目落地数字管家。"
|
||||
@@ -552,7 +618,10 @@ def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, s
|
||||
if _is_unusable_llm_reply(reply) or _looks_mismatched_reply(prompt, reply):
|
||||
errors.append(f"provider#{idx} unusable_reply={reply[:120]}")
|
||||
continue
|
||||
return reply
|
||||
parsed = _extract_usage_from_openai_json(data)
|
||||
if parsed:
|
||||
return reply, parsed, False
|
||||
return reply, _estimate_usage_tokens(prompt, reply), True
|
||||
errors.append(f"provider#{idx} status={r.status_code} body={r.text[:120]}")
|
||||
except Exception as e:
|
||||
errors.append(f"provider#{idx} exception={type(e).__name__}: {str(e)[:160]}")
|
||||
@@ -563,8 +632,10 @@ def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, s
|
||||
except Exception:
|
||||
# 告警失败不影响主流程,继续降级
|
||||
pass
|
||||
return _template_reply(prompt, matched_skill, skill_path, error=" | ".join(errors[:3]))
|
||||
return _template_reply(prompt, matched_skill, skill_path)
|
||||
err_reply = _template_reply(prompt, matched_skill, skill_path, error=" | ".join(errors[:3]))
|
||||
return err_reply, _estimate_usage_tokens(prompt, err_reply), True
|
||||
tpl = _template_reply(prompt, matched_skill, skill_path)
|
||||
return tpl, _estimate_usage_tokens(prompt, tpl), True
|
||||
|
||||
|
||||
class OpenAIChatCompletionsRequest(BaseModel):
|
||||
@@ -772,7 +843,9 @@ def _template_reply(prompt: str, matched_skill: str, skill_path: str, error: str
|
||||
)
|
||||
|
||||
|
||||
def _as_openai_stream(reply: str, model: str, created: int):
|
||||
def _as_openai_stream(
|
||||
reply: str, model: str, created: int, usage: Optional[Dict[str, int]] = None
|
||||
):
|
||||
"""
|
||||
OpenAI Chat Completions 流式(SSE)最小兼容实现。
|
||||
"""
|
||||
@@ -783,13 +856,19 @@ def _as_openai_stream(reply: str, model: str, created: int):
|
||||
"model": model,
|
||||
"choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
|
||||
}
|
||||
chunk1 = {
|
||||
chunk1: Dict[str, Any] = {
|
||||
"id": f"chatcmpl-{created}",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": created,
|
||||
"model": model,
|
||||
"choices": [{"index": 0, "delta": {"content": reply}, "finish_reason": "stop"}],
|
||||
}
|
||||
if usage:
|
||||
chunk1["usage"] = {
|
||||
"prompt_tokens": usage.get("prompt_tokens", 0),
|
||||
"completion_tokens": usage.get("completion_tokens", 0),
|
||||
"total_tokens": usage.get("total_tokens", 0),
|
||||
}
|
||||
yield f"data: {json.dumps(chunk0, ensure_ascii=False)}\n\n"
|
||||
yield f"data: {json.dumps(chunk1, ensure_ascii=False)}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
@@ -804,6 +883,7 @@ def index():
|
||||
<body>
|
||||
<h1>卡若AI 网关</h1>
|
||||
<p>外网可访问、按卡若AI 思考逻辑生成。其他 AI 可 POST /v1/chat 调用。</p>
|
||||
<p>科室用量:<code>GET /v1/usage</code>(需科室 Key);对话响应内带 <code>usage</code>。</p>
|
||||
<p>API 文档:<a href="/docs">/docs</a></p>
|
||||
</body></html>
|
||||
"""
|
||||
@@ -848,7 +928,10 @@ async def chat(req: ChatRequest, request: Request):
|
||||
if (skill_id not in allowed) and (skill_path not in allowed):
|
||||
raise HTTPException(status_code=403, detail="skill not allowed for tenant")
|
||||
|
||||
reply = build_reply_with_llm(req.prompt, cfg, matched_skill, skill_path)
|
||||
reply, usage_tokens, usage_estimated = build_reply_with_llm(
|
||||
req.prompt, cfg, matched_skill, skill_path
|
||||
)
|
||||
_record_gateway_usage(tenant_id, usage_tokens)
|
||||
|
||||
# 4) 访问日志(默认不落 prompt 内容)
|
||||
logging_cfg = (cfg or {}).get("logging") or {}
|
||||
@@ -861,6 +944,8 @@ async def chat(req: ChatRequest, request: Request):
|
||||
"skill_path": skill_path,
|
||||
"client": request.client.host if request.client else "",
|
||||
"ua": request.headers.get("user-agent", ""),
|
||||
"usage": usage_tokens,
|
||||
"usage_estimated": usage_estimated,
|
||||
}
|
||||
if bool(logging_cfg.get("log_request_body", False)):
|
||||
record["prompt"] = req.prompt
|
||||
@@ -873,6 +958,8 @@ async def chat(req: ChatRequest, request: Request):
|
||||
skill_id=skill_id,
|
||||
matched_skill=matched_skill,
|
||||
skill_path=skill_path,
|
||||
usage=usage_tokens,
|
||||
usage_estimated=usage_estimated,
|
||||
)
|
||||
|
||||
|
||||
@@ -941,7 +1028,10 @@ async def openai_chat_completions(req: OpenAIChatCompletionsRequest, request: Re
|
||||
cfg = dict(cfg or {})
|
||||
cfg["llm"] = llm_cfg
|
||||
|
||||
reply = build_reply_with_llm(prompt, cfg, matched_skill, skill_path)
|
||||
reply, usage_tokens, usage_estimated = build_reply_with_llm(
|
||||
prompt, cfg, matched_skill, skill_path
|
||||
)
|
||||
_record_gateway_usage(tenant_id, usage_tokens)
|
||||
|
||||
logging_cfg = (cfg or {}).get("logging") or {}
|
||||
record: Dict[str, Any] = {
|
||||
@@ -955,12 +1045,19 @@ async def openai_chat_completions(req: OpenAIChatCompletionsRequest, request: Re
|
||||
"ua": request.headers.get("user-agent", ""),
|
||||
"openai_compatible": True,
|
||||
"requested_model": req.model,
|
||||
"usage": usage_tokens,
|
||||
"usage_estimated": usage_estimated,
|
||||
}
|
||||
if bool(logging_cfg.get("log_request_body", False)):
|
||||
record["prompt"] = prompt
|
||||
_log_access(cfg, record)
|
||||
|
||||
now = int(time.time())
|
||||
usage_block = {
|
||||
"prompt_tokens": usage_tokens.get("prompt_tokens", 0),
|
||||
"completion_tokens": usage_tokens.get("completion_tokens", 0),
|
||||
"total_tokens": usage_tokens.get("total_tokens", 0),
|
||||
}
|
||||
payload = {
|
||||
"id": f"chatcmpl-{now}",
|
||||
"object": "chat.completion",
|
||||
@@ -973,11 +1070,16 @@ async def openai_chat_completions(req: OpenAIChatCompletionsRequest, request: Re
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": usage_block,
|
||||
}
|
||||
if usage_estimated:
|
||||
payload["karuo_usage_estimated"] = True
|
||||
if bool(req.stream):
|
||||
model_name = str(req.model or "karuo-ai")
|
||||
return StreamingResponse(
|
||||
_as_openai_stream(reply=reply, model=model_name, created=now),
|
||||
_as_openai_stream(
|
||||
reply=reply, model=model_name, created=now, usage=usage_block
|
||||
),
|
||||
media_type="text/event-stream",
|
||||
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
|
||||
)
|
||||
@@ -1038,6 +1140,52 @@ def health():
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/v1/usage")
|
||||
def usage_stats(request: Request):
|
||||
"""
|
||||
返回当前 API Key(科室/部门)在本进程内的累计 TOKEN 统计(重启清零)。
|
||||
鉴权方式同 /v1/chat/completions。
|
||||
"""
|
||||
cfg = load_config()
|
||||
if not cfg:
|
||||
st = _get_or_create_usage_stats()
|
||||
row = st["by_tenant"].get("_open") or {
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
"requests": 0,
|
||||
}
|
||||
return {
|
||||
"tenants_enabled": False,
|
||||
"tenant_id": "_open",
|
||||
"started_at": st.get("started_at"),
|
||||
"last_ts": st.get("last_ts"),
|
||||
"totals": row,
|
||||
"note": "未启用 gateway.yaml 多租户时,所有请求计入 _open",
|
||||
}
|
||||
api_key = _get_api_key_from_request(request, cfg)
|
||||
tenant = _tenant_by_key(cfg, api_key)
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=401, detail="invalid api key")
|
||||
tenant_id = str(tenant.get("id", "")).strip()
|
||||
st = _get_or_create_usage_stats()
|
||||
row = st["by_tenant"].get(tenant_id) or {
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
"requests": 0,
|
||||
}
|
||||
return {
|
||||
"tenants_enabled": True,
|
||||
"tenant_id": tenant_id,
|
||||
"tenant_name": str(tenant.get("name", "")).strip(),
|
||||
"started_at": st.get("started_at"),
|
||||
"last_ts": st.get("last_ts"),
|
||||
"totals": row,
|
||||
"per_response": "非流式 JSON 的 usage 字段;estimated 见 karuo_usage_estimated",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/v1/skills")
|
||||
def allowed_skills(request: Request):
|
||||
"""
|
||||
|
||||
103
运营中枢/scripts/send_douyin_appeal_mail.py
Normal file
103
运营中枢/scripts/send_douyin_appeal_mail.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""向抖音协议公示邮箱 feedback@douyin.com 发送账号/视频违规类申诉(SMTP 同卡若 gateway .env)。
|
||||
|
||||
用法:
|
||||
python3 send_douyin_appeal_mail.py Lkdie001
|
||||
|
||||
说明:侵权举报类邮箱(如 qinquan@bytedance.com)不用于此类申诉,见 Douyin账号申诉解封 SKILL。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from email.message import EmailMessage
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_ENV = Path(__file__).resolve().parent / "karuo_ai_gateway" / ".env"
|
||||
# 《抖音用户服务协议》1.5:feedback@douyin.com
|
||||
TO_LIST = ["feedback@douyin.com"]
|
||||
|
||||
|
||||
def load_env_file(path: Path) -> None:
|
||||
if not path.is_file():
|
||||
return
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, val = line.partition("=")
|
||||
key = key.strip()
|
||||
val = val.strip().strip('"').strip("'")
|
||||
if key:
|
||||
os.environ[key] = val
|
||||
|
||||
|
||||
def build_message(douyin_id: str) -> tuple[str, str]:
|
||||
subject = f"【账号申诉】抖音号 {douyin_id} 请求人工复核视频违规/功能限制"
|
||||
body = f"""抖音客服您好,
|
||||
|
||||
我是抖音创作者,抖音号(ID):{douyin_id},昵称与主页展示一致。
|
||||
|
||||
【账号情况】
|
||||
该账号长期发布知识类、创业与私域运营相关口播内容,属正常创作与分享,无恶意营销、色情低俗、造谣传谣等主观故意。目前主页可见作品约百余条,粉丝与互动为长期积累,非短期异常账号。
|
||||
|
||||
【遇到的问题】
|
||||
近期在发布/管理视频时,系统提示涉及「视频违规」或相关处罚,但提示信息较笼统,我未能明确对应到哪一条具体规则或哪一条视频、哪一个画面/话术触线,因此难以精准自查与整改。
|
||||
|
||||
【申诉请求】
|
||||
1. 恳请对抖音号 {douyin_id} 的处罚依据进行人工复核,并告知违规类型、对应规则要点;若涉及具体视频,请尽量给出可定位的信息(例如视频标题、处罚时间),便于我对照整改。
|
||||
2. 如属于可纠正情形,请视情况恢复相应功能或解除不当处罚;如需我删除/修改指定作品、补充说明或身份核验材料,我将按平台指引配合办理。
|
||||
3. 后续我将更严格对照《抖音社区自律公约》及平台公示规则进行创作与发布。
|
||||
|
||||
【联系方式】
|
||||
请回复本邮件发件地址。感谢审阅与处理。
|
||||
|
||||
账号持有人
|
||||
"""
|
||||
return subject, body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
douyin_id = (sys.argv[1] if len(sys.argv) > 1 else "").strip()
|
||||
if not douyin_id:
|
||||
print("用法: python3 send_douyin_appeal_mail.py <抖音号>", file=sys.stderr)
|
||||
print("示例: python3 send_douyin_appeal_mail.py Lkdie001", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
env_path = Path(os.environ.get("KARUO_SMTP_ENV", str(DEFAULT_ENV)))
|
||||
load_env_file(env_path)
|
||||
|
||||
user = os.environ.get("SMTP_USER", "").strip()
|
||||
password = os.environ.get("SMTP_PASS", "").strip()
|
||||
host = os.environ.get("SMTP_HOST", "smtp.qq.com").strip()
|
||||
port = int(os.environ.get("SMTP_PORT", "465") or "465")
|
||||
|
||||
if not user or not password:
|
||||
print("错误:未读取到 SMTP_USER / SMTP_PASS", env_path, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
subject, body = build_message(douyin_id)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = user
|
||||
msg["To"] = ", ".join(TO_LIST)
|
||||
msg.set_content(body)
|
||||
|
||||
try:
|
||||
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
|
||||
smtp.login(user, password)
|
||||
smtp.send_message(msg)
|
||||
except Exception as e:
|
||||
print("发送失败:", type(e).__name__, str(e), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print("已发送至", ", ".join(TO_LIST), "发件人", user)
|
||||
print("主题:", subject)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
137
运营中枢/scripts/send_soul_appeal_mail.py
Normal file
137
运营中枢/scripts/send_soul_appeal_mail.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从卡若 gateway .env 读 SMTP,向 Soul 官方各邮箱发申诉邮件。
|
||||
|
||||
收件人:soul@、hr@、ad@、commercial-b@、pc@(官网公示的 5 个邮箱)。
|
||||
用法:
|
||||
python3 send_soul_appeal_mail.py 15210897710 # 大白话 A 版
|
||||
python3 send_soul_appeal_mail.py 13779954946 # 大白话 B 版
|
||||
|
||||
不在终端打印密码。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from email.message import EmailMessage
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_ENV = Path(__file__).resolve().parent / "karuo_ai_gateway" / ".env"
|
||||
# Soul 官网公示的官方邮箱,用户要求账号申诉一并抄送
|
||||
TO_LIST = [
|
||||
"soul@soulapp.cn",
|
||||
"hr@soulapp.cn",
|
||||
"ad@soulapp.cn",
|
||||
"commercial-b@soulapp.cn",
|
||||
"pc@soulapp.cn",
|
||||
]
|
||||
|
||||
|
||||
def load_env_file(path: Path) -> None:
|
||||
if not path.is_file():
|
||||
return
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, val = line.partition("=")
|
||||
key = key.strip()
|
||||
val = val.strip().strip('"').strip("'")
|
||||
if key:
|
||||
os.environ[key] = val
|
||||
|
||||
|
||||
def appeal_variant_a(phone: str) -> tuple[str, str]:
|
||||
"""152 绑定号:分条 + 大白话。"""
|
||||
subject = f"求助|Soul账号被限制,求人工复核(手机{phone})"
|
||||
body = f"""Soul 客服您好,
|
||||
|
||||
我是咱们平台的用户,注册绑定的手机号是 {phone},这个号我一直自己在用。账号也做过实名认证,身份证信息是我本人,能对上。
|
||||
|
||||
现在我的号登录不了 / 被限制了,客户端也没跟我说明白到底是哪一条原因,我这边有点搞不清楚状况。我自己回想了一下,就是正常聊天、看广场,没有故意发乱七八糟的东西,也没有用外挂、脚本刷量那种操作。
|
||||
|
||||
所以想麻烦你们帮我做个人工复核:
|
||||
• 如果是误判或者可以解的那种,希望能恢复我正常使用;
|
||||
• 如果确实有问题,也请告诉我具体是啥、对应哪条规则,我以后好注意;需要我补材料(比如身份核验)也可以说,我按你们流程配合。
|
||||
|
||||
回信请发到我这封信的发件邮箱。需要电话联系的话,可以用 {phone}。
|
||||
|
||||
谢谢,辛苦了。
|
||||
|
||||
账号本人
|
||||
"""
|
||||
return subject, body
|
||||
|
||||
|
||||
def appeal_variant_b(phone: str) -> tuple[str, str]:
|
||||
"""137 绑定号:另一套大白话(叙述流,同目的)。"""
|
||||
subject = f"想申请恢复账号|绑定手机{phone}"
|
||||
body = f"""您好,
|
||||
|
||||
写邮件是想说说我的 Soul 账号。我绑定的手机号是 {phone},实名认证也是我自己,这个没问题。
|
||||
|
||||
最近这个号突然用不了了(提示限制或者封禁之类),但具体因为什么,App 里没写清楚,我没办法对症下药自查。我平时就是普通玩一玩、跟人正常聊天,不搞广告骚扰,也不搞作弊刷数据。
|
||||
|
||||
所以想请你们帮忙看看这个号还能不能恢复。需要我补充什么材料,你们回信说一声,我尽量配合。回复发到发件邮箱就行;电话也可以打 {phone}。
|
||||
|
||||
麻烦了,感谢。
|
||||
|
||||
用户敬上
|
||||
"""
|
||||
return subject, body
|
||||
|
||||
|
||||
def pick_variant(phone: str) -> tuple[str, str]:
|
||||
if phone == "15210897710":
|
||||
return appeal_variant_a(phone)
|
||||
if phone == "13779954946":
|
||||
return appeal_variant_b(phone)
|
||||
return appeal_variant_b(phone)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
phone = (sys.argv[1] if len(sys.argv) > 1 else "").strip()
|
||||
if not phone:
|
||||
print("用法: python3 send_soul_appeal_mail.py <11位手机号>", file=sys.stderr)
|
||||
print("示例: … 15210897710 或 … 13779954946", file=sys.stderr)
|
||||
return 1
|
||||
if not phone.isdigit() or len(phone) != 11:
|
||||
print("手机号须为 11 位数字", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
env_path = Path(os.environ.get("KARUO_SMTP_ENV", str(DEFAULT_ENV)))
|
||||
load_env_file(env_path)
|
||||
|
||||
user = os.environ.get("SMTP_USER", "").strip()
|
||||
password = os.environ.get("SMTP_PASS", "").strip()
|
||||
host = os.environ.get("SMTP_HOST", "smtp.qq.com").strip()
|
||||
port = int(os.environ.get("SMTP_PORT", "465") or "465")
|
||||
|
||||
if not user or not password:
|
||||
print("错误:未从环境或", env_path, "读取到 SMTP_USER / SMTP_PASS", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
subject, body = pick_variant(phone)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = user
|
||||
msg["To"] = ", ".join(TO_LIST)
|
||||
msg.set_content(body)
|
||||
|
||||
try:
|
||||
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
|
||||
smtp.login(user, password)
|
||||
smtp.send_message(msg)
|
||||
except Exception as e:
|
||||
print("发送失败:", type(e).__name__, str(e), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print("已发送至", len(TO_LIST), "个邮箱:", ", ".join(TO_LIST))
|
||||
print("主题:", subject)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
118
运营中枢/scripts/send_xiaohongshu_appeal_mail.py
Normal file
118
运营中枢/scripts/send_xiaohongshu_appeal_mail.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""向小红书官网公示的多邮箱发送账号申诉(SMTP 同卡若 gateway .env)。
|
||||
|
||||
默认收件人(合作邮箱页):service / community / app_feedback / shuduizhang
|
||||
用法:
|
||||
python3 send_xiaohongshu_appeal_mail.py 15880802661
|
||||
python3 send_xiaohongshu_appeal_mail.py 15880802661 --ceo # 额外抄送 ceo@(勿滥用)
|
||||
|
||||
详见:02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from email.message import EmailMessage
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_ENV = Path(__file__).resolve().parent / "karuo_ai_gateway" / ".env"
|
||||
# 来源:https://www.xiaohongshu.com/contact (以官网更新为准)
|
||||
TO_BASE = [
|
||||
"service@xiaohongshu.com",
|
||||
"community@xiaohongshu.com",
|
||||
"app_feedback@xiaohongshu.com",
|
||||
"shuduizhang@xiaohongshu.com",
|
||||
]
|
||||
CEO = "ceo@xiaohongshu.com"
|
||||
|
||||
|
||||
def load_env_file(path: Path) -> None:
|
||||
if not path.is_file():
|
||||
return
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, val = line.partition("=")
|
||||
key = key.strip()
|
||||
val = val.strip().strip('"').strip("'")
|
||||
if key:
|
||||
os.environ[key] = val
|
||||
|
||||
|
||||
def build_message(phone: str) -> tuple[str, str]:
|
||||
subject = f"【账号申诉】小红书绑定手机{phone} 请求人工复核账号限制/封禁"
|
||||
body = f"""小红书客服与社区团队您好,
|
||||
|
||||
我是平台用户,账号绑定的手机号码为:{phone}(可与站内注册信息核对)。
|
||||
|
||||
【情况说明】
|
||||
近期账号出现无法正常使用、限制或封禁等情况;站内提示有时比较笼统,我难以判断具体触犯了哪一条社区规范,也不便于针对性整改。本人使用小红书以正常浏览、发布生活与兴趣内容为主,无恶意营销、色情低俗、造谣传谣等主观故意。
|
||||
|
||||
【请求】
|
||||
1. 请对该账号(绑定手机 {phone})的处罚安排人工复核,并告知违规类型或对应规则要点;若涉及具体笔记,请尽量给出可定位的信息,便于我自查与修改。
|
||||
2. 如属可纠正情形,恳请解除限制或恢复账号;如需补充身份核验或其他材料,我将按指引配合。
|
||||
3. 后续我会更认真地遵守《小红书社区规范》及平台公示规则。
|
||||
|
||||
【联系方式】
|
||||
请回复本邮件发件地址;如需电话沟通可使用上述绑定号码(如能外呼)。
|
||||
|
||||
感谢处理。
|
||||
|
||||
用户敬上
|
||||
"""
|
||||
return subject, body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = [a for a in sys.argv[1:] if a != "--ceo"]
|
||||
with_ceo = "--ceo" in sys.argv[1:]
|
||||
phone = (args[0] if args else "").strip()
|
||||
if not phone:
|
||||
print("用法: python3 send_xiaohongshu_appeal_mail.py <11位手机> [--ceo]", file=sys.stderr)
|
||||
return 1
|
||||
if not phone.isdigit() or len(phone) != 11:
|
||||
print("手机号须为 11 位数字", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
to_list = list(TO_BASE)
|
||||
if with_ceo and CEO not in to_list:
|
||||
to_list.append(CEO)
|
||||
|
||||
env_path = Path(os.environ.get("KARUO_SMTP_ENV", str(DEFAULT_ENV)))
|
||||
load_env_file(env_path)
|
||||
|
||||
user = os.environ.get("SMTP_USER", "").strip()
|
||||
password = os.environ.get("SMTP_PASS", "").strip()
|
||||
host = os.environ.get("SMTP_HOST", "smtp.qq.com").strip()
|
||||
port = int(os.environ.get("SMTP_PORT", "465") or "465")
|
||||
|
||||
if not user or not password:
|
||||
print("错误:未读取到 SMTP_USER / SMTP_PASS", env_path, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
subject, body = build_message(phone)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = user
|
||||
msg["To"] = ", ".join(to_list)
|
||||
msg.set_content(body)
|
||||
|
||||
try:
|
||||
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
|
||||
smtp.login(user, password)
|
||||
smtp.send_message(msg)
|
||||
except Exception as e:
|
||||
print("发送失败:", type(e).__name__, str(e), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print("已发送至", len(to_list), "个邮箱:", ", ".join(to_list))
|
||||
print("主题:", subject)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -406,3 +406,4 @@
|
||||
| 2026-03-20 16:08:50 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 21:38:30 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 21:45:45 | 🔄 卡若AI 同步 2026-03-20 21:45 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 23:22:48 | 🔄 卡若AI 同步 2026-03-20 23:22 | 更新:Cursor规则、金仓、卡木、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
|
||||
@@ -409,3 +409,4 @@
|
||||
| 2026-03-20 16:08:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 21:38:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 21:45:45 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 21:45 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 23:22:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 23:22 | 更新:Cursor规则、金仓、卡木、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
Reference in New Issue
Block a user