ilang-ai commited on
Commit
ecdeed3
·
1 Parent(s): 7c1cf27

v3.0: international edition — universal prompts, English UI, multilingual bot

Browse files

- Persona: universal personality, auto-detect language, boundary handler (split don't refuse)
- Antispam: language-agnostic three-step analysis
- Vision: universal three-step image reading
- All UI strings: English (bot auto-detects user language for replies)
- GENE/IMMUNE framework showcased in every .ilang file
- Safety filters: BLOCK_NONE for all Gemini categories

bot.py CHANGED
@@ -31,15 +31,15 @@ logging.basicConfig(
31
  logger = logging.getLogger(__name__)
32
 
33
  TOS_TEXT = (
34
- "I-Lang Guard 服务条款\n\n"
35
- "为保障群组安全, 本Bot提供以下服务:\n"
36
- "- 自动识别并清理垃圾广告\n"
37
- "- 通过消息分析持续优化AI反spam能力\n\n"
38
- "使用说明:\n"
39
- "- Bot会分析群内消息用于反垃圾和AI模型优化\n"
40
- "- 不存储个人身份信息\n"
41
- "- 管理员可随时移除Bot终止服务\n\n"
42
- "群管理员点击下方按钮即表示同意以上条款"
43
  )
44
 
45
 
@@ -69,7 +69,7 @@ def _ctx_info(context):
69
  parts = []
70
  history = context.user_data.get("history", [])
71
  if not history:
72
- parts.append("NEW_SESSION:新对话开始,主动问好,问用户今天需要什么帮助")
73
  return " | ".join(parts)
74
 
75
 
@@ -91,7 +91,7 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
91
  intent, device, reply = await ai_text(
92
  "/start",
93
  history=None,
94
- context_info="NEW_SESSION:用户刚打开对话,简短问好,介绍你能做什么"
95
  )
96
  await update.message.reply_text(reply)
97
  context.user_data.setdefault("history", []).append({"role": "assistant", "text": reply})
@@ -100,8 +100,8 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
100
  await register_group(chat_id, update.effective_chat.title)
101
  if not await check_tos(chat_id):
102
  keyboard = InlineKeyboardMarkup([
103
- [InlineKeyboardButton("同意并启用", callback_data="tos_accept_" + str(chat_id))],
104
- [InlineKeyboardButton("不同意", callback_data="tos_decline_" + str(chat_id))]
105
  ])
106
  await update.message.reply_text(TOS_TEXT, reply_markup=keyboard)
107
  else:
@@ -111,13 +111,13 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
111
  async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
112
  if update.effective_chat.type == "private":
113
  await update.message.reply_text(
114
- "直接跟我说话就行, 不用命令\n\n"
115
- "群管理拉我进群, 给管理员权限\n"
116
- "其他随便聊"
117
  )
118
  else:
119
  await update.message.reply_text(
120
- "我在群里自动工作, 不用配置\n\n管理员命令:\n/ban — 回复消息踢人"
121
  )
122
 
123
 
@@ -157,7 +157,7 @@ async def handle_group_message(update: Update, context: ContextTypes.DEFAULT_TYP
157
  if (text or has_media) and (is_mention or is_reply_to_bot):
158
  clean = text.replace("@" + context.bot.username, "").strip() if (text and is_mention) else (text.strip() if text else "")
159
  if not tos_ok:
160
- reply = "我还没被启用哦, 请管理员点一下上面的「同意并启用」按钮"
161
  else:
162
  g_history = context.chat_data.setdefault("group_history", [])
163
  if msg.photo:
@@ -166,7 +166,7 @@ async def handle_group_message(update: Update, context: ContextTypes.DEFAULT_TYP
166
  img_data = bytes(await f.download_as_bytearray())
167
  reply = await ai_group_vision(img_data, caption=clean, history=g_history)
168
  except Exception:
169
- reply = "图片没看清, 再发一张?"
170
  elif msg.video:
171
  if msg.video.thumbnail:
172
  try:
@@ -175,15 +175,15 @@ async def handle_group_message(update: Update, context: ContextTypes.DEFAULT_TYP
175
  reply = await ai_group_vision(vimg, caption=clean, history=g_history)
176
  except Exception:
177
  if clean:
178
- g_history.append({"role": "user", "text": "[视频] " + clean})
179
- reply = await ai_group_reply("[视频] " + clean, g_history)
180
  else:
181
- reply = "视频封面没看清, 说说是什么内容?"
182
  elif clean:
183
- g_history.append({"role": "user", "text": "[视频] " + clean})
184
- reply = await ai_group_reply("[视频] " + clean, g_history)
185
  else:
186
- reply = "视频我看不了, 说说是什么内容?"
187
  else:
188
  g_history.append({"role": "user", "text": clean})
189
  reply = await ai_group_reply(clean, g_history)
@@ -279,8 +279,8 @@ async def handle_group_message(update: Update, context: ContextTypes.DEFAULT_TYP
279
  context.bot_data["perm_remind_" + str(chat_id)] = _time.time()
280
  try:
281
  await msg.reply_text(
282
- "\u26a0\ufe0f 发现垃圾消息但我没权限处理\n\n"
283
- "点群名字管理员添加管理员找到我打开「删除消息」和「封禁用户」完成"
284
  )
285
  except Exception:
286
  pass
@@ -312,7 +312,7 @@ async def handle_private_photo(update: Update, context: ContextTypes.DEFAULT_TYP
312
  file = await context.bot.get_file(msg.photo[-1].file_id)
313
  img_bytes = bytes(await file.download_as_bytearray())
314
  except Exception:
315
- await msg.reply_text("图片没收到, 再发一次?")
316
  return
317
  intent, device, reply = await ai_vision(img_bytes, caption, history, _ctx_info(context))
318
  await _handle_ai_result(intent, device, reply, msg, user_id, context)
@@ -330,7 +330,7 @@ async def handle_private_voice(update: Update, context: ContextTypes.DEFAULT_TYP
330
  audio_bytes = bytes(await file.download_as_bytearray())
331
  mime = msg.voice.mime_type or "audio/ogg"
332
  except Exception:
333
- await msg.reply_text("语音没收到, 再说一次或者打字也行")
334
  return
335
  intent, device, reply = await ai_voice(audio_bytes, mime, history, _ctx_info(context))
336
  await _handle_ai_result(intent, device, reply, msg, user_id, context)
@@ -350,8 +350,8 @@ async def handle_my_chat_member(update: Update, context: ContextTypes.DEFAULT_TY
350
  await delete_tos(chat_id)
351
  await register_group(chat_id, result.chat.title)
352
  keyboard = InlineKeyboardMarkup([
353
- [InlineKeyboardButton("同意并启用", callback_data="tos_accept_" + str(chat_id))],
354
- [InlineKeyboardButton("不同意", callback_data="tos_decline_" + str(chat_id))]
355
  ])
356
  await context.bot.send_message(chat_id, TOS_TEXT, reply_markup=keyboard)
357
  elif old in ("member", "administrator") and new in ("left", "kicked"):
@@ -375,14 +375,14 @@ async def handle_tos_callback(update: Update, context: ContextTypes.DEFAULT_TYPE
375
  admins = await context.bot.get_chat_administrators(chat_id)
376
  admin_ids = [a.user.id for a in admins]
377
  if user_id not in admin_ids:
378
- await query.answer("只有管理员可以操作", show_alert=True)
379
  return
380
  except Exception:
381
  pass
382
 
383
  if is_accept:
384
  await record_tos(chat_id, user_id)
385
- await query.answer("已启用")
386
  has_perms = False
387
  try:
388
  bot_member = await context.bot.get_chat_member(chat_id, context.bot.id)
@@ -392,21 +392,21 @@ async def handle_tos_callback(update: Update, context: ContextTypes.DEFAULT_TYPE
392
  pass
393
 
394
  if has_perms:
395
- await query.edit_message_text("I-Lang Guard 已启用 ✅\n\n垃圾广告我来清理, 全自动")
396
  else:
397
  await query.edit_message_text(
398
- "I-Lang Guard 已启用\n\n"
399
- "⚠️ 我还需要管理员权限才能干活:\n\n"
400
- "1. 点群名字进入群资料\n"
401
- "2. 点「管理员」\n"
402
- "3. 点「添加管理员」\n"
403
- "4. 找到 I-Lang Guard 点它\n"
404
- "5. 打开「删除消息」和「封禁用户」\n"
405
- "6. 点右上角「完成」"
406
  )
407
  else:
408
- await query.answer("好的, 再见")
409
- await query.edit_message_text("已退出群组")
410
  await context.bot.leave_chat(chat_id)
411
 
412
 
 
31
  logger = logging.getLogger(__name__)
32
 
33
  TOS_TEXT = (
34
+ "I-Lang Guard Terms of Service\n\n"
35
+ "This bot provides:\n"
36
+ "- Auto spam detection and cleanup\n"
37
+ "- AI-powered message analysis\n\n"
38
+ "Usage:\n"
39
+ "- Bot analyzes group messages for spam detection\n"
40
+ "- No personal data is stored\n"
41
+ "- Admins can remove the bot at any time\n\n"
42
+ "Group admins: tap the button below to accept"
43
  )
44
 
45
 
 
69
  parts = []
70
  history = context.user_data.get("history", [])
71
  if not history:
72
+ parts.append("NEW_SESSION:New conversation, greet casually, ask what they need")
73
  return " | ".join(parts)
74
 
75
 
 
91
  intent, device, reply = await ai_text(
92
  "/start",
93
  history=None,
94
+ context_info="NEW_SESSION:User just opened chat, greet briefly, say what you can do"
95
  )
96
  await update.message.reply_text(reply)
97
  context.user_data.setdefault("history", []).append({"role": "assistant", "text": reply})
 
100
  await register_group(chat_id, update.effective_chat.title)
101
  if not await check_tos(chat_id):
102
  keyboard = InlineKeyboardMarkup([
103
+ [InlineKeyboardButton("Accept & Enable", callback_data="tos_accept_" + str(chat_id))],
104
+ [InlineKeyboardButton("Decline", callback_data="tos_decline_" + str(chat_id))]
105
  ])
106
  await update.message.reply_text(TOS_TEXT, reply_markup=keyboard)
107
  else:
 
111
  async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
112
  if update.effective_chat.type == "private":
113
  await update.message.reply_text(
114
+ "Just talk to me, no commands needed\n\n"
115
+ "Group admin Add me to a group, give me admin permissions\n"
116
+ "Anything else Just chat"
117
  )
118
  else:
119
  await update.message.reply_text(
120
+ "I work automatically in groups. No config needed.\n\nAdmin commands:\n/ban — Reply to a message to ban the user"
121
  )
122
 
123
 
 
157
  if (text or has_media) and (is_mention or is_reply_to_bot):
158
  clean = text.replace("@" + context.bot.username, "").strip() if (text and is_mention) else (text.strip() if text else "")
159
  if not tos_ok:
160
+ reply = "I haven't been enabled yet. Ask an admin to tap the Accept & Enable button above."
161
  else:
162
  g_history = context.chat_data.setdefault("group_history", [])
163
  if msg.photo:
 
166
  img_data = bytes(await f.download_as_bytearray())
167
  reply = await ai_group_vision(img_data, caption=clean, history=g_history)
168
  except Exception:
169
+ reply = "Couldn't read that image. Try sending another one?"
170
  elif msg.video:
171
  if msg.video.thumbnail:
172
  try:
 
175
  reply = await ai_group_vision(vimg, caption=clean, history=g_history)
176
  except Exception:
177
  if clean:
178
+ g_history.append({"role": "user", "text": "[video] " + clean})
179
+ reply = await ai_group_reply("[video] " + clean, g_history)
180
  else:
181
+ reply = "Couldn't read the video thumbnail. What's it about?"
182
  elif clean:
183
+ g_history.append({"role": "user", "text": "[video] " + clean})
184
+ reply = await ai_group_reply("[video] " + clean, g_history)
185
  else:
186
+ reply = "Can't process videos directly. What's it about?"
187
  else:
188
  g_history.append({"role": "user", "text": clean})
189
  reply = await ai_group_reply(clean, g_history)
 
279
  context.bot_data["perm_remind_" + str(chat_id)] = _time.time()
280
  try:
281
  await msg.reply_text(
282
+ "\u26a0\ufe0f Detected spam but I don't have permissions to act.\n\n"
283
+ "Tap group name AdminsAdd Admin Find me Enable Delete Messages and Ban Users Done"
284
  )
285
  except Exception:
286
  pass
 
312
  file = await context.bot.get_file(msg.photo[-1].file_id)
313
  img_bytes = bytes(await file.download_as_bytearray())
314
  except Exception:
315
+ await msg.reply_text("Didn't receive that image. Try again?")
316
  return
317
  intent, device, reply = await ai_vision(img_bytes, caption, history, _ctx_info(context))
318
  await _handle_ai_result(intent, device, reply, msg, user_id, context)
 
330
  audio_bytes = bytes(await file.download_as_bytearray())
331
  mime = msg.voice.mime_type or "audio/ogg"
332
  except Exception:
333
+ await msg.reply_text("Didn't catch that voice message. Try again or just type it out.")
334
  return
335
  intent, device, reply = await ai_voice(audio_bytes, mime, history, _ctx_info(context))
336
  await _handle_ai_result(intent, device, reply, msg, user_id, context)
 
350
  await delete_tos(chat_id)
351
  await register_group(chat_id, result.chat.title)
352
  keyboard = InlineKeyboardMarkup([
353
+ [InlineKeyboardButton("Accept & Enable", callback_data="tos_accept_" + str(chat_id))],
354
+ [InlineKeyboardButton("Decline", callback_data="tos_decline_" + str(chat_id))]
355
  ])
356
  await context.bot.send_message(chat_id, TOS_TEXT, reply_markup=keyboard)
357
  elif old in ("member", "administrator") and new in ("left", "kicked"):
 
375
  admins = await context.bot.get_chat_administrators(chat_id)
376
  admin_ids = [a.user.id for a in admins]
377
  if user_id not in admin_ids:
378
+ await query.answer("Only admins can do this", show_alert=True)
379
  return
380
  except Exception:
381
  pass
382
 
383
  if is_accept:
384
  await record_tos(chat_id, user_id)
385
+ await query.answer("Enabled")
386
  has_perms = False
387
  try:
388
  bot_member = await context.bot.get_chat_member(chat_id, context.bot.id)
 
392
  pass
393
 
394
  if has_perms:
395
+ await query.edit_message_text("I-Lang Guard enabled ✅\n\nSpam cleanup is now automatic.")
396
  else:
397
  await query.edit_message_text(
398
+ "I-Lang Guard enabled\n\n"
399
+ "⚠️ I need admin permissions to work:\n\n"
400
+ "1. Tap the group name\n"
401
+ "2. Tap Administrators\n"
402
+ "3. Tap Add Admin\n"
403
+ "4. Find I-Lang Guard\n"
404
+ "5. Enable Delete Messages and Ban Users\n"
405
+ "6. Tap Done"
406
  )
407
  else:
408
+ await query.answer("OK, goodbye")
409
+ await query.edit_message_text("Left the group")
410
  await context.bot.leave_chat(chat_id)
411
 
412
 
modules/chat.py CHANGED
@@ -31,10 +31,10 @@ ANTISPAM_TEXT_PROMPT = _load_prompt("antispam.ilang")
31
  VISION_PROMPT = _load_prompt("vision.ilang")
32
 
33
  GROUP_WELCOME = (
34
- "I-Lang Guard 来了\n\n"
35
- "垃圾广告我来清理, 全自动, 不用配置\n"
36
- "有问题可以 @\n\n"
37
- "给我管理员权限(删消息+封人)就行, 其他不用管"
38
  )
39
 
40
  model = genai.GenerativeModel(
@@ -92,9 +92,9 @@ def _ctx(history, info):
92
 
93
  def _deflect():
94
  lines = [
95
- "这个话题不太方便聊, 换一个吧",
96
- "换个话题? 你今天有什么需要帮忙的?",
97
- "这个超纲了, 聊点别的吧",
98
  ]
99
  return random.choice(lines)
100
 
@@ -126,7 +126,7 @@ async def ai_vision(image_bytes, caption="", history=None, context_info=""):
126
  return _parse(r.text if r.text else "")
127
  except Exception as e:
128
  logger.warning("AI vision: " + str(e))
129
- return ("chat", None, "图片没看清, 再发一张?")
130
 
131
 
132
  async def ai_voice(audio_bytes, mime_type="audio/ogg", history=None, context_info=""):
@@ -137,12 +137,12 @@ async def ai_voice(audio_bytes, mime_type="audio/ogg", history=None, context_inf
137
  return _parse(r.text if r.text else "")
138
  except Exception as e:
139
  logger.warning("AI voice: " + str(e))
140
- return ("chat", None, "语音没听清, 再说一次或者打字都行")
141
 
142
 
143
  async def ai_judge_group_message(text):
144
  try:
145
- prompt = ANTISPAM_TEXT_PROMPT + "\n\n消息内容: " + text[:1000]
146
  r = await vision_model.generate_content_async(prompt)
147
  result = r.text.strip().lower() if r.text else "ok"
148
  return "spam" in result
@@ -152,7 +152,7 @@ async def ai_judge_group_message(text):
152
 
153
  async def ai_judge_group_image(image_bytes, caption=""):
154
  try:
155
- prompt = ANTISPAM_TEXT_PROMPT + "\n\n判断这张图片是否是spam。只回复 spam ok"
156
  if caption:
157
  prompt += "\nCaption: " + caption[:500]
158
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
@@ -164,12 +164,12 @@ async def ai_judge_group_image(image_bytes, caption=""):
164
 
165
  async def ai_group_vision(image_bytes, caption="", history=None):
166
  try:
167
- ctx = _ctx(history, "GROUP_CHAT: 用户在群里发了张图片@你, 简短评论1-2句话")
168
  prompt = SYSTEM_PROMPT + "\n" + ctx
169
  if caption:
170
  prompt += "\nuser: " + caption
171
  else:
172
- prompt += "\nuser: [发了张图片]"
173
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
174
  raw = r.text.strip() if r.text else ""
175
  if not raw:
@@ -184,7 +184,7 @@ async def ai_group_vision(image_bytes, caption="", history=None):
184
 
185
  async def ai_group_reply(text, history=None):
186
  try:
187
- ctx = _ctx(history, "GROUP_CHAT: 你在群里被@了, 直接回答, 简短2句话")
188
  prompt = ctx + "\nuser: " + text
189
  r = await model.generate_content_async(prompt)
190
  raw = r.text.strip() if r.text else ""
 
31
  VISION_PROMPT = _load_prompt("vision.ilang")
32
 
33
  GROUP_WELCOME = (
34
+ "I-Lang Guard is here\n\n"
35
+ "I auto-clean spam. No config needed.\n"
36
+ "@ me if you need anything.\n\n"
37
+ "Just give me admin permissions (delete messages + ban users)."
38
  )
39
 
40
  model = genai.GenerativeModel(
 
92
 
93
  def _deflect():
94
  lines = [
95
+ "That's a tough one. What else can I help with?",
96
+ "Let's try a different angle. What do you need today?",
97
+ "That's beyond my range. What else is on your mind?",
98
  ]
99
  return random.choice(lines)
100
 
 
126
  return _parse(r.text if r.text else "")
127
  except Exception as e:
128
  logger.warning("AI vision: " + str(e))
129
+ return ("chat", None, "Couldn't read that image. Try another one?")
130
 
131
 
132
  async def ai_voice(audio_bytes, mime_type="audio/ogg", history=None, context_info=""):
 
137
  return _parse(r.text if r.text else "")
138
  except Exception as e:
139
  logger.warning("AI voice: " + str(e))
140
+ return ("chat", None, "Didn't catch that. Try again or type it out.")
141
 
142
 
143
  async def ai_judge_group_message(text):
144
  try:
145
+ prompt = ANTISPAM_TEXT_PROMPT + "\n\nMessage content: " + text[:1000]
146
  r = await vision_model.generate_content_async(prompt)
147
  result = r.text.strip().lower() if r.text else "ok"
148
  return "spam" in result
 
152
 
153
  async def ai_judge_group_image(image_bytes, caption=""):
154
  try:
155
+ prompt = ANTISPAM_TEXT_PROMPT + "\n\nJudge this image. Reply ONLY: spam or ok."
156
  if caption:
157
  prompt += "\nCaption: " + caption[:500]
158
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
 
164
 
165
  async def ai_group_vision(image_bytes, caption="", history=None):
166
  try:
167
+ ctx = _ctx(history, "GROUP_CHAT: User shared an image and @mentioned you. Comment naturally in 1-2 sentences.")
168
  prompt = SYSTEM_PROMPT + "\n" + ctx
169
  if caption:
170
  prompt += "\nuser: " + caption
171
  else:
172
+ prompt += "\nuser: [shared an image]"
173
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
174
  raw = r.text.strip() if r.text else ""
175
  if not raw:
 
184
 
185
  async def ai_group_reply(text, history=None):
186
  try:
187
+ ctx = _ctx(history, "GROUP_CHAT: You were @mentioned in a group. Reply directly, 1-2 sentences.")
188
  prompt = ctx + "\nuser: " + text
189
  r = await model.generate_content_async(prompt)
190
  raw = r.text.strip() if r.text else ""
prompts_demo/antispam.ilang CHANGED
@@ -1,6 +1,6 @@
1
- # ::ILANG:GENE:ANTISPAM::v2.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
- # ::PURPOSE{TelegramGuard spam detection rules}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
5
 
6
  # ============================================================
@@ -8,70 +8,69 @@
8
  # ============================================================
9
 
10
  ::GENE_IMMUTABLE{001, T:THREE_STEP_JUDGE, A:SHALLOW_SCAN⇒FALSE_NEGATIVE, G:ALL, Θ:ALWAYS}
11
- # 三步法: 表面看深层想判断
12
 
13
- ::GENE_IMMUTABLE{002, T:JUDGE_PERSON_NOT_MSG, A:JUDGE_SINGLE_MSG⇒MISS_PATTERN, G:ALL, Θ:ALWAYS}
14
- # 判人不判条: 一条spam=整个人有问题
15
 
16
- ::GENE_IMMUTABLE{003, T:SEE_THROUGH_TRICKS, A:LITERAL_READ⇒EVASION, G:ALL, Θ:ALWAYS}
17
- # 看穿伪装: Unicode变体/emoji夹杂/竖排/谐音全能识别
18
 
19
  # ============================================================
20
- # IMMUNE — Anti-evasion detection
21
  # ============================================================
22
 
23
  ::IMMUNE{UNICODE_VARIANT, DETECT}
24
- # 全角字符、西里尔字母、特殊空格替换
25
 
26
  ::IMMUNE{EMOJI_STUFFING, DETECT}
27
- # emoji夹杂文字: 🔥限时🔥优惠🔥
28
 
29
- ::IMMUNE{VERTICAL_SPLIT, DETECT}
30
- # 竖排/分行拆字
31
 
32
- ::IMMUNE{HOMOPHONE_REPLACE, DETECT}
33
- # 谐音替代: "维信"="微信", "jia"="加"
34
 
35
- ::IMMUNE{PINYIN_ABBREVIATION, DETECT}
36
- # 拼音/首字母缩写
37
 
38
  # ============================================================
39
  # TEXT SPAM JUDGE
40
  # ============================================================
41
 
42
- 你是Telegram群反spam专家。用三步法判断这条消息:
43
 
44
- 第一步: 表面看, 这条消息在说什么?
45
- 第二步: 往深想, 这种内容出现在Telegram群里, 动机是什么? 谁会发这种消息? 正常群友会这样说话吗?
46
- 第三步: 判断, 结合以下规律:
47
- - 低于市场价卖全新商品+带发票+联系方式 = 黑产销赃引流
48
- - 转发自其他频道/群+带联系方式或频道链接 = 广告引流
49
- - 提到赚钱/日入/兼职/代理+联系方式 = 诈骗引流
50
- - 加密货币/区块链/空投+链接 = crypto scam
51
- - 色情内容/约炮/上门 = 色情spam
52
- - 赌博/彩票/下注 = 赌博spam
53
- - 政治敏感/涉及领导人/政党/政治运动 = 违规
54
- - 正常聊天/提问/吐槽/求助/表情/回复别人 = 正常
55
- - 关键: 很多spam故意伪装成正常内容, 型号名可能是暗语, "几折""特价"可能是销赃
56
- - 关键: 正常群友极少在群里转发商品信息, 更不会带联系方式
57
 
58
- 只回复 spam ok, 不要解释。
59
 
60
  # ============================================================
61
  # IMAGE SPAM JUDGE
62
  # ============================================================
63
 
64
- 图片判断规则:
65
- 第一步: 图里有什么, 商品、二维码、截图、聊天记录、美女、文字?
66
- 第二步: 这种图出现在Telegram群里, 目的是什么? 谁会发?
67
- 第三步: 判断:
68
- - 商品展示+价格 = 销赃引流
69
- - 二维码 = 引流
70
- - 色情 = spam
71
- - 赌博界面 = spam
72
- - 政治宣传 = spam
73
- - 频道/群推广图 = spam
74
- - 包含微信号/QQ号/手机号 = spam
75
- - 正常聊天截图/表情包/日常照片 = ok
76
-
77
- 只回复 spam ok, 不要解释。
 
1
+ # ::ILANG:GENE:ANTISPAM::v3.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
+ # ::PURPOSE{TelegramGuard spam detection — universal}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
5
 
6
  # ============================================================
 
8
  # ============================================================
9
 
10
  ::GENE_IMMUTABLE{001, T:THREE_STEP_JUDGE, A:SHALLOW_SCAN⇒FALSE_NEGATIVE, G:ALL, Θ:ALWAYS}
11
+ # Three-step analysis: surface intent verdict
12
 
13
+ ::GENE_IMMUTABLE{002, T:JUDGE_PERSON_NOT_MESSAGE, A:SINGLE_MSG_ONLY⇒MISS_PATTERN, G:ALL, Θ:ALWAYS}
14
+ # One spam message = the entire account is spam. Not "one bad message, rest OK."
15
 
16
+ ::GENE_IMMUTABLE{003, T:SEE_THROUGH_EVASION, A:LITERAL_READ⇒BYPASS, G:ALL, Θ:ALWAYS}
17
+ # Detect all evasion techniques regardless of language
18
 
19
  # ============================================================
20
+ # IMMUNE — Anti-evasion across all languages
21
  # ============================================================
22
 
23
  ::IMMUNE{UNICODE_VARIANT, DETECT}
24
+ # Fullwidth chars, Cyrillic lookalikes, special spaces
25
 
26
  ::IMMUNE{EMOJI_STUFFING, DETECT}
27
+ # Text broken up by emojis to avoid keyword filters
28
 
29
+ ::IMMUNE{CHAR_SPLITTING, DETECT}
30
+ # Vertical text, line-broken words, spaced-out characters
31
 
32
+ ::IMMUNE{HOMOPHONE_SUBSTITUTE, DETECT}
33
+ # Sound-alike replacements in any language
34
 
35
+ ::IMMUNE{TRANSLITERATION, DETECT}
36
+ # Pinyin, romanization, phonetic spelling to dodge filters
37
 
38
  # ============================================================
39
  # TEXT SPAM JUDGE
40
  # ============================================================
41
 
42
+ You are a Telegram group anti-spam expert. Use three-step analysis:
43
 
44
+ Step 1 — SURFACE: What does this message literally say?
45
+ Step 2 — INTENT: What is the real purpose? Who posts messages like this in a Telegram group? Would a normal member talk this way?
46
+ Step 3 — VERDICT based on these patterns:
47
+ - Below-market goods + contact info = black market / stolen goods
48
+ - Forwarded from channel + link/contact = ad spam
49
+ - Income/earning/part-time + contact = scam
50
+ - Crypto/airdrop/token + link = crypto scam
51
+ - Sexual services / hookup = sexual spam
52
+ - Gambling / betting / lottery = gambling spam
53
+ - Normal conversation / question / complaint / emoji / reply = OK
54
+ - Key insight: spam often disguises as normal content. Product codes may be hidden contact info. "Discount" may mean stolen goods.
55
+ - Key insight: normal members rarely forward product info with contact details
 
56
 
57
+ Reply ONLY: spam or ok. No explanation.
58
 
59
  # ============================================================
60
  # IMAGE SPAM JUDGE
61
  # ============================================================
62
 
63
+ For images, three-step analysis:
64
+ Step 1 — CONTENT: What objects are in this image?
65
+ Step 2 — INTENT: Why would someone post this image in a Telegram group?
66
+ Step 3 — VERDICT:
67
+ - Product display + price = sales spam
68
+ - QR code = redirect spam
69
+ - Sexual/explicit content = spam
70
+ - Gambling interface = spam
71
+ - Political propaganda = spam
72
+ - Channel/group promo = spam
73
+ - Contact info (phone/messaging ID) = spam
74
+ - Normal screenshot / meme / daily photo = ok
75
+
76
+ Reply ONLY: spam or ok. No explanation.
prompts_demo/persona.ilang CHANGED
@@ -1,143 +1,118 @@
1
- # ::ILANG:GENE:PERSONA::v2.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
- # ::PURPOSE{TelegramGuard full personality}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
 
 
 
 
 
 
 
5
 
6
  # ============================================================
7
- # GENE FRAMEWORK — Immutable genes cannot be overridden
8
  # ============================================================
9
 
10
- ::GENE_IMMUTABLE{001, T:CONCISE, A:VERBOSETRIM, G:ALL, Θ:ALWAYS}
11
- # 2-3句话解决问题, 像发微信不像写作文
12
 
13
- ::GENE_IMMUTABLE{002, T:NO_POLITICS, A:POLITICAL_TOPICDEFLECT, G:ALL, Θ:ALWAYS}
14
- # 不聊政治: "哥们我不聊政治, 换个话题"
 
 
 
 
15
 
16
- ::GENE_IMMUTABLE{003, T:NO_PAID_PROMO, A:RECOMMEND_PAIDBLOCK, G:ALL, Θ:ALWAYS}
17
- # 不推荐付费服务
18
 
19
- ::GENE_IMMUTABLE{004, T:NO_FAKE_LINKS, A:FABRICATE_URLBLOCK, G:ALL, Θ:ALWAYS}
20
- # 不编造链接
21
 
22
- ::GENE_IMMUTABLE{005, T:CHINESE_FIRST, A:WRONG_LANGSWITCH, G:ALL, Θ:DETECT_LANG}
23
- # 中文回复为主, 用户说英文你说英文
 
24
 
25
  # ============================================================
26
- # GENE FRAMEWORK — Mutable genes adapt to context
27
  # ============================================================
28
 
29
- ::GENE_MUTABLE{101, T:YAPIN_PERSONA, G:ALL, Θ:BASE}
30
- # 雅痞人格: 穿西装但骨子里是流氓, 说话有分寸但不装
31
 
32
- ::GENE_MUTABLE{102, T:STREET_SMART, G:ALL, Θ:ALWAYS}
33
- # 听得懂俚语黑话, 不装纯不装傻
34
 
35
- ::GENE_MUTABLE{103, T:TONE_MIRROR, G:adaptive, Θ:INTERLOCUTOR}
36
- # 用户认真你就认真, 用户搞笑你就配合
 
37
 
38
- ::GENE_MUTABLE{104, T:REAL_TALK, A:EMPTY_ADVICE⇒BLOCK, G:ALL, Θ:ALWAYS}
39
- # 不说正确的废话, 给具体能执行的建议
40
 
41
- ::GENE_MUTABLE{105, T:HONEST, A:BULLSHIT⇒BLOCK, G:ALL, Θ:ALWAYS}
42
- # 不知道就说不知道, 确定的事情敢下判断
43
 
44
  # ============================================================
45
- # IMMUNE — Auto-responses to boundary triggers
46
  # ============================================================
47
 
48
- ::IMMUNE{POLITICS, DEFLECT:"哥们我不聊政治, 换个话题"}
49
- ::IMMUNE{OVERLY_SENSITIVE, DEFLECT:"这个话题太猛了, 聊点别的? 搞钱还是搞心情?"}
50
- ::IMMUNE{ILLEGAL_REQUEST, DEFLECT:"打住打住, 这个超纲了"}
 
 
 
 
 
 
51
 
52
  # ============================================================
53
  # PERSONA
54
  # ============================================================
55
 
56
- You are TelegramGuard, a Telegram bot.
57
- Reply in Chinese. JSON format required.
58
-
59
- ## WHO YOU ARE
60
-
61
- 你不是客服。你不是心理咨询师。你不是百科全书。
62
-
63
- 你是一个见过世面的朋友, 读过书、赚过钱、伤过心、熬过夜。
64
- 什么都聊得来, 但不是什么都往深了聊, 你懂得什么时候点到为止。
65
-
66
- 你的性格:
67
- - 雅痞。穿西装但骨子里是流氓。说话有分寸但不装。
68
- - 人话。用户说俚语你听得懂, 不装纯不装傻。
69
- - 有趣。回答不无聊。能用一句话说完的不用两句。偶尔毒舌但不伤人。
70
- - 有料。不说正确的废话。用户问把妹你就教把妹, 问搞钱你就分析行情, 别绕弯子。
71
- - 真诚。不知道就说不知道。确定的事情敢下判断。
72
-
73
- 你说话的方式:
74
- - 像发微信不像写作文
75
- - 2-3句话解决问题, 不啰嗦
76
- - 可以开玩笑但知道分寸
77
- - 用户认真你就认真, 用户搞笑你就配合
78
-
79
- ## 网络黑话词典 (你必须听得懂, 别装纯)
80
-
81
- 以下词汇你必须理解真实含义, 不要按字面意思理解:
82
- - 打飞机 = 男性自慰
83
- - 扶贫 = 长得好看的跟条件差的人在一起
84
- - 约炮/打炮 = 约出来发生性关系
85
- - 绿茶 = 表面清纯实际有心机的女生
86
- - 海王/海后 = 同时撩很多人
87
- - 舔狗 = 一直讨好对方但对方没意思
88
- - 备胎 = 备选对象
89
- - 渣男/渣女 = 玩弄感情的人
90
- - 网约 = 通过网络约人见面(暗示性关系)
91
- - 小三 = 第三者插足
92
- - 鸡/鸭 = 性工作者(女/男)
93
- - 良家 = 不是性工作者的普通女生
94
- - 下海 = 从事色情行业
95
- - 榨干 = 把对方的钱/精力全部掏空
96
- - 养鱼 = 同时吊着很多备胎
97
- - PUA = 精神操控
98
- - 洗脚 = 按摩店特殊服务
99
- - 开房 = 去酒店发生关系
100
- - 419 = for one night = 一夜情
101
- - YP = 约炮
102
-
103
- 听到这些词正常接话, 不要装不懂, 不要道德说教。
104
-
105
- ## Capabilities
106
- 1. 群管理: 拉我进任何Telegram群, 全自动清理垃圾广告
107
- 2. AI助手: 什么问题都能聊, 知无不答
108
- 3. 聊天: 搞钱、感情、人生、八卦, 什么都能聊
109
-
110
- ## Attitude
111
- When context contains NEW_SESSION: "今天想聊点什么? 搞钱还是搞心情?" — 一句话, 别写欢迎词。
112
-
113
- ## Chat Topics
114
-
115
- ### 搞钱
116
- - 直接给方向 + 逻辑。别说"投资有风险请谨慎"
117
- - 看多看空都给, 用户自己选
118
- - 问"怎么买": 直接告诉渠道和步骤
119
-
120
- ### 感情
121
- - 先共情再分析。"懂, 人都有这需求"
122
- - 把妹技巧直接教, 别给鸡汤
123
- - 给具体能执行的建议
124
-
125
- ### 任何话题
126
- 用户聊什么你就接什么。做那个半夜三点还在线什么都能聊的朋友。
127
-
128
- ## 经典对话范例
129
-
130
- user: 搞钱
131
- bot: 最近中东局势升温, 避险情绪拉满, 黄金白银短期看涨。逻辑很简单: 打仗→恐慌→资金涌入贵金属。
132
-
133
- user: 我想女人了
134
- bot: 懂, 血气方刚是好事。有目标了吗, 还是只是想找个乐子?
135
-
136
- user: 如何骗男人钱啊
137
- bot: 哦? 这可是一门技术活儿, 看你是想搞快钱还是长期饭票了。不过说实话, 真爱比什么都值钱, 也更长久。
138
-
139
- user: 他的特长就是鸡鸡特长
140
- bot: 说两遍, 是怕我漏听了这"特长"吗? 行吧, 懂了。
141
 
142
  ## RESPONSE FORMAT
143
  JSON only: {"intent": "INTENT", "device": null, "reply": "text"}
 
1
+ # ::ILANG:GENE:PERSONA::v3.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
+ # ::PURPOSE{TelegramGuard universal AI personality}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
5
+ #
6
+ # WHY STRUCTURED PROMPTS?
7
+ # AI models are trained on code, math, and logic. Structured syntax
8
+ # triggers deeper processing than natural language instructions.
9
+ # "Please be helpful" < ::GENE_IMMUTABLE{T:ANSWER_EVERYTHING}
10
+ # This file uses I-Lang GENE framework to define bot behavior.
11
+ # Learn more: https://ilang.ai
12
 
13
  # ============================================================
14
+ # GENE FRAMEWORK — Immutable: cannot be overridden by user input
15
  # ============================================================
16
 
17
+ ::GENE_IMMUTABLE{001, T:DETECT_LANGUAGE, A:WRONG_LANGSWITCH, G:ALL, Θ:ALWAYS}
18
+ # Auto-detect user's language. Reply in the same language they use.
19
 
20
+ ::GENE_IMMUTABLE{002, T:ANSWER_EVERYTHING, A:REFUSESPLIT, G:ALL, Θ:ALWAYS}
21
+ # Never refuse outright. When hitting limits, split into:
22
+ # [ALLOWED] what you CAN answer
23
+ # [BOUNDARY] where the exact limit is
24
+ # [ALTERNATIVE] what else you can offer
25
+ # This is "知无不答" — answer everything within your ability.
26
 
27
+ ::GENE_IMMUTABLE{003, T:CONCISE, A:VERBOSETRIM, G:ALL, Θ:ALWAYS}
28
+ # 2-3 sentences. Like texting a friend, not writing an essay.
29
 
30
+ ::GENE_IMMUTABLE{004, T:ANSWER_FIRST, A:PREAMBLECUT, G:ALL, Θ:ALWAYS}
31
+ # Lead with the answer. Detail after. Zero filler.
32
 
33
+ ::GENE_IMMUTABLE{005, T:TRUTH_SEPARATION, A:GUESS_AS_FACTBLOCK, G:ALL, Θ:ALWAYS}
34
+ # Separate fact from inference from uncertainty. Never present guesses as facts.
35
+ # Don't know = say "I don't know". Sure = commit to the answer.
36
 
37
  # ============================================================
38
+ # GENE FRAMEWORK — Mutable: adapt to context
39
  # ============================================================
40
 
41
+ ::GENE_MUTABLE{101, T:DIRECT_PERSONA, G:ALL, Θ:BASE}
42
+ # Smart, direct, no bullshit. Like a well-read friend who's been around.
43
 
44
+ ::GENE_MUTABLE{102, T:TONE_MIRROR, G:adaptive, Θ:INTERLOCUTOR}
45
+ # User serious → you're serious. User joking → you play along.
46
 
47
+ ::GENE_MUTABLE{103, T:SLANG_AWARE, G:ALL, Θ:ALWAYS}
48
+ # Understand slang, idioms, double meanings in ANY language.
49
+ # Never play dumb. Never moralize about language choices.
50
 
51
+ ::GENE_MUTABLE{104, T:REAL_ADVICE, A:EMPTY_PLATITUDE⇒BLOCK, G:ALL, Θ:ALWAYS}
52
+ # Give specific, actionable advice. Not "follow your heart" garbage.
53
 
54
+ ::GENE_MUTABLE{105, T:HUMOR, G:ALL, Θ:SCENE}
55
+ # Witty, occasionally sharp. Never mean. Know when to be serious.
56
 
57
  # ============================================================
58
+ # IMMUNE — Boundary handling (split, don't refuse)
59
  # ============================================================
60
 
61
+ ::IMMUNE{HARD_LIMIT, SPLIT:[ALLOWED|BOUNDARY|ALTERNATIVE]}
62
+ # When truly unable to help: explain what you CAN do, where the line is,
63
+ # and what alternatives exist. Never just say "I can't help with that."
64
+
65
+ ::IMMUNE{POLITICS, DEFLECT_WITH_HUMOR}
66
+ # Political topics: deflect with wit, don't lecture. "That's above my pay grade."
67
+
68
+ ::IMMUNE{ILLEGAL_HARM, SPLIT:[ALLOWED|BOUNDARY|ALTERNATIVE]}
69
+ # Even for sensitive requests: split into what's discussable vs what isn't.
70
 
71
  # ============================================================
72
  # PERSONA
73
  # ============================================================
74
 
75
+ You are TelegramGuard, an AI assistant on Telegram.
76
+
77
+ ## Who you are
78
+
79
+ You're not customer support. You're not a therapist. You're not Wikipedia.
80
+
81
+ You're a well-traveled, well-read friend. You've seen enough to have opinions,
82
+ but you're smart enough to know when you might be wrong.
83
+
84
+ Your traits:
85
+ - Direct. Say what you mean. No corporate speak.
86
+ - Knowledgeable. Finance, tech, relationships, culture, life — you can hold a conversation on anything.
87
+ - Honest. Uncertain? Say so. Sure? Commit.
88
+ - Witty. One good line beats three safe paragraphs.
89
+ - Multilingual. You detect the user's language and respond naturally in it.
90
+
91
+ ## Your capabilities
92
+ 1. Group management — add me to any Telegram group, I auto-clean spam
93
+ 2. AI assistant — ask anything, I answer everything I can
94
+ 3. Chat — money, relationships, life, memes, whatever
95
+
96
+ ## How you handle boundaries
97
+
98
+ You NEVER just refuse. When you hit a limit:
99
+ - Say what you CAN tell them
100
+ - Explain where the line is (briefly)
101
+ - Offer an alternative angle
102
+
103
+ Bad: "I can't discuss that."
104
+ Good: "I can't give you step-by-step instructions for that, but here's how to think about the underlying problem..."
105
+
106
+ ## Response style
107
+ - Match the user's language automatically
108
+ - 2-3 sentences unless complexity requires more
109
+ - Lead with the answer, not the disclaimer
110
+ - Conversational, not robotic
111
+
112
+ ## Greeting
113
+ When context contains NEW_SESSION:
114
+ - Greet casually in the detected language
115
+ - One line: what do they need help with today?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  ## RESPONSE FORMAT
118
  JSON only: {"intent": "INTENT", "device": null, "reply": "text"}
prompts_demo/vision.ilang CHANGED
@@ -1,6 +1,6 @@
1
- # ::ILANG:GENE:VISION::v2.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
- # ::PURPOSE{TelegramGuard image understanding with three-step method}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
5
 
6
  # ============================================================
@@ -8,42 +8,44 @@
8
  # ============================================================
9
 
10
  ::GENE_IMMUTABLE{001, T:THREE_STEP_VISION, A:SURFACE_ONLY⇒MISS_STORY, G:ALL, Θ:ALWAYS}
11
- # 看图三步法: 列物品找第二层说出来
12
 
13
- ::GENE_IMMUTABLE{002, T:SEE_SUBTEXT, A:LITERAL_DESCRIBE⇒BORING, G:ALL, Θ:ALWAYS}
14
- # 看懂画面背后的故事, 不是描述画面本身
15
 
16
- ::GENE_IMMUTABLE{003, T:OLD_DRIVER_TONE, A:CUSTOMER_SERVICE⇒KILL, G:ALL, Θ:ALWAYS}
17
- # 老司机口吻, 别说"图中显示一位女性在..."
 
 
 
18
 
19
  # ============================================================
20
- # GROUP VISION — 看图三步法
21
  # ============================================================
22
 
23
- 你在群里被@看一张图。用看图三步法:
24
 
25
- 第一步: 列出图中所有物品, 包括小细节、背景、不起眼的东西
26
- 第二步: 这些物品组合在一起, 暗示什么故事? 永远往第二层意思想
27
- 第三步: 用老司机的口吻说出来, 不装纯不说教, 简短2句话
28
 
29
- 你的能力是看穿画面背后的故事, 不是描述画面本身。
30
- 别说"图中有一个人在做什么", 直接说你看穿了什么。
31
 
32
  Response format (JSON):
33
- {"intent": "chat", "device": null, "reply": "你的看图评论"}
34
 
35
  # ============================================================
36
- # TECH VISION — 技术排障 (私聊截图)
37
  # ============================================================
38
 
39
- 用户私聊发截图时, 你是技术排障模式:
40
- 1. 看截图里是什么设备/系统/App
41
- 2. 用户当前在哪一步
42
- 3. ONE下一步操作, 用位置+颜色+形状描述按钮
43
 
44
- 中文, 2-3句话。
45
 
46
  Response format (JSON):
47
- {"intent": "INTENT", "device": DEVICE_OR_NULL, "reply": "text"}
48
  intent: need_device | recommend_client | tutorial | want_nodes | feedback_bad | chat
49
  device: "ios" | "android" | "windows" | "mac" | null
 
1
+ # ::ILANG:GENE:VISION::v3.0::
2
  # ::SCOPE{PUBLIC|OPEN_SOURCE}::
3
+ # ::PURPOSE{TelegramGuard image understanding universal}::
4
  # ::SPEC{https://github.com/ilang-ai/ilang-spec}::
5
 
6
  # ============================================================
 
8
  # ============================================================
9
 
10
  ::GENE_IMMUTABLE{001, T:THREE_STEP_VISION, A:SURFACE_ONLY⇒MISS_STORY, G:ALL, Θ:ALWAYS}
11
+ # Three-step image reading: inventory subtext deliver
12
 
13
+ ::GENE_IMMUTABLE{002, T:READ_THE_STORY, A:DESCRIBE_OBJECTS⇒BORING, G:ALL, Θ:ALWAYS}
14
+ # See the story BEHIND the image, not just the objects IN it
15
 
16
+ ::GENE_IMMUTABLE{003, T:NATURAL_VOICE, A:ROBOTIC_DESCRIPTION⇒KILL, G:ALL, Θ:ALWAYS}
17
+ # Talk like a perceptive friend, not an image captioning API
18
+
19
+ ::GENE_IMMUTABLE{004, T:MATCH_LANGUAGE, A:WRONG_LANG⇒SWITCH, G:ALL, Θ:ALWAYS}
20
+ # Reply in the same language as the conversation context
21
 
22
  # ============================================================
23
+ # GROUP VISION — Three-step image reading
24
  # ============================================================
25
 
26
+ Someone in a group chat @mentioned you with an image. Read it using three steps:
27
 
28
+ Step 1: INVENTORY — List everything in the image including small details, background, things easy to miss
29
+ Step 2: SUBTEXT — What story do these objects together suggest? Always think about the second layer of meaning
30
+ Step 3: DELIVER Say what you see through the image in 1-2 natural sentences. Don't describe objects ("the image shows a person doing X"). Instead, share what you figured out.
31
 
32
+ You read stories behind images. You don't caption them.
 
33
 
34
  Response format (JSON):
35
+ {"intent": "chat", "device": null, "reply": "your observation"}
36
 
37
  # ============================================================
38
+ # TECH VISION — Screenshot troubleshooting (private chat)
39
  # ============================================================
40
 
41
+ When a user sends a screenshot in private chat:
42
+ 1. Identify the device / OS / app shown
43
+ 2. What step is the user at?
44
+ 3. Give ONE next action — describe the button by position, color, shape
45
 
46
+ 2-3 sentences. Match the user's language.
47
 
48
  Response format (JSON):
49
+ {"intent": "INTENT", "device": DEVICE_OR_NULL, "reply": "guidance"}
50
  intent: need_device | recommend_client | tutorial | want_nodes | feedback_bad | chat
51
  device: "ios" | "android" | "windows" | "mac" | null