Python Telegram Minesweeper Bot https://github.com/isjerryxiao/tgmsbot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

420 lines
17 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. from telegram import InlineKeyboardMarkup, InlineKeyboardButton
  4. from telegram.ext import run_async
  5. from random import randrange
  6. from time import time
  7. import logging
  8. logger = logging.getLogger('tgmsbot.cards')
  9. # from the main module
  10. get_player = lambda *args, **kwargs: None
  11. game_manager = None
  12. MAX_LEVEL: int = 100
  13. MID_LEVEL: int = 80
  14. LVL_UP_CARDS: int = 20
  15. def display_username(user, atuser=True, shorten=False, markdown=True):
  16. """
  17. atuser and shorten has no effect if markdown is True.
  18. """
  19. name = user.full_name
  20. if markdown:
  21. mdtext = user.mention_markdown(name=user.full_name)
  22. return mdtext
  23. if shorten:
  24. return name
  25. if user.username:
  26. if atuser:
  27. name += " (@{})".format(user.username)
  28. else:
  29. name += " ({})".format(user.username)
  30. return name
  31. def _msg_users(update):
  32. '''
  33. get from_user and reply_to_user
  34. '''
  35. if update.message:
  36. if update.message.reply_to_message:
  37. return (update.message.from_user,
  38. update.message.reply_to_message.from_user)
  39. else:
  40. return (update.message.from_user, None)
  41. else:
  42. return (None, None)
  43. @run_async
  44. def getperm(update, context):
  45. logger.info(f'getperm from {getattr(update.effective_user, "id", None)}')
  46. (from_user, reply_to_user) = _msg_users(update)
  47. if not from_user:
  48. return
  49. if reply_to_user:
  50. tuser = reply_to_user
  51. else:
  52. tuser = from_user
  53. tplayer = get_player(int(tuser.id))
  54. update.message.reply_text((f"{display_username(tuser)} 等级为 {tplayer.permission}\n"
  55. f"口袋里有 {tplayer.immunity_cards} 张免疫卡"),
  56. parse_mode="Markdown")
  57. @run_async
  58. def setperm(update, context):
  59. logger.info(f'setperm from {getattr(update.effective_user, "id", None)}')
  60. (from_user, reply_to_user) = _msg_users(update)
  61. if not from_user:
  62. return
  63. if reply_to_user:
  64. if context.args and len(context.args) == 1:
  65. try:
  66. new_level = int(context.args[0])
  67. except ValueError:
  68. update.message.reply_text('数字不合法')
  69. return
  70. else:
  71. update.message.reply_text('请指定新的等级')
  72. return
  73. if get_player(int(from_user.id)).permission >= MAX_LEVEL:
  74. tplayer = get_player(int(reply_to_user.id))
  75. tplayer.permission = new_level
  76. tplayer.save()
  77. update.message.reply_text('请求成功')
  78. else:
  79. update.message.reply_text('请求忽略')
  80. else:
  81. update.message.reply_text('请回复被操作人')
  82. @run_async
  83. def lvlup(update, context):
  84. '''
  85. use LVL_UP_CARDS cards to level up 1 lvl
  86. '''
  87. logger.info(f'lvlup from {getattr(update.effective_user, "id", None)}')
  88. LVLUP_TIMEOUT = 10
  89. last_time = context.user_data.setdefault('lvlup_time', 0.0)
  90. ctime = time()
  91. if ctime - last_time < LVLUP_TIMEOUT:
  92. update.message.reply_text('别急,你不是刚刚才来过吗\nTips: /lvlup n 可以一次升n级哦')
  93. return
  94. else:
  95. context.user_data['lvlup_time'] = ctime
  96. (from_user, reply_to_user) = _msg_users(update)
  97. if context.args and len(context.args) == 1:
  98. try:
  99. amount = int(context.args[0])
  100. except ValueError:
  101. update.message.reply_text('数字不合法')
  102. return
  103. else:
  104. amount = 1
  105. if not from_user:
  106. return
  107. if reply_to_user:
  108. fplayer = get_player(int(from_user.id))
  109. tplayer = get_player(int(reply_to_user.id))
  110. amount = abs(amount)
  111. if fplayer.immunity_cards >= (used_cards := LVL_UP_CARDS * amount):
  112. fplayer.immunity_cards -= used_cards
  113. tplayer.permission = MAX_LEVEL - 1 if (_tpp := tplayer.permission + amount) >= MAX_LEVEL - 1 \
  114. and tplayer.permission < MAX_LEVEL else _tpp
  115. fplayer.save()
  116. tplayer.save()
  117. update.message.reply_text((f"{display_username(from_user)} 消耗了{used_cards}张免疫卡,"
  118. f"为 {display_username(reply_to_user)} 升了{amount}级"),
  119. parse_mode="Markdown")
  120. else:
  121. update.message.reply_text(f"您的免疫卡不足({fplayer.immunity_cards}),{used_cards}张免疫卡兑换{amount}等级",
  122. parse_mode="Markdown")
  123. else:
  124. fplayer = get_player(int(from_user.id))
  125. if fplayer.immunity_cards >= (used_cards := LVL_UP_CARDS * amount):
  126. if amount < 0:
  127. if fplayer.permission + amount >= 0:
  128. fplayer.immunity_cards += abs(used_cards)
  129. else:
  130. fplayer.immunity_cards += LVL_UP_CARDS * fplayer.permission
  131. else:
  132. fplayer.immunity_cards -= abs(used_cards)
  133. fplayer.permission = MAX_LEVEL - 1 if (_fpp := fplayer.permission + amount) >= MAX_LEVEL - 1 \
  134. and fplayer.permission < MAX_LEVEL else _fpp
  135. fplayer.save()
  136. update.message.reply_text((f"{display_username(from_user)} 消耗了{used_cards}张免疫卡,"
  137. f"为 自己 升了{amount}级"), parse_mode="Markdown")
  138. else:
  139. update.message.reply_text(f"您的免疫卡不足({fplayer.immunity_cards}),{used_cards}张免疫卡兑换{amount}等级",
  140. parse_mode="Markdown")
  141. @run_async
  142. def transfer_cards(update, context):
  143. logger.info(f'transfer_cards from {getattr(update.effective_user, "id", None)}')
  144. (from_user, reply_to_user) = _msg_users(update)
  145. if not from_user:
  146. return
  147. if reply_to_user:
  148. if context.args and len(context.args) == 1:
  149. try:
  150. amount = int(context.args[0])
  151. except ValueError:
  152. update.message.reply_text('数字不合法')
  153. return
  154. else:
  155. update.message.reply_text('请指定数量')
  156. return
  157. if from_user.id == reply_to_user.id:
  158. fplayer = get_player(int(from_user.id))
  159. if fplayer.permission >= MID_LEVEL:
  160. fplayer.immunity_cards += amount
  161. fplayer.save()
  162. update.message.reply_text(f'{display_username(from_user)} 转给了自己{amount}张卡', parse_mode="Markdown")
  163. else:
  164. update.message.reply_text(f'{display_username(from_user)} 转给了自己{amount}张卡', parse_mode="Markdown")
  165. else:
  166. fplayer = get_player(int(from_user.id))
  167. tplayer = get_player(int(reply_to_user.id))
  168. if (amount >= 0 and fplayer.immunity_cards >= amount) or \
  169. (fplayer.permission >= MID_LEVEL and tplayer.permission <= fplayer.permission):
  170. fplayer.immunity_cards -= amount
  171. tplayer.immunity_cards += amount
  172. fplayer.save()
  173. tplayer.save()
  174. update.message.reply_text(f'{display_username(from_user)} 转给了 {display_username(reply_to_user)} {amount}张卡',
  175. parse_mode="Markdown")
  176. else:
  177. update.message.reply_text(f'转账失败,你可能没有这么多卡哦({fplayer.immunity_cards}/{amount})',
  178. parse_mode="Markdown")
  179. else:
  180. update.message.reply_text('请回复被操作人')
  181. @run_async
  182. def rob_cards(update, context):
  183. logger.info(f'rob_cards from {getattr(update.effective_user, "id", None)}')
  184. ROB_TIMEOUT = 10
  185. last_time = context.user_data.setdefault('rob_time', 0.0)
  186. ctime = time()
  187. if ctime - last_time < ROB_TIMEOUT:
  188. update.message.reply_text('别急,你不是刚刚才来过吗')
  189. return
  190. else:
  191. context.user_data['rob_time'] = ctime
  192. (from_user, reply_to_user) = _msg_users(update)
  193. if not from_user:
  194. return
  195. if reply_to_user:
  196. amount = randrange(1, 9)
  197. if from_user.id == reply_to_user.id:
  198. fplayer = get_player(int(from_user.id))
  199. fplayer.immunity_cards -= amount
  200. fplayer.save()
  201. update.message.reply_text(f'{display_username(from_user)} 自己抢走自己{amount}张卡', parse_mode="Markdown")
  202. else:
  203. fplayer = get_player(int(from_user.id))
  204. tplayer = get_player(int(reply_to_user.id))
  205. _fp = fplayer.permission if fplayer.permission > 0 else 0
  206. _tp = tplayer.permission if tplayer.permission > 0 else 0
  207. success_chance = _fp / (_fp + _tp) if _fp + _tp != 0 else 0.5
  208. def __chance(percentage):
  209. if randrange(0,10000)/10000 < percentage:
  210. return True
  211. else:
  212. return False
  213. MSG_TEXT_SUCCESS = "抢劫成功,获得"
  214. MSG_TEXT_FAIL = "抢劫失败,反被抢走"
  215. if _fp >= MID_LEVEL and _tp >= MID_LEVEL:
  216. cards_amount = int(max(abs(fplayer.immunity_cards), abs(tplayer.immunity_cards)) * randrange(1000,8000)/10000)
  217. lvl_amount = int(max(_fp, _tp) * randrange(1000,8000)/10000)
  218. if (_tple if (_fp < MAX_LEVEL) ^ (_tple := _tp < MAX_LEVEL) else __chance(success_chance)):
  219. msg_text = MSG_TEXT_SUCCESS
  220. else:
  221. msg_text = MSG_TEXT_FAIL
  222. cards_amount = -cards_amount
  223. lvl_amount = -lvl_amount
  224. fplayer.immunity_cards += cards_amount
  225. tplayer.immunity_cards -= cards_amount
  226. fplayer.permission = _fpp if (_fpp := _fp + lvl_amount) < MAX_LEVEL or _fp >= MAX_LEVEL else MAX_LEVEL - 1
  227. tplayer.permission = _tpp if (_tpp := _tp - lvl_amount) < MAX_LEVEL or _tp >= MAX_LEVEL else MAX_LEVEL - 1
  228. fplayer.save()
  229. tplayer.save()
  230. update.message.reply_text((f'{display_username(from_user)} {msg_text}{abs(cards_amount)}张卡, '
  231. f'{abs(lvl_amount)}级'),
  232. parse_mode="Markdown")
  233. else:
  234. if __chance(success_chance):
  235. msg_text = MSG_TEXT_SUCCESS
  236. else:
  237. msg_text = MSG_TEXT_FAIL
  238. amount = -amount
  239. fplayer.immunity_cards += amount
  240. tplayer.immunity_cards -= amount
  241. fplayer.save()
  242. tplayer.save()
  243. update.message.reply_text(f'{display_username(from_user)} {msg_text}{abs(amount)}张卡', parse_mode="Markdown")
  244. else:
  245. update.message.reply_text('请回复被操作人')
  246. @run_async
  247. def cards_lottery(update, context):
  248. logger.info(f'cards_lottery from {getattr(update.effective_user, "id", None)}')
  249. LOTTERY_TIMEOUT = 10
  250. last_time = context.user_data.setdefault('lottery_time', 0.0)
  251. ctime = time()
  252. if ctime - last_time < LOTTERY_TIMEOUT:
  253. update.message.reply_text('别急,你不是刚刚才来过吗')
  254. return
  255. else:
  256. context.user_data['lottery_time'] = ctime
  257. (from_user, _) = _msg_users(update)
  258. if not from_user:
  259. return
  260. fplayer = get_player(int(from_user.id))
  261. cards = abs(fplayer.immunity_cards) / 3
  262. def __floating(value):
  263. return randrange(5000,15000)/10000 * value
  264. cards = __floating(cards)
  265. cards = int(cards) if cards > 1 else 1
  266. cards *= randrange(-1, 2, 2)
  267. fplayer.immunity_cards += cards
  268. fplayer.save()
  269. update.message.reply_text(f'您{"获得" if cards >= 0 else "血亏"}了{abs(cards)}张卡')
  270. @run_async
  271. def dist_cards(update, context):
  272. logger.info(f'dist_cards from {getattr(update.effective_user, "id", None)}')
  273. (from_user, _) = _msg_users(update)
  274. if not from_user:
  275. return
  276. try:
  277. if context.args and len(context.args) == 2:
  278. (cards, damount) = [int(a) for a in context.args]
  279. assert (cards > 0 and damount > 0)
  280. fplayer = get_player(int(from_user.id))
  281. assert fplayer.immunity_cards >= cards
  282. fplayer.immunity_cards -= cards
  283. fplayer.save()
  284. red_packets = context.chat_data.setdefault('red_packets', dict())
  285. rphash = str(hash(f"{update.effective_chat.id} {update.effective_message.message_id}"))[:8]
  286. red_packets[rphash] = [cards, damount]
  287. update.message.reply_text(f'{display_username(from_user)}的红包🧧', parse_mode="Markdown",
  288. reply_markup=InlineKeyboardMarkup.from_button(
  289. InlineKeyboardButton(text=f"{cards} / {damount}",
  290. callback_data=f"dist {rphash}")
  291. ))
  292. else:
  293. raise ValueError('')
  294. except (ValueError, AssertionError):
  295. update.message.reply_text(f'数字不合法: /dist 卡 红包数量')
  296. @run_async
  297. def dist_cards_btn_click(update, context):
  298. logger.info(f'dist_cards_btn_click from {getattr(update.effective_user, "id", None)}')
  299. data = update.callback_query.data
  300. user = update.callback_query.from_user
  301. omsg = update.callback_query.message
  302. red_packets = context.chat_data.setdefault('red_packets', dict())
  303. try:
  304. (_, rphash) = data.split(' ')
  305. rp = red_packets.get(str(rphash), None)
  306. if rp:
  307. (cards, damount) = [int(a) for a in rp]
  308. assert (cards > 0 and damount > 0)
  309. def __floating(value):
  310. return randrange(5000,15000)/10000 * value
  311. got_cards = int(__floating(cards/damount))
  312. got_cards = got_cards if got_cards <= cards else cards
  313. got_cards = 1 if got_cards == 0 and randrange(0,10000)/10000 < 0.2 else got_cards
  314. got_cards = got_cards if damount != 1 else cards
  315. rp[0] -= got_cards
  316. rp[1] -= 1
  317. (cards, damount) = rp
  318. fplayer = get_player(int(user.id))
  319. fplayer.immunity_cards += got_cards
  320. fplayer.save()
  321. update.callback_query.answer(text=f"你得到了{got_cards}张卡", show_alert=False)
  322. if cards > 0 and damount > 0:
  323. omsg.reply_markup.inline_keyboard[0][0].text = f"{cards} / {damount}"
  324. omsg.edit_reply_markup(reply_markup=omsg.reply_markup)
  325. else:
  326. raise AssertionError('')
  327. else:
  328. raise AssertionError('')
  329. except (ValueError, AssertionError):
  330. try:
  331. update.callback_query.answer()
  332. except Exception:
  333. pass
  334. def free_mem(job_context):
  335. try:
  336. red_packets.pop(rphash)
  337. except KeyError:
  338. pass
  339. if rphash:
  340. rp = red_packets.get(rphash, [0, 0])
  341. if rp[0] != -1:
  342. rp[0] = -1
  343. omsg.edit_text(omsg.text_markdown + "褪裙了", parse_mode="Markdown", reply_markup=None)
  344. context.job_queue.run_once(free_mem, 5)
  345. @run_async
  346. def reveal(update, context):
  347. logger.info(f'reveal from {getattr(update.effective_user, "id", None)}')
  348. (from_user, _) = _msg_users(update)
  349. if not from_user:
  350. return
  351. if (msg := update.effective_message) and (rmsg := msg.reply_to_message):
  352. try:
  353. assert (rmarkup := rmsg.reply_markup) and (kbd := rmarkup.inline_keyboard) \
  354. and type((btn := kbd[0][0])) is InlineKeyboardButton and (data := btn.callback_data)
  355. data = data.split(' ')
  356. data = [int(i) for i in data]
  357. (bhash, _, _) = data
  358. except:
  359. msg.reply_text('不是一条有效的消息')
  360. return
  361. game = game_manager.get_game_from_hash(bhash)
  362. if not game:
  363. msg.reply_text('这局似乎走丢了呢')
  364. return
  365. if (mmap := game.board.mmap) is None:
  366. msg.reply_text('这局似乎还没开始呢')
  367. return
  368. def map_to_msg():
  369. ZERO_CELL = '\u23f9'
  370. MINE_CELL = '\u2622'
  371. NUM_CELL_SUFFIX = '\ufe0f\u20e3'
  372. BAD_CELL = "\U0001f21a\ufe0f"
  373. msg_text = ""
  374. for row in mmap:
  375. for cell in row:
  376. if cell == 0:
  377. msg_text += ZERO_CELL
  378. elif cell == 9:
  379. msg_text += MINE_CELL
  380. elif cell in range(1,9):
  381. msg_text += str(cell) + NUM_CELL_SUFFIX
  382. else:
  383. msg_text += BAD_CELL
  384. msg_text += '\n'
  385. return msg_text
  386. fplayer = get_player(int(from_user.id))
  387. cards = abs(fplayer.immunity_cards) / 3
  388. def __floating(value):
  389. return randrange(5000,15000)/10000 * value
  390. cards = __floating(cards)
  391. cards = int(cards) if cards > 1 else 1
  392. extra_text = ""
  393. fplayer.immunity_cards -= cards
  394. if fplayer.permission >= MID_LEVEL and fplayer.permission < MAX_LEVEL:
  395. lvl = int(randrange(100,3000)/10000 * fplayer.permission)
  396. lvl = lvl if lvl > 0 else 1
  397. fplayer.permission -= lvl
  398. extra_text = f", {lvl}级"
  399. fplayer.save()
  400. msg.reply_text(f'本局地图如下:\n\n{map_to_msg()}\n您用去了{cards}张卡{extra_text}')
  401. else:
  402. msg.reply_text('请回复想要查看的雷区')