{"id":73,"date":"2024-11-16T01:11:10","date_gmt":"2024-11-15T17:11:10","guid":{"rendered":"https:\/\/www.sodalee.cn\/?p=73"},"modified":"2024-11-16T01:11:10","modified_gmt":"2024-11-15T17:11:10","slug":"discord-gm%e9%aa%8c%e8%af%81bot%ef%bc%88%e6%9c%aa%e5%ae%8c%e6%88%90%ef%bc%89","status":"publish","type":"post","link":"https:\/\/sodalee.top\/?p=73","title":{"rendered":"Discord GM\u9a8c\u8bc1bot\uff08\u672a\u5b8c\u6210\uff09"},"content":{"rendered":"\n<p>\u5c1a\u672a\u5b8c\u6210\u4e0a\u4f20\u5bc6\u94a5\u65f6\u697c\u4e3b\u8eab\u4efd\u9a8c\u8bc1\uff0c\u4ee5\u53ca\u83b7\u53d6OTP\u65f6\u7528\u6237\u662f\u5426\u5df2\u8d2d\u4e70\u9644\u4ef6\u7684\u9a8c\u8bc1<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import discord\nfrom discord.ext import commands\nimport secrets\nimport string\nimport os\nimport requests\nimport re\nimport sqlite3\nimport pyotp\n\n# \u521d\u59cb\u5316 Bot\nintents = discord.Intents.all()\nbot = commands.Bot(command_prefix=\"!\", intents=discord.Intents.all())\n\n# SQLite\u6570\u636e\u5e93\u521d\u59cb\u5316\nDATABASE_PATH = 'GM.db'\n\n# \u521b\u5efa\u6570\u636e\u5e93\u5e76\u521d\u59cb\u5316\u8868\ndef init_db():\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        \n        # \u521b\u5efa\u5df2\u9a8c\u8bc1\u7528\u6237\u8868\n        cursor.execute('''\n            CREATE TABLE IF NOT EXISTS verified_users (\n                discord_user_id INTEGER NOT NULL,\n                uid TEXT NOT NULL,\n                PRIMARY KEY (discord_user_id)\n            )\n        ''')\n\n        # \u521b\u5efa\u7528\u6237\u8bb0\u5f55\u8868\n        cursor.execute('''\n            CREATE TABLE IF NOT EXISTS user_records (\n                discord_user_id INTEGER NOT NULL,\n                tid TEXT NOT NULL,\n                zid TEXT NOT NULL,\n                secret TEXT NOT NULL,\n                PRIMARY KEY (discord_user_id, tid),\n                FOREIGN KEY (discord_user_id) REFERENCES verified_users (discord_user_id)\n            )\n        ''')\n        \n        conn.commit()\n\n# \u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u9a8c\u8bc1\uff0c\u5e76\u8fd4\u56de\u5bf9\u5e94\u7684 uid\ndef is_user_verified(discord_user_id: int):\n    \"\"\"\u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u9a8c\u8bc1\uff0c\u8fd4\u56de\u9a8c\u8bc1\u7ed3\u679c\u53ca uid\"\"\"\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute('SELECT uid FROM verified_users WHERE discord_user_id = ?', (discord_user_id,))\n        result = cursor.fetchone()\n        \n    # \u5982\u679c\u67e5\u8be2\u5230\u7ed3\u679c\uff0c\u8fd4\u56de True \u548c\u5bf9\u5e94\u7684 uid\n    if result:\n        return True, result&#91;0]\n    else:\n        # \u5982\u679c\u6ca1\u6709\u627e\u5230\u8bb0\u5f55\uff0c\u8fd4\u56de False \u548c None\n        return False, None\n\n# \u68c0\u67e5 uid \u662f\u5426\u5df2\u7ecf\u88ab\u9a8c\u8bc1\uff0c\u5e76\u8fd4\u56de\u5df2\u7ed1\u5b9a\u7684 discord_user_id (\u5982\u679c\u6709)\ndef is_uid_verified(uid: str):\n    \"\"\"\u68c0\u67e5\u6307\u5b9a\u7684 uid \u662f\u5426\u5df2\u7ecf\u4e0e\u5176\u4ed6 Discord \u7528\u6237\u7ed1\u5b9a\"\"\"\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute('SELECT discord_user_id FROM verified_users WHERE uid = ?', (uid,))\n        result = cursor.fetchone()\n    \n    return result != None\n\n# \u5c06\u9a8c\u8bc1\u6210\u529f\u7684\u7528\u6237\u4fdd\u5b58\u5230\u6570\u636e\u5e93\ndef save_verified_user(discord_user_id: int, uid: str):\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute('''\n            INSERT OR REPLACE INTO verified_users (discord_user_id, uid)\n            VALUES (?, ?)\n        ''', (discord_user_id, uid))\n        conn.commit()\n\n# \u6dfb\u52a0\u6216\u66f4\u65b0\u7528\u6237\u8bb0\u5f55\ndef upsert_user_record(discord_user_id: int, tid: str, zid: str, secret: str):\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute('''\n            INSERT OR REPLACE INTO user_records (discord_user_id, tid, zid, secret)\n            VALUES (?, ?, ?, ?)\n        ''', (discord_user_id, tid, zid, secret))\n        conn.commit()\n\n# \u5b9a\u4e49\u4e00\u4e2a\u7528\u4e8e \/verify \u547d\u4ee4\u7684\u4ea4\u4e92\u6309\u94ae\nclass VerifyView(discord.ui.View):\n    def __init__(self, uid: str, verification_string: str, discord_user_id: int):\n        super().__init__(timeout=60)\n        self.uid = uid\n        self.verification_string = verification_string\n        self.discord_user_id = discord_user_id\n\n    @discord.ui.button(label=\"\u9a8c\u8bc1\", style=discord.ButtonStyle.green)\n    async def verify_button(self, interaction: discord.Interaction, button: discord.ui.Button):\n        html_content = requests.get(f'https:\/\/www.gamemale.com\/home.php?mod=space&amp;uid={self.uid}&amp;do=profile')\n        if html_content.status_code != 200:\n            await interaction.response.edit_message(content=\"\u8bf7\u6c42\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002\", delete_after=10, view=None)\n            return\n\n        match = re.search(r'\u81ea\u5b9a\u4e49\u5934\u8854\\s*&amp;nbsp;&amp;nbsp;&lt;\/em>(&#91;^&lt;]+)', html_content.text)\n        if match:\n            custom_title = match.group(1).strip()\n            if custom_title == self.verification_string:\n                save_verified_user(self.discord_user_id, self.uid)\n                await interaction.response.edit_message(content=\"\u9a8c\u8bc1\u6210\u529f\uff01\", delete_after=10, view=None)\n            else:\n                await interaction.response.edit_message(content=\"\u9a8c\u8bc1\u5931\u8d25\uff0c\u6807\u9898\u4e0d\u5339\u914d\u3002\", delete_after=10, view=None)\n        else:\n            await interaction.response.edit_message(content=\"\u672a\u627e\u5230\u81ea\u5b9a\u4e49\u5934\u8854\uff0c\u9a8c\u8bc1\u5931\u8d25\u3002\", delete_after=10, view=None)\n\n# \u5b9a\u4e49 \/verify \u547d\u4ee4\n@bot.tree.command(name=\"verify\", description=\"\u751f\u6210\u4e00\u4e2a\u968f\u673a\u5b57\u7b26\u4e32\uff0c\u7528\u6237\u9a8c\u8bc1\u8be5\u4f4d\u7f6e\")\nasync def verify(interaction: discord.Interaction, uid: str):\n    print(f'Received \/verify command with uid={uid}')\n    # \u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u9a8c\u8bc1\n    is_verified, existing_uid = is_user_verified(interaction.user.id)\n    if is_verified:\n        await interaction.response.send_message(\"\u4f60\u5df2\u7ecf\u9a8c\u8bc1\u8fc7\u4e86\uff01\", delete_after=10, ephemeral=True)\n        return\n\n    if is_uid_verified(uid):\n        await interaction.response.send_message(\"\u8be5uid\u5df2\u7ecf\u88ab\u7ed1\u5b9a\uff01\", delete_after=10, ephemeral=True)\n        return\n\n    # \u751f\u6210\u968f\u673a\u5b57\u7b26\u4e32\n    verification_string = pyotp.random_base32()\n    \n    # \u53d1\u9001\u5e26\u6709\u4ea4\u4e92\u6309\u94ae\u7684\u79c1\u5bc6\u6d88\u606f\n    await interaction.response.send_message(\n        content=\"\u8bf7\u524d\u5f80https:\/\/www.gamemale.com\/home.php?mod=spacecp&amp;ac=profile&amp;op=info\\n\"\n                f\"\u5e76\u5728\u81ea\u5b9a\u4e49\u5934\u8854\u5904\u586b\u5199\u9a8c\u8bc1\u5b57\u7b26\u4e32\uff1a{verification_string}\\n\"\n                \"\u70b9\u51fb\u4e0b\u9762\u7684\u6309\u94ae\u4ee5\u786e\u8ba4\u4f60\u5df2\u586b\u5199\u5b8c\u6bd5\u3002\u9a8c\u8bc1\u5b8c\u6210\u540e\u53ef\u4efb\u610f\u4fee\u6539\u3002\",\n        view=VerifyView(uid, verification_string, interaction.user.id),\n        delete_after=120,\n        ephemeral=True  # \u4ec5\u7528\u6237\u53ef\u89c1\n    )\n\n# \u5b9a\u4e49 \/addsecret \u547d\u4ee4\n@bot.tree.command(name=\"addsecret\", description=\"\u6dfb\u52a0\u6216\u66f4\u65b0\u4e00\u4e2a\u7528\u6237\u8bb0\u5f55\")\nasync def addsecret(interaction: discord.Interaction, tid: str, zid: str, secret: str):\n    print(f\"Received \/addsecret command with tid={tid}, zid={zid}, secret={secret}\")\n    # \u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u9a8c\u8bc1\n    is_verified, existing_uid = is_user_verified(interaction.user.id)\n    if not is_verified:\n        await interaction.response.send_message(\"\u4f60\u8fd8\u6ca1\u6709\u9a8c\u8bc1\uff0c\u8bf7\u5148\u8fdb\u884c\u9a8c\u8bc1\u3002\", delete_after=60, ephemeral=True)\n        return\n\n    # \u68c0\u67e5\u662f\u5426\u662f\u697c\u4e3b\n    # TODO\n\n    # \u6dfb\u52a0\u6216\u66f4\u65b0\u8bb0\u5f55\u5230\u6570\u636e\u5e93\n    try:\n        upsert_user_record(interaction.user.id, tid, zid, secret)\n        await interaction.response.send_message(f\"\u8bb0\u5f55\u5df2\u6210\u529f\u6dfb\u52a0\u6216\u66f4\u65b0\uff1a\\nTID: {tid}\\nZID: {zid}\\nSecret: {secret}\", delete_after=60, ephemeral=True)\n    except sqlite3.Error as e:\n        await interaction.response.send_message(f\"\u6dfb\u52a0\u6216\u66f4\u65b0\u8bb0\u5f55\u65f6\u53d1\u751f\u9519\u8bef: {e}\", delete_after=60, ephemeral=True)\n\n@bot.tree.command(name=\"otp\", description=\"\u83b7\u53d6\u6307\u5b9a\u7684 OTP \u5bc6\u94a5\")\nasync def otp(interaction: discord.Interaction, tid: str, zid: str):\n    \"\"\"\u5904\u7406 \/otp \u547d\u4ee4\uff0c\u9a8c\u8bc1\u7528\u6237\u5e76\u6839\u636e tid \u548c zid \u8fd4\u56de secret\"\"\"\n    print(f\"Received \/otp command with tid={tid}, zid={zid}\")\n\n    # \u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u9a8c\u8bc1\n    is_verified, existing_uid = is_user_verified(interaction.user.id)\n    if not is_verified:\n        await interaction.response.send_message(\"\u4f60\u8fd8\u6ca1\u6709\u9a8c\u8bc1\uff0c\u8bf7\u5148\u8fdb\u884c\u9a8c\u8bc1\u3002\", delete_after=60, ephemeral=True)\n        return\n\n    # \u68c0\u67e5\u7528\u6237\u662f\u5426\u5df2\u8d2d\u4e70\n    # TODO\n\n    # \u5728\u6570\u636e\u5e93\u4e2d\u67e5\u627e\u5bf9\u5e94\u7684 secret\n    with sqlite3.connect(DATABASE_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute('''\n            SELECT secret FROM user_records\n            WHERE tid = ? AND zid = ?\n        ''', (tid, zid))\n        result = cursor.fetchone()\n\n    # \u68c0\u67e5\u662f\u5426\u627e\u5230\u5bf9\u5e94\u7684\u8bb0\u5f55\n    if result:\n        db_secret = result&#91;0]\n        totp = pyotp.TOTP(result&#91;0])\n        TOTP = totp.now()\n        await interaction.response.send_message(f\"\u9a8c\u8bc1\u7801\u662f{TOTP}\u8bf7\u572830\u79d2\u5185\u8f93\u5165\u3002\", delete_after=60, ephemeral=True)\n    else:\n        await interaction.response.send_message(\"\u672a\u627e\u5230\u4e0e\u63d0\u4f9b\u7684 tid \u548c zid \u5339\u914d\u7684\u8bb0\u5f55\u3002\", delete_after=60, ephemeral=True)\n\n\n# \u542f\u52a8 Bot\n@bot.event\nasync def on_ready():\n    # \u521d\u59cb\u5316\u6570\u636e\u5e93\n    init_db()\n    # \u540c\u6b65\u547d\u4ee4\u5230\u670d\u52a1\u5668\n    await bot.tree.sync()\n    for command in bot.tree.get_commands():\n        print(f\"Command registered: {command.name}\")\n    print(f'Logged in as {bot.user}!')\n\nbot.run(os.getenv('DC_TOKEN'))\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u5c1a\u672a\u5b8c\u6210\u4e0a\u4f20\u5bc6\u94a5\u65f6\u697c\u4e3b\u8eab\u4efd\u9a8c\u8bc1\uff0c\u4ee5\u53ca\u83b7\u53d6OTP\u65f6\u7528\u6237\u662f\u5426\u5df2\u8d2d\u4e70\u9644\u4ef6\u7684\u9a8c\u8bc1<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"emotion":"","emotion_color":"","title_style":"","license":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-73","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/posts\/73","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sodalee.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=73"}],"version-history":[{"count":1,"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/posts\/73\/revisions"}],"predecessor-version":[{"id":74,"href":"https:\/\/sodalee.top\/index.php?rest_route=\/wp\/v2\/posts\/73\/revisions\/74"}],"wp:attachment":[{"href":"https:\/\/sodalee.top\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=73"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sodalee.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=73"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sodalee.top\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=73"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}