├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── config.yml │ └── suggest.md └── workflows │ ├── codeql-analysis.yml │ └── pflake8.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── _pathmagic.py ├── base_img ├── 5000_bg.png ├── 5000_gold.png ├── 5000_red.png ├── ps4.png ├── ps4_mask.png ├── switch.png ├── switch_mask.png ├── text_base.png ├── text_base_fore.png └── text_fore.png ├── boot_manager.py ├── cogs ├── _pathmagic.py ├── admin.py ├── auth.py ├── auto_text.py ├── automod.py ├── autoreply.py ├── batch.py ├── bump.py ├── channel_settings.py ├── cog.py ├── expand_message.py ├── fun.py ├── global.py ├── level.py ├── lock_message.py ├── moderation.py ├── panel.py ├── role_link.py ├── server_stats.py ├── template.py ├── ticket.py ├── timed_role.py ├── tools.py └── topgg.py ├── common_resources ├── consts.py ├── lang_en.py ├── lang_ja.py ├── settings.py ├── tokens.py.sample └── tools.py ├── crowdin.yml ├── fonts └── .gitkeep ├── images ├── 5000.png ├── 795568167534723102.png ├── 795569425361010719.png ├── 7botml.png ├── add.png ├── b0.png ├── b1.png ├── b10.png ├── b2.png ├── b3.png ├── b4.png ├── b5.png ├── b6.png ├── b7.png ├── b8.png ├── b9.png ├── barc1.png ├── barc21.png ├── barc210.png ├── barc22.png ├── barc23.png ├── barc24.png ├── barc25.png ├── barc26.png ├── barc27.png ├── barc28.png ├── barc29.png ├── barcg21.png ├── barcg210.png ├── barcg22.png ├── barcg23.png ├── barcg24.png ├── barcg25.png ├── barcg26.png ├── barcg27.png ├── barcg28.png ├── barcg29.png ├── barcy21.png ├── barcy210.png ├── barcy22.png ├── barcy23.png ├── barcy24.png ├── barcy25.png ├── barcy26.png ├── barcy27.png ├── barcy28.png ├── barcy29.png ├── barl.png ├── barr.png ├── bot.png ├── cat.png ├── category.png ├── check.png ├── check2.png ├── check3.png ├── check4.png ├── check5.png ├── check6.png ├── check7.png ├── check8.png ├── dnd.png ├── down.png ├── downa.png ├── icon.png ├── icon2.png ├── icon3.png ├── icon4.png ├── icon5.png ├── icon6.png ├── icon_round.png ├── idle.png ├── img_auth_base.png ├── info.png ├── join.png ├── lainan.png ├── leave.png ├── left.png ├── lefta.png ├── lock.png ├── love.png ├── network.gif ├── offline.png ├── online.png ├── othello0.png ├── othello1.png ├── othello2.png ├── othello3.png ├── othello4.png ├── ox1.png ├── ox2.png ├── per.png ├── premium.png ├── queue.gif ├── queue.psd ├── remove.png ├── reply.png ├── report.png ├── right.png ├── righta.png ├── skip.png ├── tmp │ ├── queue0000.png │ ├── queue0001.png │ ├── queue0002.png │ ├── queue0003.png │ ├── queue0004.png │ ├── queue0005.png │ ├── queue0006.png │ ├── queue0007.png │ ├── queue0008.png │ ├── queue0009.png │ ├── queue0010.png │ ├── queue0011.png │ ├── queue0012.png │ ├── queue0013.png │ ├── queue0014.png │ ├── queue0015.png │ ├── queue0016.png │ └── queue0017.png ├── toggle.png ├── unknown.png ├── up.png ├── upa.png ├── user.png ├── vc.png ├── voice.gif ├── voice_info.png ├── warn.png └── white.png ├── main.py ├── poetry.lock ├── pyproject.toml ├── save.sample └── translations ├── en └── main.json └── ja └── main.json /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: バグ報告 3 | about: バグを報告します。 4 | title: '' 5 | labels: バグ 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | ## バグの説明 18 | バグを説明してください。 19 | 20 | ## 再現方法 21 | 再現方法を書いてください。 22 | 23 | ## 想定 24 | 想定されていた動作を説明してください。 25 | 26 | ## 実際 27 | 実際に起きた動作を説明してください。 28 | 29 | ## 追加情報 30 | (もしあれば)スクリーンショットなどを添付して下さい。 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: サポートサーバー 4 | url: https://discord.gg/dnB3mcMJdu 5 | about: サポートサーバーです。 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggest.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 機能提案 3 | about: 新機能を提案します。 4 | title: '' 5 | labels: 改善 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | 18 | ## 提案は今までの問題に関連していますか?説明してください。 19 | 問題を説明して下さい。例:いっつも○○をするときに○○できなくて~ 20 | 21 | ## 解決方法 22 | 提案を受け入れることによってで解決する内容を説明して下さい。 23 | 24 | ## 他の解決法 25 | 他の解決方を説明して下さい。 26 | 27 | ## 追加情報 28 | スクショなど追加情報があれば書いてください。 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '43 22 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/pflake8.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Lint 4 | 5 | on: 6 | push: 7 | paths: 8 | - '*.py' 9 | 10 | jobs: 11 | main: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | - name: Setup Python 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.9 20 | architecture: x64 21 | - name: Install poetry 22 | run: pip install poetry 23 | - name: Setup virtualenv 24 | run: poetry install 25 | - name: Run pflake8 26 | run: | 27 | poetry run pflake8 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | Save.txt 141 | win 142 | nohup.out 143 | 144 | *.swp 145 | tokens.py 146 | .git-old/ 147 | .git-old2/ 148 | *.OTF 149 | *.htsvoice 150 | *_docs/ 151 | *.git -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | format: 2 | black . 3 | isort . 4 | 5 | start: 6 | echo "" > nohup.out 7 | nohup poetry run python3.9 boot_manager.py & -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SevenBot 2 | [![Library-discord.py](https://img.shields.io/badge/Python-3.9.2-3778ae?logo=Python&logoColor=ffffff)](https://python.org) [![Main Library-discord.py](https://img.shields.io/badge/Main%20Library-discord.py-fecc34?logo=pypi&logoColor=ffffff)](https://github.com/Rapptz/discord.py) [![Dependencies](https://img.shields.io/librariesio/github/SevenBot-dev/SevenBot?label=Dependencies&logo=libraries.io&logoColor=ffffff)](https://libraries.io/github/SevenBot-dev/SevenBot) [![Support](https://img.shields.io/discord/715540925081714788?color=5865f2&label=Discord&logo=Discord&logoColor=ffffff)](https://discord.gg/9DXVhkKZhb) 3 | 多機能Discord Botです。 4 | ネタ機能から便利な機能までいろいろな機能があります。 5 | ホームページ: https://sevenbot.jp 6 | 7 | ## 動かす前に 8 | いくつかのフォルダやファイルを意図的に消しています。 9 | |ファイル名|説明| 10 | |--|--| 11 | |tokens.py|トークンやパスワードを詰め込んだファイル。tokens.py.sampleを置き換えてください。| 12 | |fonts/|使うフォント。midium.OTF、bold.OTFという名前で入れて下さい。| 13 | 14 | ## ライセンス 15 | GPL 3.0で公開しています。 16 | これを使って出来たコードは同じライセンスで公開する必要があるためご注意下さい。 17 | -------------------------------------------------------------------------------- /_pathmagic.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/_pathmagic.py -------------------------------------------------------------------------------- /base_img/5000_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/5000_bg.png -------------------------------------------------------------------------------- /base_img/5000_gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/5000_gold.png -------------------------------------------------------------------------------- /base_img/5000_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/5000_red.png -------------------------------------------------------------------------------- /base_img/ps4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/ps4.png -------------------------------------------------------------------------------- /base_img/ps4_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/ps4_mask.png -------------------------------------------------------------------------------- /base_img/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/switch.png -------------------------------------------------------------------------------- /base_img/switch_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/switch_mask.png -------------------------------------------------------------------------------- /base_img/text_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/text_base.png -------------------------------------------------------------------------------- /base_img/text_base_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/text_base_fore.png -------------------------------------------------------------------------------- /base_img/text_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/base_img/text_fore.png -------------------------------------------------------------------------------- /boot_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import time 5 | 6 | if sys.platform == "win32": 7 | cmd = "python main.py" 8 | else: 9 | cmd = "exec python3.9 main.py" 10 | p = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr) 11 | 12 | while True: 13 | res = subprocess.run( 14 | "git pull origin main --dry-run".split(), 15 | stdout=subprocess.PIPE, 16 | stderr=subprocess.STDOUT, 17 | text=True, 18 | ) 19 | if "main.py" in res.stdout: 20 | p.kill() 21 | res = subprocess.run( 22 | "git pull origin main".split(), 23 | stdout=subprocess.PIPE, 24 | stderr=subprocess.STDOUT, 25 | text=True, 26 | ) 27 | p = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr) 28 | elif "origin/" in res.stdout: 29 | subprocess.run("git pull origin main".split(), stdout=subprocess.DEVNULL) 30 | elif os.path.exists("./reboot"): 31 | os.remove("./reboot") 32 | p.kill() 33 | p = subprocess.Popen(cmd, shell=True) 34 | elif os.path.exists("./shutdown"): 35 | os.remove("./shutdown") 36 | p.kill() 37 | break 38 | time.sleep(10) 39 | -------------------------------------------------------------------------------- /cogs/_pathmagic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | -------------------------------------------------------------------------------- /cogs/auth.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import io 4 | import random 5 | import time 6 | from itertools import product 7 | 8 | import aiohttp 9 | import discord 10 | from discord.errors import Forbidden, NotFound 11 | from discord.ext import commands, components 12 | from PIL import Image, ImageDraw, ImageFont 13 | from sembed import SEmbed 14 | 15 | import _pathmagic # type: ignore # noqa 16 | from common_resources.consts import Error, Process, Success, Widget 17 | from common_resources.tokens import web_pass 18 | 19 | 20 | class AuthCog(commands.Cog): 21 | def __init__(self, bot): 22 | global Texts 23 | global get_txt 24 | self.bot: commands.Bot = bot 25 | self.bot.guild_settings = bot.guild_settings 26 | Texts = bot.texts 27 | get_txt = bot.get_txt 28 | 29 | @commands.Cog.listener() 30 | async def on_raw_reaction_add(self, pl): 31 | # loop = asyncio.get_event_loop() 32 | if pl.user_id == self.bot.user.id: 33 | return 34 | channel = self.bot.get_channel(pl.channel_id) 35 | try: 36 | message = await channel.fetch_message(pl.message_id) 37 | except (NotFound, Forbidden): 38 | return 39 | guild = self.bot.get_guild(pl.guild_id) 40 | user = guild.get_member(pl.user_id) 41 | if message.embeds == []: 42 | return 43 | elif message.author.id != self.bot.user.id: 44 | return 45 | 46 | m0 = message.embeds[0] 47 | if message.embeds[0].title == "認証ボタン" or message.embeds[0].title.startswith("認証ボタン - "): 48 | await message.remove_reaction(pl.emoji, self.bot.get_user(pl.user_id)) 49 | guild = self.bot.get_guild(pl.guild_id) 50 | user = guild.get_member(pl.user_id) 51 | try: 52 | r = guild.get_role(int(m0.description.splitlines()[1].split(": ")[1][3:-1])) 53 | except IndexError: 54 | r = guild.get_role(self.bot.guild_settings[pl.guild_id]["auth_role"]) 55 | if message.embeds[0].title == "認証ボタン" or message.embeds[0].title.endswith("リアクション"): 56 | if pl.emoji.name == "check5": 57 | if r not in user.roles: 58 | await user.add_roles(r) 59 | try: 60 | await user.send(f"{guild.name} での認証が完了しました。") 61 | except BaseException: 62 | pass 63 | elif message.embeds[0].title.endswith("画像認証") and r not in user.roles: 64 | url, auth_text = await self.make_image_auth_url(message) 65 | e = SEmbed( 66 | color=Process, 67 | title=get_txt(message.guild.id, "img_auth_header"), 68 | description=get_txt(message.guild.id, "img_auth_desc") 69 | + "\n" 70 | + get_txt(message.guild.id, "img_auth_warn"), 71 | image_url=url, 72 | ) 73 | try: 74 | msg = await user.send(embed=e) 75 | try: 76 | await self.bot.wait_for( 77 | "message", 78 | check=lambda message: message.content.lower() == auth_text 79 | and message.guild is None 80 | and message.author == user, 81 | timeout=30, 82 | ) 83 | await msg.edit( 84 | embed=discord.Embed( 85 | title=get_txt(guild.id, "img_auth_ok").format(channel.id), 86 | color=Success, 87 | ) 88 | ) 89 | await user.add_roles(r) 90 | except asyncio.TimeoutError: 91 | await msg.edit( 92 | embed=discord.Embed( 93 | title=get_txt(guild.id, "timeout"), 94 | color=Error, 95 | ) 96 | ) 97 | except Forbidden: 98 | e = discord.Embed( 99 | title=get_txt(guild.id, "dm_fail"), 100 | color=Error, 101 | ) 102 | e.set_footer(text=get_txt(guild.id, "message_delete").format(5)) 103 | msg = await self.bot.get_channel(pl.channel_id).send(embed=e) 104 | await msg.delete(delay=5) 105 | elif message.embeds[0].title.endswith("Web認証") and r not in user.roles: 106 | async with aiohttp.ClientSession() as session: 107 | async with session.post( 108 | "https://captcha.sevenbot.jp/session", 109 | json={ 110 | "password": web_pass, 111 | "uid": user.id, 112 | "gid": guild.id, 113 | "rid": r.id, 114 | }, 115 | ) as r: 116 | r.raise_for_status() 117 | session_id = (await r.json())["message"] 118 | try: 119 | await user.send( 120 | get_txt(guild.id, "web_auth") + "\nhttps://captcha.sevenbot.jp/verify?id=" + session_id 121 | ) 122 | except discord.Forbidden: 123 | msg = await channel.send( 124 | user.mention 125 | + "\n" 126 | + get_txt(guild.id, "web_auth_notdm") 127 | + "\nhttps://captcha.sevenbot.jp/verify?id=" 128 | + session_id 129 | ) 130 | await msg.delete(delay=60) 131 | 132 | @commands.Cog.listener() 133 | async def on_button_click(self, com): 134 | if com.message.embeds == []: 135 | return 136 | m0 = com.message.embeds[0] 137 | if m0.title.startswith("認証ボタン - "): 138 | await com.defer_source(hidden=True) 139 | guild = com.guild 140 | user = com.member 141 | try: 142 | r = guild.get_role(int(m0.description.splitlines()[1].split(": ")[1][3:-1])) 143 | except IndexError: 144 | r = guild.get_role(self.bot.guild_settings[com.guild.id]["auth_role"]) 145 | if m0.title.endswith("ワンクリック"): 146 | if r not in user.roles: 147 | await user.add_roles(r) 148 | await com.send(f"{guild.name} での認証が完了しました。", hidden=True) 149 | else: 150 | await com.send("すでに認証済みです。", hidden=True) 151 | elif m0.title.endswith("画像認証"): 152 | if r not in user.roles: 153 | url, auth_text = await self.make_image_auth_url(com.message) 154 | await com.send( 155 | embed=SEmbed( 156 | description=get_txt(com.guild.id, "img_auth_desc2") 157 | + "\n" 158 | + get_txt(com.guild.id, "img_auth_warn"), 159 | image_url=url, 160 | color=Process, 161 | ) 162 | ) 163 | try: 164 | await self.bot.wait_for( 165 | "message", 166 | check=lambda message: message.content.lower() == auth_text 167 | and message.guild is None 168 | and message.author == user, 169 | timeout=30, 170 | ) 171 | await user.send(get_txt(guild.id, "img_auth_ok").format(com.channel.id)) 172 | await user.add_roles(r) 173 | except asyncio.TimeoutError: 174 | await com.send(get_txt(guild.id, "timeout"), hidden=True) 175 | else: 176 | await com.send("すでに認証済みです。", hidden=True) 177 | elif m0.title.endswith("Web認証"): 178 | if r not in user.roles: 179 | async with aiohttp.ClientSession() as session: 180 | async with session.post( 181 | "https://captcha.sevenbot.jp/session", 182 | json={ 183 | "password": web_pass, 184 | "uid": user.id, 185 | "gid": guild.id, 186 | "rid": r.id, 187 | "appid": com.application_id, 188 | "token": com.token, 189 | }, 190 | ) as r: 191 | r.raise_for_status() 192 | session_id = (await r.json())["message"] 193 | await com.send( 194 | get_txt(guild.id, "web_auth") + "\nhttps://captcha.sevenbot.jp/verify?id=" + session_id, 195 | hidden=True, 196 | ) 197 | else: 198 | await com.send("すでに認証済みです。") 199 | 200 | @commands.group() 201 | @commands.has_guild_permissions(manage_roles=True) 202 | async def auth(self, ctx): 203 | if ctx.invoked_subcommand is None: 204 | await self.bot.send_subcommands(ctx) 205 | 206 | @auth.command(name="click") 207 | @commands.has_guild_permissions(manage_roles=True) 208 | async def auth_click(self, ctx, role: discord.Role = 0): 209 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 210 | e = discord.Embed( 211 | title="ロールが登録されていません", 212 | description="初回はロールを登録する必要があります", 213 | color=Error, 214 | ) 215 | await ctx.reply(embed=e) 216 | return 217 | if role == 0: 218 | role = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]) 219 | if role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 220 | e = discord.Embed( 221 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 222 | color=Error, 223 | ) 224 | await ctx.reply(embed=e) 225 | return 226 | elif role.position > ctx.me.top_role.position: 227 | e = discord.Embed( 228 | title=get_txt(ctx.guild.id, "no_role_perm_bot").format(role.name), 229 | color=Error, 230 | ) 231 | await ctx.reply(embed=e) 232 | return 233 | if role != 0: 234 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 235 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 236 | e = discord.Embed( 237 | title="認証ボタン - ワンクリック", 238 | description="下のボタンを押して認証\n" f"ロール: {role_mention}", 239 | color=Widget, 240 | ) 241 | await components.send(ctx, embed=e, components=[components.Button("認証", "auth")]) 242 | try: 243 | await ctx.message.delete() 244 | except discord.Forbidden: 245 | pass 246 | 247 | @auth.command(name="react") 248 | @commands.has_guild_permissions(manage_roles=True) 249 | async def auth_react(self, ctx, role: discord.Role = 0): 250 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 251 | e = discord.Embed( 252 | title="ロールが登録されていません", 253 | description="初回はロールを登録する必要があります", 254 | color=Error, 255 | ) 256 | m = await ctx.reply(embed=e) 257 | return 258 | if role == 0: 259 | role = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]) 260 | if role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 261 | e = discord.Embed( 262 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 263 | color=Error, 264 | ) 265 | await ctx.reply(embed=e) 266 | return 267 | elif role.position > ctx.me.top_role.position: 268 | e = discord.Embed( 269 | title=get_txt(ctx.guild.id, "no_role_perm_bot").format(role.name), 270 | color=Error, 271 | ) 272 | await ctx.reply(embed=e) 273 | return 274 | if role != 0: 275 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 276 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 277 | e = discord.Embed( 278 | title="認証ボタン - リアクション", 279 | description=f'下の{self.bot.oemojis["check5"]}を押して認証\n' f"ロール: {role_mention}", 280 | color=Widget, 281 | ) 282 | m = await ctx.send(embed=e) 283 | await m.add_reaction(self.bot.oemojis["check5"]) 284 | await ctx.message.delete() 285 | 286 | @auth.command(name="image", aliases=["img"]) 287 | @commands.has_guild_permissions(manage_roles=True) 288 | async def auth_image(self, ctx, role: discord.Role = 0): 289 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 290 | e = discord.Embed( 291 | title="ロールが登録されていません", 292 | description="初回はロールを登録する必要があります", 293 | color=Error, 294 | ) 295 | await ctx.reply(embed=e) 296 | return 297 | if role == 0: 298 | role = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]) 299 | if role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 300 | e = discord.Embed( 301 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 302 | color=Error, 303 | ) 304 | await ctx.reply(embed=e) 305 | return 306 | elif role.position > ctx.me.top_role.position: 307 | e = discord.Embed( 308 | title=get_txt(ctx.guild.id, "no_role_perm_bot").format(role.name), 309 | color=Error, 310 | ) 311 | await ctx.reply(embed=e) 312 | return 313 | if role != 0: 314 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 315 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 316 | e = discord.Embed( 317 | title="認証ボタン - 画像認証", 318 | description=f"下のボタンを押して認証\n" f"ロール: {role_mention}", 319 | color=Widget, 320 | ) 321 | await components.send(ctx, embed=e, components=[components.Button("認証", "auth")]) 322 | try: 323 | await ctx.message.delete() 324 | except discord.Forbidden: 325 | pass 326 | 327 | @auth.command(name="react_image", aliases=["react_img"]) 328 | @commands.has_guild_permissions(manage_roles=True) 329 | async def auth_react_image(self, ctx, role: discord.Role = 0): 330 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 331 | e = discord.Embed( 332 | title="ロールが登録されていません", 333 | description="初回はロールを登録する必要があります", 334 | color=Error, 335 | ) 336 | m = await ctx.reply(embed=e) 337 | return 338 | if role == 0: 339 | role = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]) 340 | if role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 341 | e = discord.Embed( 342 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 343 | color=Error, 344 | ) 345 | await ctx.reply(embed=e) 346 | return 347 | elif role.position > ctx.me.top_role.position: 348 | e = discord.Embed( 349 | title=get_txt(ctx.guild.id, "no_role_perm_bot").format(role.name), 350 | color=Error, 351 | ) 352 | await ctx.reply(embed=e) 353 | return 354 | if role != 0: 355 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 356 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 357 | e = discord.Embed( 358 | title="認証ボタン - 画像認証", 359 | description=f'下の{self.bot.oemojis["check5"]}を押して認証\n' f"ロール: {role_mention}", 360 | color=Widget, 361 | ) 362 | m = await ctx.send(embed=e) 363 | await m.add_reaction(self.bot.oemojis["check5"]) 364 | await ctx.message.delete() 365 | 366 | @auth.command(name="react_web") 367 | @commands.has_guild_permissions(manage_roles=True) 368 | async def auth_react_web(self, ctx, role: discord.Role = 0): 369 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 370 | e = discord.Embed( 371 | title="ロールが登録されていません", 372 | description="初回はロールを登録する必要があります", 373 | color=Error, 374 | ) 375 | m = await ctx.reply(embed=e) 376 | return 377 | if role == 0: 378 | role = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]) 379 | if role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 380 | e = discord.Embed( 381 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 382 | color=Error, 383 | ) 384 | await ctx.reply(embed=e) 385 | return 386 | elif role.position > ctx.me.top_role.position: 387 | e = discord.Embed( 388 | title=get_txt(ctx.guild.id, "no_role_perm_bot").format(role.name), 389 | color=Error, 390 | ) 391 | await ctx.reply(embed=e) 392 | return 393 | if role != 0: 394 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 395 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 396 | e = discord.Embed( 397 | title="認証ボタン - Web認証", 398 | description=f'下の{self.bot.oemojis["check5"]}を押して認証\n' f"ロール: {role_mention}", 399 | color=Widget, 400 | ) 401 | m = await ctx.send(embed=e) 402 | await m.add_reaction(self.bot.oemojis["check5"]) 403 | await ctx.message.delete() 404 | 405 | @auth.command(name="web") 406 | @commands.has_guild_permissions(manage_roles=True) 407 | async def auth_web(self, ctx, role: discord.Role = 0): 408 | if self.bot.guild_settings[ctx.guild.id]["auth_role"] == 0 and role == 0: 409 | e = discord.Embed( 410 | title="ロールが登録されていません", 411 | description="初回はロールを登録する必要があります", 412 | color=Error, 413 | ) 414 | await ctx.reply(embed=e) 415 | return 416 | elif role.position > ctx.author.top_role.position and not ctx.guild.owner_id == ctx.author.id: 417 | e = discord.Embed( 418 | title=get_txt(ctx.guild.id, "no_role_perm").format(role.name), 419 | color=Error, 420 | ) 421 | await ctx.reply(embed=e) 422 | return 423 | if role != 0: 424 | self.bot.guild_settings[ctx.guild.id]["auth_role"] = role.id 425 | role_mention = ctx.guild.get_role(self.bot.guild_settings[ctx.guild.id]["auth_role"]).mention 426 | e = discord.Embed( 427 | title="認証ボタン - Web認証", 428 | description=f"下のボタンを押して認証\n" f"ロール: {role_mention}", 429 | color=Widget, 430 | ) 431 | await components.send(ctx, embed=e, components=[components.Button("認証", "auth")]) 432 | try: 433 | await ctx.message.delete() 434 | except discord.Forbidden: 435 | pass 436 | 437 | async def make_image_auth_url(self, message): 438 | im = Image.open("./base_img/text_base.png").convert("RGBA") 439 | im2 = Image.open("./base_img/text_base_fore.png") 440 | draw = ImageDraw.Draw(im) 441 | # fnt = ImageFont.truetype("./fonts/bold.OTF", 32) 442 | # tfnt = ImageFont.truetype("./fonts/midium.OTF", 32) 443 | bfnt = ImageFont.truetype("./fonts/bold.OTF", 64) 444 | auth_text = hashlib.md5(str(time.time()).encode()).hexdigest()[0:8] 445 | w, h = draw.textsize(auth_text, font=bfnt) 446 | draw.text( 447 | ((640 - w) / 2, (640 - h) / 2), 448 | auth_text, 449 | fill="white", 450 | font=bfnt, 451 | ) 452 | im3 = Image.new("RGBA", (160, 160), (0, 0, 0, 0)) 453 | for x, y in product(range(im3.size[0]), repeat=2): 454 | nr = random.randrange(0, 255) 455 | ng = random.randrange(0, 255) 456 | nb = random.randrange(0, 255) 457 | im3.putpixel((x, y), (nr, ng, nb, 128)) 458 | im = Image.alpha_composite(im, im3.resize(im.size)) 459 | draw = ImageDraw.Draw(im) 460 | im.paste(im2, mask=im2) 461 | tmpio = io.BytesIO() 462 | im.save(tmpio, format="png") 463 | tmpio.seek(0) 464 | amsg = await self.bot.get_channel(765528694500360212).send(file=discord.File(tmpio, filename="result.png")) 465 | tmpio.close() 466 | return amsg.attachments[0].url, auth_text 467 | 468 | 469 | def setup(_bot): 470 | global bot 471 | bot = _bot 472 | _bot.add_cog(AuthCog(_bot), override=True) 473 | -------------------------------------------------------------------------------- /cogs/auto_text.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from sembed import SEmbed 4 | 5 | import _pathmagic # type: ignore # noqa 6 | from common_resources.consts import ( 7 | Activate_aliases, 8 | Deactivate_aliases, 9 | Error, 10 | Info, 11 | Success, 12 | ) 13 | 14 | 15 | class AutoTextCog(commands.Cog): 16 | def __init__(self, bot): 17 | global Texts, Auto_text_channels 18 | global get_txt 19 | self.bot: commands.Bot = bot 20 | self.bot.guild_settings = bot.guild_settings 21 | Texts = bot.texts 22 | get_txt = bot.get_txt 23 | if self.bot.consts.get("atc"): 24 | Auto_text_channels = self.bot.consts["atc"] 25 | else: 26 | Auto_text_channels = {} 27 | self.bot.consts["atc"] = Auto_text_channels 28 | 29 | @commands.group(aliases=["at"], invoke_without_command=True) 30 | async def auto_text(self, ctx): 31 | await self.bot.send_subcommands(ctx) 32 | 33 | @auto_text.command("activate", aliases=Activate_aliases) 34 | async def auto_text_activate(self, ctx, channel: discord.VoiceChannel): 35 | if channel.category is None: 36 | return await ctx.reply(embed=SEmbed("VCが条件を満たしていません。", "VCはカテゴリに入っている必要があります。", color=Error)) 37 | elif channel.id in self.bot.guild_settings[ctx.guild.id]["auto_text"]: 38 | return await ctx.reply( 39 | embed=SEmbed( 40 | "VCはすでに有効です。", 41 | f"無効化するには`sb#auto_text deactivate #{channel.name}`を使用して下さい。", 42 | color=Error, 43 | ) 44 | ) 45 | self.bot.guild_settings[ctx.guild.id]["auto_text"].append(channel.id) 46 | await ctx.reply( 47 | embed=SEmbed( 48 | "自動TCを有効にしました。", 49 | f"無効化するには`sb#auto_text deactivate #{channel.name}`を使用して下さい。", 50 | color=Success, 51 | ) 52 | ) 53 | 54 | @auto_text.command("deactivate", aliases=Deactivate_aliases) 55 | async def auto_text_deactivate(self, ctx, channel: discord.VoiceChannel): 56 | if channel.id not in self.bot.guild_settings[ctx.guild.id]["auto_text"]: 57 | return await ctx.reply( 58 | embed=SEmbed( 59 | "VCはすでに無効です。", 60 | f"有効化するには`sb#auto_text activate #{channel.name}`を使用して下さい。", 61 | color=Error, 62 | ) 63 | ) 64 | self.bot.guild_settings[ctx.guild.id]["auto_text"].remove(channel.id) 65 | await ctx.reply( 66 | embed=SEmbed( 67 | "自動TCを無効にしました。", 68 | f"有効化するには`sb#auto_text activate #{channel.name}`を使用して下さい。", 69 | color=Success, 70 | ) 71 | ) 72 | 73 | @commands.Cog.listener() 74 | async def on_voice_channel_update( 75 | self, member: discord.Member, before: discord.VoiceChannel, after: discord.VoiceChannel 76 | ): 77 | if before is not None and (not before.members) and before in Auto_text_channels: 78 | try: 79 | await Auto_text_channels[before].delete() 80 | except discord.NotFound: 81 | pass 82 | del Auto_text_channels[before] 83 | if ( 84 | after is not None 85 | and after.id in self.bot.guild_settings[member.guild.id]["auto_text"] 86 | and after.id not in Auto_text_channels 87 | ): 88 | ntc = await after.category.create_text_channel( 89 | after.name, 90 | topic=f"このチャンネルは{after.mention}に誰もいなくなったら自動的に消去されます。", 91 | ) 92 | Auto_text_channels[after] = ntc 93 | await ntc.send( 94 | member.mention, 95 | embed=SEmbed( 96 | "", 97 | f"このチャンネルは{after.mention}に誰もいなくなったら自動的に消去されます。", 98 | color=Info, 99 | ), 100 | ) 101 | 102 | 103 | def setup(_bot): 104 | global bot 105 | bot = _bot 106 | _bot.add_cog(AutoTextCog(_bot), override=True) 107 | -------------------------------------------------------------------------------- /cogs/automod.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | from copy import deepcopy 4 | from typing import TYPE_CHECKING 5 | 6 | import discord 7 | import re2 as re 8 | from discord.ext import commands, tasks 9 | from sembed import SEmbed 10 | 11 | import _pathmagic # type: ignore # noqa 12 | from cogs.moderation import delta_to_text 13 | from common_resources.consts import ( # Activate_aliases,; Deactivate_aliases,; Error,; Attention, 14 | Activate_aliases, 15 | Attention, 16 | Deactivate_aliases, 17 | Error, 18 | Success, 19 | ) 20 | from common_resources.settings import ( 21 | DEFAULT_AUTOMOD_GLOBAL, 22 | DEFAULT_AUTOMOD_ITEM, 23 | AutoMod, 24 | AutoModItem, 25 | ) 26 | 27 | if TYPE_CHECKING: 28 | from moderation import ModerationCog 29 | 30 | TOKEN_PATTERN = re.compile(r"[A-Za-z0-9\-_]{23,30}\.[A-Za-z0-9\-_]{6,7}\.[A-Za-z0-9\-_]{27,40}") 31 | INVITE_PATTERN = re.compile(r"(https?://)?((ptb|canary)\.)?(discord\.(gg|io)|discord(app)?.com/invite)/[0-9a-zA-Z]+") 32 | 33 | 34 | class AutoModCog(commands.Cog): 35 | def __init__(self, bot): 36 | global Texts 37 | global get_txt 38 | self.bot: commands.Bot = bot 39 | Texts = bot.texts 40 | get_txt = bot.get_txt 41 | self._fetch_event = None 42 | if self.bot.consts.get("automod"): 43 | self.automod_settings: dict[int, AutoMod] = self.bot.consts["automod"] 44 | else: 45 | self.automod_settings = None 46 | self.bot.loop.create_task(self.fetch_automod_settings()) 47 | if not self.save.is_running(): 48 | self.save.start() 49 | 50 | @commands.group(name="automod", aliases=["am"], invoke_without_command=True) 51 | async def automod(self, ctx): 52 | await self.bot.send_subcommands(ctx) 53 | 54 | @automod.command("activate", aliases=Activate_aliases) 55 | async def activate(self, ctx: commands.Context, items: list[str]): 56 | for item in items: 57 | if not AutoModItem.__annotations__.get(item): 58 | return await ctx.reply( 59 | embed=SEmbed(f"AutoModの設定 `{item}` が見つかりませんでした。", "`sb#help` でヘルプを参照してください。", color=Error) 60 | ) 61 | self.automod_settings[int(ctx.guild.id)][item]["enabled"] = True 62 | await ctx.reply( 63 | embed=SEmbed( 64 | f"`{item}` を有効化しました。", 65 | f"オフにするときは`sb#automod {item} deactivate`を実行してください。", 66 | color=Success, 67 | fields=self.get_automod_settings_fields(ctx.guild, item), 68 | ) 69 | ) 70 | 71 | @automod.command("deactivate", aliases=Deactivate_aliases) 72 | async def deactivate(self, ctx: commands.Context, item: str): 73 | if not AutoModItem.__annotations__.get(item): 74 | return await ctx.reply( 75 | embed=SEmbed(f"AutoModの設定 `{item}` が見つかりませんでした。", "`sb#help` でヘルプを参照してください。", color=Error) 76 | ) 77 | self.automod_settings[int(ctx.guild.id)][item]["enabled"] = False 78 | await ctx.reply( 79 | embed=SEmbed( 80 | f"`{item}` を無効化しました。", 81 | f"有効化するときは`sb#automod {item} activate`を実行してください。", 82 | color=Success, 83 | fields=self.get_automod_settings_fields(ctx.guild, item), 84 | ) 85 | ) 86 | 87 | @commands.Cog.listener("on_message") 88 | async def on_message(self, message): 89 | if message.author.bot: 90 | return 91 | await self.validate_automod_settings(message.guild) 92 | if self.moderate_for(message, "token_spam"): 93 | await self.check_token_spam(message) 94 | if self.moderate_for(message, "invite_spam"): 95 | await self.check_invite_spam(message) 96 | 97 | def moderate_for(self, message: discord.Message, setting: str) -> bool: 98 | global_setting = self.automod_settings[message.guild.id]["_global"] 99 | setting_data = self.automod_settings[message.guild.id][setting] 100 | if not setting_data["enabled"]: 101 | return False 102 | if message.channel.id in setting_data["disabled_channel"] + global_setting["disabled_channel"]: 103 | return False 104 | return True 105 | 106 | async def check_token_spam(self, message: discord.Message): 107 | if TOKEN_PATTERN.search(message.content): 108 | warn = ( 109 | self.automod_settings[message.guild.id]["token_spam"]["warn"] 110 | or self.automod_settings[message.guild.id]["_global"]["warn"] 111 | ) 112 | txt = await self.add_warn(message.author, message, "トークンスパム", warn) 113 | await message.channel.send( 114 | message.author.mention, 115 | embed=SEmbed("トークンスパムが検出されました。", txt, color=Attention, footer="このメッセージは5秒後に削除されます。"), 116 | mention_author=True, 117 | delete_after=5, 118 | ) 119 | await message.delete() 120 | 121 | async def check_invite_spam(self, message: discord.Message): 122 | if INVITE_PATTERN.search(message.content): 123 | warn = ( 124 | self.automod_settings[message.guild.id]["invite_spam"]["warn"] 125 | or self.automod_settings[message.guild.id]["_global"]["warn"] 126 | ) 127 | txt = await self.add_warn(message.author, message, "招待リンクスパム", warn) 128 | await message.channel.send( 129 | message.author.mention, 130 | embed=SEmbed("招待リンクスパムが検出されました。", txt, color=Attention, footer="このメッセージは5秒後に削除されます。"), 131 | mention_author=True, 132 | delete_after=5, 133 | ) 134 | await message.delete() 135 | 136 | def get_warn_text(self, bot, message, p): 137 | res = get_txt(message.guild.id, "warn_punish")[p["action"]] 138 | if p["action"] == "mute": 139 | res += f'({delta_to_text(datetime.timedelta(seconds=p["length"]), message)})' 140 | elif p["action"] in ("role_add", "role_remove"): 141 | r = message.guild.get_role(p["role"]) 142 | res += f"({r.name})" 143 | return res 144 | 145 | async def add_warn(self, target: discord.User, message: discord.Message, reason: str, level: int): 146 | guild = message.guild 147 | if not self.bot.guild_settings[guild.id]["warns"].get(target.id): 148 | self.bot.guild_settings[guild.id]["warns"][target.id] = 0 149 | res = get_txt(guild.id, "warn_desc_info").format( 150 | target, self.bot.guild_settings[guild.id]["warns"][target.id] + level 151 | ) 152 | for _ in range(level): 153 | self.bot.guild_settings[guild.id]["warns"][target.id] += 1 154 | if self.bot.guild_settings[guild.id]["warns"][target.id] < 0: 155 | self.bot.guild_settings[guild.id]["warns"][target.id] = 0 156 | elif self.bot.guild_settings[guild.id]["warns"][target.id] >= 2 ** 64: 157 | self.bot.guild_settings[guild.id]["warns"][target.id] = 2 ** 64 - 1 158 | pun = self.bot.guild_settings[guild.id]["warn_settings"]["punishments"] 159 | nw = self.bot.guild_settings[guild.id]["warns"][target.id] 160 | if pun.get(nw): 161 | res += ( 162 | get_txt(guild.id, "warn_desc_now").format(nw, self.moderation.get_warn_text(message, pun[nw])) 163 | + "\n" 164 | ) 165 | await self.moderation.punish(target, pun[nw]) 166 | return res 167 | 168 | @property 169 | def moderation(self) -> "ModerationCog": 170 | return self.bot.cogs["ModerationCog"] 171 | 172 | async def cog_check(self, ctx: commands.Context) -> bool: 173 | return self.automod_settings is not None 174 | 175 | async def fetch_automod_settings(self): 176 | if self._fetch_event: 177 | return await self._fetch_event 178 | self._fetch_event = asyncio.Event() 179 | self.automod_settings = {} 180 | async for item in self.bot.db.automod.find({}): 181 | self.automod_settings[item["id"]] = item["data"] 182 | self.bot.consts["automod"] = self.automod_settings 183 | self._fetch_event.set() 184 | 185 | @tasks.loop(minutes=5) 186 | async def save(self): 187 | print("-- Saving automod settings...") 188 | for k, v in self.automod_settings.items(): 189 | self.bot.db.automod.replace_one({"id": k}, {"id": k, "data": v}, upsert=True) 190 | 191 | async def validate_automod_settings(self, guild: discord.Guild): 192 | if self.automod_settings is None: 193 | await self.fetch_automod_settings() 194 | if guild.id not in self.automod_settings: 195 | self.automod_settings[guild.id] = dict([[i, (DEFAULT_AUTOMOD_ITEM)] for i in AutoMod.__annotations__]) 196 | if missing_keys := (set(AutoMod.__annotations__) - set(self.automod_settings[guild.id])): 197 | for key in missing_keys: 198 | self.automod_settings[guild.id][key] = deepcopy(DEFAULT_AUTOMOD_ITEM) 199 | if "_global" not in self.automod_settings[guild.id]: 200 | self.automod_settings[guild.id]["_global"] = deepcopy(DEFAULT_AUTOMOD_GLOBAL) 201 | 202 | async def cog_before_invoke(self, ctx: commands.Context) -> None: 203 | await self.validate_automod_settings(ctx.guild) 204 | 205 | def cog_unload(self): 206 | self.save.stop() 207 | 208 | 209 | def setup(_bot): 210 | global bot 211 | bot = _bot 212 | _bot.add_cog(AutoModCog(_bot), override=True) 213 | -------------------------------------------------------------------------------- /cogs/autoreply.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import io 4 | import json 5 | import random 6 | import time 7 | import zlib 8 | 9 | import discord 10 | import re2 as re 11 | from discord.ext import commands, components 12 | from discord.ext.commands.converter import TextChannelConverter 13 | from texttable import Texttable 14 | 15 | import _pathmagic # type: ignore # noqa 16 | from common_resources.consts import Alert, Error, Info, Success 17 | 18 | 19 | class AutoreplyCog(commands.Cog): 20 | def __init__(self, bot): 21 | global Texts, GBan, SB_Bans 22 | global get_txt, is_command 23 | self.bot: commands.Bot = bot 24 | is_command = self.bot.is_command 25 | self.bot.guild_settings = bot.guild_settings 26 | Texts = bot.texts 27 | get_txt = bot.get_txt 28 | GBan = bot.raw_config["gb"] 29 | SB_Bans = bot.raw_config["sbb"] 30 | 31 | async def do_reply(self, ar, message, content): 32 | m = re.match(r"^([^:]+):([\s\S]*)$", ar[0]) 33 | if m: 34 | cmd = m[1].lower() 35 | cnt = m[2] 36 | if cmd == "re": 37 | if re.search(cnt, content): 38 | return True 39 | elif cmd == "fullmatch": 40 | if content.lower() == cnt.lower(): 41 | return True 42 | elif cmd == "channel": 43 | m2 = re.match(r"^([^|]*)\|([^|]*)$", cnt) 44 | if m2 is not None: 45 | ch = m2.group(1).split(",") 46 | for c in ch: 47 | try: 48 | fake_ctx = await self.bot.get_context(message) 49 | channel = await TextChannelConverter().convert(fake_ctx, c) 50 | except commands.errors.BadArgument: 51 | pass 52 | else: 53 | if message.channel == channel: 54 | ar[0] = m2.group(2) 55 | return await self.do_reply(ar, message, content) 56 | elif cmd == "has-image": 57 | if message.attachments and [a for a in message.attachments if a.content_type.startswith("image")]: 58 | ar[0] = cnt 59 | return await self.do_reply(ar, message, content) 60 | else: 61 | return ar[0].lower() in content.lower() 62 | elif ar[0].lower() in content.lower(): 63 | return True 64 | 65 | return False 66 | 67 | @commands.Cog.listener("on_message") 68 | async def on_message_ar(self, message): 69 | if message.author.id in GBan.keys() or message.channel.id in self.bot.global_chats: 70 | return 71 | if not self.bot.is_ready(): 72 | return 73 | if message.author.bot or message.channel.id in self.bot.guild_settings[message.guild.id]["lainan_talk"]: 74 | return 75 | if message.author.id in SB_Bans.keys() and is_command(message): 76 | if SB_Bans[message.author.id] > time.time(): 77 | return 78 | arp = self.bot.guild_settings[message.guild.id].get("autoreply") 79 | random_tmp = [] 80 | 81 | async def ar_send(ch, msg_content): 82 | try: 83 | m = re.match(r"^([^:]+):([\s\S]*)", msg_content) 84 | if m: 85 | cmd = m[1].lower() 86 | cnt = m[2] 87 | if cmd == "noreply": 88 | await ch.send(cnt) 89 | elif cmd == "pingreply": 90 | await message.reply(cnt, mention_author=True) 91 | elif cmd == "react": 92 | try: 93 | await message.add_reaction(cnt) 94 | except discord.BadRequest: 95 | await message.add_reaction(self.bot.oemojis["check2"]) 96 | elif cmd == "random": 97 | random_tmp.append(cnt) 98 | else: 99 | await message.reply(msg_content) 100 | else: 101 | await message.reply(msg_content) 102 | except asyncio.TimeoutError: 103 | pass 104 | 105 | ga = [] 106 | if arp is not None: 107 | for ar in arp.values(): 108 | if await self.do_reply(ar, message, message.content) and not is_command(message): 109 | ga.append(ar_send(message.channel, ar[1])) 110 | await asyncio.gather(*ga[:5]) 111 | if random_tmp: 112 | await ar_send(message.channel, random.choice(random_tmp)) 113 | 114 | @commands.group(aliases=["ar"]) 115 | async def autoreply(self, ctx): 116 | if ctx.invoked_subcommand is None: 117 | await self.bot.send_subcommands(ctx) 118 | 119 | @autoreply.command(name="add", aliases=["set"]) 120 | @commands.has_guild_permissions(manage_messages=True) 121 | async def ar_add(self, ctx, base, *, reply): 122 | dat = base + reply 123 | rid = hashlib.md5(dat.encode("utf8")).hexdigest()[0:8] 124 | if ctx.guild.id not in self.bot.guild_settings: 125 | await self.reset(ctx) 126 | if "autoreply" not in self.bot.guild_settings[ctx.guild.id]: 127 | self.bot.guild_settings[ctx.guild.id]["autoreply"] = {} 128 | if len(self.bot.guild_settings[ctx.guild.id]["autoreply"]) >= 500: 129 | await ctx.reply(embed=discord.Embed(title="自動返信の上限に達しました。", description="自動返信の上限は500です。", color=Error)) 130 | self.bot.guild_settings[ctx.guild.id]["autoreply"][rid] = [base, reply] 131 | e = discord.Embed( 132 | title=f"自動返信に`{base}`を追加しました。", 133 | description=f"戻すには`sb#autoreply remove {base}`または" f"`sb#autoreply remove {rid}`を使用してください", 134 | color=Success, 135 | ) 136 | await ctx.reply(embed=e) 137 | if reply.startswith("!"): 138 | e = discord.Embed( 139 | title="注意!", 140 | description="コマンドは`!コマンド名 内容`から`コマンド名:内容`へ移行しました。", 141 | color=Alert, 142 | ) 143 | await ctx.reply(embed=e) 144 | 145 | @autoreply.command(name="remove", aliases=["del", "delete", "rem"]) 146 | @commands.has_guild_permissions(manage_messages=True) 147 | async def ar_remove(self, ctx, *, txt): 148 | res = "" 149 | count = 0 150 | new = {} 151 | if txt in self.bot.guild_settings[ctx.guild.id]["autoreply"].keys(): 152 | res += "`" + self.bot.guild_settings[ctx.guild.id]["autoreply"][txt][1] + "`\n" 153 | for ark, ar in self.bot.guild_settings[ctx.guild.id]["autoreply"].items(): 154 | if ark != txt: 155 | new[ark] = ar 156 | count = 1 157 | else: 158 | for ark, ar in self.bot.guild_settings[ctx.guild.id]["autoreply"].items(): 159 | if ar[0] == txt: 160 | count += 1 161 | res += "`" + ar[1] + "`\n" 162 | else: 163 | new[ark] = ar 164 | if count == 0: 165 | e = discord.Embed( 166 | title=f"自動返信に`{txt}`は含まれていません。", 167 | description="`sb#autoreply list`で確認してください。", 168 | color=Error, 169 | ) 170 | else: 171 | e = discord.Embed(title=f"{count}個の自動返信を削除しました。", description=res, color=Success) 172 | self.bot.guild_settings[ctx.guild.id]["autoreply"] = new 173 | return await ctx.reply(embed=e) 174 | 175 | @autoreply.command(name="list") 176 | async def ar_list(self, ctx): 177 | g = ctx.guild.id 178 | if g not in self.bot.guild_settings: 179 | await self.reset(ctx) 180 | gs = self.bot.guild_settings[g] 181 | if gs["autoreply"] == {}: 182 | e = discord.Embed( 183 | title=get_txt(ctx.guild.id, "ar_list_no"), 184 | description=get_txt(ctx.guild.id, "ar_list_no_desc"), 185 | color=Error, 186 | ) 187 | return await ctx.reply(embed=e) 188 | else: 189 | 190 | def make_new(): 191 | table = Texttable(max_width=80) 192 | table.set_deco(Texttable.HEADER) 193 | table.set_cols_dtype(["t", "t", "t"]) 194 | table.set_cols_align(["l", "l", "l"]) 195 | table.set_cols_width([8, 19, 20]) 196 | table.add_row(get_txt(ctx.guild.id, "ar_list_row")) 197 | return table 198 | 199 | table = make_new() 200 | res = [] 201 | for k, v in gs["autoreply"].items(): 202 | b = table.draw() 203 | table.add_row( 204 | [ 205 | k, 206 | v[0].replace("\n", get_txt(ctx.guild.id, "ar_list_br")), 207 | v[1].replace("\n", get_txt(ctx.guild.id, "ar_list_br")), 208 | ] 209 | ) 210 | if len(table.draw()) > 2000: 211 | res.append(b) 212 | table = make_new() 213 | table.add_row( 214 | [ 215 | k, 216 | v[0].replace("\n", get_txt(ctx.guild.id, "ar_list_br")), 217 | v[1].replace("\n", get_txt(ctx.guild.id, "ar_list_br")), 218 | ] 219 | ) 220 | res.append(table.draw()) 221 | e = discord.Embed( 222 | title=get_txt(ctx.guild.id, "ar_list") + f" - {1}/{len(res)}", 223 | description=f"```asciidoc\n{res[0]}```", 224 | color=Info, 225 | ) 226 | buttons = [ 227 | components.Button("前のページ", "left", style=2), 228 | components.Button("次のページ", "right", style=2), 229 | components.Button("終了", "exit", style=4), 230 | ] 231 | msg = await components.reply(ctx, embed=e, components=buttons) 232 | page = 0 233 | while True: 234 | try: 235 | com = await self.bot.wait_for( 236 | "button_click", 237 | check=lambda com: com.message == msg and com.member == ctx.author, 238 | timeout=60, 239 | ) 240 | await com.defer_update() 241 | if com.custom_id == "left": 242 | if page > 0: 243 | page -= 1 244 | buttons[0].enabled = page != 0 245 | elif com.custom_id == "right": 246 | if page < (len(res) - 1): 247 | page += 1 248 | buttons[1].enabled = page != (len(res) - 1) 249 | elif com.custom_id == "exit": 250 | break 251 | e = discord.Embed( 252 | title=get_txt(ctx.guild.id, "ar_list") + f" - {page+1}/{len(res)}", 253 | description=f"```asciidoc\n{res[page]}```", 254 | color=Info, 255 | ) 256 | await msg.edit(embed=e) 257 | except asyncio.TimeoutError: 258 | break 259 | for c in buttons: 260 | c.enabled = False 261 | await components.edit(msg, components=buttons) 262 | 263 | @autoreply.command(name="export") 264 | async def ar_export(self, ctx): 265 | res = {} 266 | replies = self.bot.guild_settings[ctx.guild.id]["autoreply"] 267 | for key, value in replies.values(): 268 | res[key] = value 269 | res_str = zlib.compress(json.dumps(res, ensure_ascii=False).encode("utf8")) 270 | await ctx.reply(file=discord.File(io.BytesIO(res_str), filename=f"autoreply_{ctx.guild.id}.sbar")) 271 | 272 | @autoreply.command(name="import") 273 | @commands.has_permissions(manage_guild=True) 274 | async def ar_import(self, ctx): 275 | try: 276 | file = await ctx.message.attachments[0].read() 277 | except IndexError: 278 | await ctx.reply( 279 | embed=discord.Embed( 280 | title="ファイルがありません", 281 | description="ファイルを添付して実行してください。", 282 | color=Error, 283 | ) 284 | ) 285 | else: 286 | res = zlib.decompress(file).decode("utf8") 287 | res = json.loads(res) 288 | final = {} 289 | for k, v in res.items(): 290 | final[hashlib.md5((k + v).encode("utf-8")).hexdigest()[0:8]] = [k, v] 291 | if len(final) > 500: 292 | return await ctx.reply( 293 | embed=discord.Embed(title="数が多すぎます。", description="自動返信の最大数は500です。", color=Error) 294 | ) 295 | 296 | self.bot.guild_settings[ctx.guild.id]["autoreply"] = final 297 | await ctx.reply(embed=discord.Embed(title="自動返信を読み込みました。", color=Success)) 298 | 299 | 300 | def setup(_bot): 301 | global bot 302 | bot = _bot 303 | _bot.add_cog(AutoreplyCog(_bot), override=True) 304 | -------------------------------------------------------------------------------- /cogs/batch.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import collections 3 | import datetime 4 | 5 | # import sys 6 | import time 7 | 8 | import aiohttp 9 | import discord 10 | import psutil 11 | from discord.ext import commands, tasks 12 | 13 | import _pathmagic # type: ignore # noqa 14 | from common_resources.settings import GuildSettings 15 | from common_resources.tokens import botdd_token, emergency 16 | 17 | Last_favorite = {} 18 | Time_format = "%Y-%m-%d %H:%M:%S" 19 | Bump_alerts = {} 20 | Batchs = [] 21 | Number_emojis = [] 22 | Bump_id = 302050872383242240 23 | Dissoku_id = 761562078095867916 24 | Bump_color = 0x24B8B8 25 | Dissoku_color = 0x7289DA 26 | 27 | 28 | class BatchCog(commands.Cog): 29 | def __init__(self, bot): 30 | global Bump_alerts, Dissoku_alerts 31 | global get_txt 32 | self.bot: commands.AutoShardedBot = bot 33 | self.bot.guild_settings = self.bot.guild_settings 34 | Bump_alerts = self.bot.raw_config["ba"] 35 | Dissoku_alerts = self.bot.raw_config["da"] 36 | get_txt = self.bot.get_txt 37 | Batchs.append(self.restart_tasks.start()) 38 | time.sleep(0.1) 39 | Batchs.append(self.batch_change_activity.start()) 40 | time.sleep(0.1) 41 | Batchs.append(self.bot.loop.create_task(self.sync_db())) 42 | time.sleep(0.1) 43 | Batchs.append(self.batch_save.start()) 44 | if not self.bot.debug: 45 | time.sleep(0.1) 46 | Batchs.append(self.botdd_post.start()) 47 | time.sleep(0.1) 48 | Batchs.append(self.batch_send_status.start()) 49 | 50 | @tasks.loop(minutes=1) 51 | async def batch_change_activity(self): 52 | s = getattr(self.bot, "custom_status", None) 53 | guild_count = collections.Counter(map(lambda g: g.shard_id, self.bot.guilds)) 54 | for i in self.bot.shards: 55 | n = s or ( 56 | f"sb#help | {len(self.bot.guilds)} ({guild_count[i]}) Servers | " 57 | + f"Shard {i + 1} / {self.bot.shard_count} | " 58 | + ("Emergency mode" if emergency else "https://sevenbot.jp") 59 | ) 60 | await self.bot.change_presence( 61 | activity=discord.Game(name=n + "⠀" * 10), 62 | status=discord.Status.dnd if emergency else discord.Status.online, 63 | shard_id=i, 64 | ) 65 | 66 | @tasks.loop(seconds=30) 67 | async def restart_tasks(self): 68 | value: dict[str, discord.ext.tasks.Loop] = {} 69 | for c in self.bot.cogs.values(): 70 | for a in filter(lambda a: not a.startswith("_"), dir(c)): 71 | v = getattr(c, a) 72 | if isinstance(v, discord.ext.tasks.Loop): 73 | value[a] = v 74 | for k, v in value.items(): 75 | if not v.is_running(): 76 | v.start() 77 | 78 | @tasks.loop(minutes=10) 79 | async def batch_send_status(self): 80 | loop = asyncio.get_event_loop() 81 | if self.bot.latency > 5: 82 | return 83 | if len(self.bot.users) < 10000: 84 | for guild in self.bot.guilds: 85 | await guild.chunk() 86 | return 87 | cpu = await loop.run_in_executor(None, psutil.cpu_percent, 1) 88 | mem = await loop.run_in_executor(None, psutil.virtual_memory) 89 | gb = 1024 * 1024 * 1024 90 | call = await self.bot.db.command("dbstats") 91 | ping_before = time.time() 92 | await self.bot.db.ping.insert_one({"ping": "pong"}) 93 | db_ping = time.time() - ping_before 94 | ping_before = time.time() 95 | async with aiohttp.ClientSession() as c: 96 | async with c.get("https://sevenbot.jp") as r: 97 | await r.text() 98 | web_ping = time.time() - ping_before 99 | await self.bot.db.status_log.insert_one( 100 | { 101 | "ping": round(self.bot.latency * 1000), 102 | "db_ping": round(db_ping * 1000), 103 | "web_ping": round(web_ping * 1000), 104 | "guilds": len(self.bot.guilds), 105 | "users": len(self.bot.users), 106 | "cpu": cpu, 107 | "mem": {"percent": mem.percent, "gb": mem.used / gb}, 108 | "time": time.time(), 109 | "save": { 110 | "main": len(str(self.bot.raw_config).encode("utf8")) / 1024, 111 | "db": call["dataSize"] / 1024, 112 | }, 113 | } 114 | ) 115 | await self.bot.db.status_log.delete_many({"time": {"$lt": time.time() - 60 * 60 * 24 * 7}}) 116 | await self.bot.db.ping.delete_many({"ping": "pong"}) 117 | 118 | @batch_send_status.before_loop 119 | async def batch_send_status_before(self): 120 | await asyncio.sleep((10 - datetime.datetime.now().minute % 10) * 60) 121 | 122 | @tasks.loop(minutes=5) 123 | async def batch_save(self): 124 | await self.bot.save() 125 | 126 | @tasks.loop(seconds=30) 127 | async def botdd_post(self): 128 | async with aiohttp.ClientSession() as s: 129 | async with s.post( 130 | "https://botdd.alpaca131.com/api/heartbeat", 131 | headers={"authorization": "Bearer " + botdd_token}, 132 | ): 133 | pass 134 | 135 | async def sync_db(self): 136 | async with self.bot.db.guild_settings.watch() as change_stream: 137 | async for change in change_stream: 138 | if change["operationType"] == "delete": 139 | # del self.bot.guild_settings[change["documentKey"]["gid"]] 140 | pass # TODO: delete guild settings 141 | if change["operationType"] == "update": 142 | gs = await self.bot.db.guild_settings.find_one(change["documentKey"]) 143 | for ik in GuildSettings.int_keys: 144 | t = gs 145 | for ikc in ik.split("."): 146 | t = t[ikc] 147 | t2 = dict([(int(k), v) for k, v in t.items()]) 148 | t.clear() 149 | t.update(t2) 150 | del gs["_id"] 151 | self.bot.guild_settings[gs["gid"]] = gs 152 | if change["operationType"] == "insert": 153 | gs = change["fullDocument"] 154 | for ik in GuildSettings.int_keys: 155 | t = gs 156 | for ikc in ik.split("."): 157 | t = t[ikc] 158 | t2 = dict([(int(k), v) for k, v in t.items()]) 159 | t.clear() 160 | t.update(t2) 161 | del gs["_id"] 162 | self.bot.guild_settings[gs["gid"]] = gs 163 | 164 | def cog_unload(self): 165 | for ba in Batchs: 166 | ba.cancel() 167 | 168 | 169 | def setup(_bot): 170 | global bot 171 | bot = _bot 172 | _bot.add_cog(BatchCog(_bot), override=True) 173 | -------------------------------------------------------------------------------- /cogs/bump.py: -------------------------------------------------------------------------------- 1 | # -*- ignore_on_debug -*- 2 | import asyncio 3 | import time 4 | from typing import Optional 5 | 6 | import discord 7 | from discord.ext import commands, tasks 8 | 9 | import _pathmagic # type: ignore # noqa 10 | from common_resources.consts import Activate_aliases, Deactivate_aliases, Error, Success 11 | 12 | Bump_id = 302050872383242240 13 | Dissoku_id = 761562078095867916 14 | Bump_color = 0x24B8B8 15 | Dissoku_color = 0x7289DA 16 | 17 | 18 | class BumpCog(commands.Cog): 19 | def __init__(self, bot): 20 | global Texts, Dissoku_alerts 21 | global Bump_alerts 22 | global get_txt 23 | self.bot: commands.Bot = bot 24 | self.bot.guild_settings = bot.guild_settings 25 | Dissoku_alerts = bot.raw_config["da"] 26 | Bump_alerts = bot.raw_config["ba"] 27 | Texts = bot.texts 28 | get_txt = bot.get_txt 29 | self.batch_bump_alert.start() 30 | 31 | @commands.Cog.listener("on_message") 32 | async def on_message_bumps(self, message): 33 | 34 | if message.author.id == Bump_id and self.bot.guild_settings[message.guild.id]["do_bump_alert"]: 35 | try: 36 | if message.embeds[0].image != discord.Embed().Empty: 37 | if "disboard.org/images/bot-command-image-bump.png" in str(message.embeds[0].image.url): 38 | Bump_alerts[message.guild.id] = [ 39 | time.time() + 3600 * 2, 40 | message.channel.id, 41 | ] 42 | e = discord.Embed( 43 | title=get_txt(message.guild.id, "bump_detected"), 44 | description=get_txt(message.guild.id, "bump_detected_desc").format( 45 | int(time.time()) + 3600 * 2 46 | ), 47 | color=Bump_color, 48 | ) 49 | await message.channel.send(embed=e) 50 | except IndexError: 51 | pass 52 | elif message.author.id == Dissoku_id and self.bot.guild_settings[message.guild.id]["do_dissoku_alert"]: 53 | try: 54 | msg = await self.bot.wait_for( 55 | "raw_message_edit", check=lambda u: u.message_id == message.id, timeout=10 56 | ) 57 | if msg.data["embeds"][0]["fields"]: 58 | if message.guild.name in msg.data["embeds"][0]["fields"][0]["name"]: 59 | Dissoku_alerts[message.guild.id] = [ 60 | time.time() + 3600, 61 | message.channel.id, 62 | ] 63 | e = discord.Embed( 64 | title=get_txt(message.guild.id, "dissoku_detected"), 65 | description=get_txt(message.guild.id, "dissoku_detected_desc").format( 66 | int(time.time()) + 3600 67 | ), 68 | color=Dissoku_color, 69 | ) 70 | await message.channel.send(embed=e) 71 | except (IndexError, asyncio.TimeoutError): 72 | pass 73 | return 74 | 75 | @tasks.loop(seconds=10) 76 | async def batch_bump_alert(self): 77 | for guild, (alert_time, channel) in list(Bump_alerts.items()): 78 | if alert_time < time.time(): 79 | e = discord.Embed( 80 | title=get_txt(guild, "bump_alert"), 81 | description=get_txt(guild, "bump_alert_desc"), 82 | color=Bump_color, 83 | ) 84 | c = self.bot.get_channel(channel) 85 | if c is not None: 86 | m = "" 87 | if self.bot.guild_settings[guild]["bump_role"]: 88 | r = c.guild.get_role(self.bot.guild_settings[guild]["bump_role"]) 89 | if r: 90 | m = r.mention 91 | try: 92 | await c.send( 93 | content=m, 94 | embed=e, 95 | allowed_mentions=discord.AllowedMentions(roles=True), 96 | ) 97 | except discord.Forbidden: 98 | pass 99 | del Bump_alerts[guild] 100 | for guild, (alert_time, channel) in list(Dissoku_alerts.items()): 101 | if alert_time < time.time(): 102 | e = discord.Embed( 103 | title=get_txt(guild, "dissoku_alert"), 104 | description=get_txt(guild, "dissoku_alert_desc"), 105 | color=Dissoku_color, 106 | ) 107 | c = self.bot.get_channel(channel) 108 | if c is not None: 109 | m = "" 110 | if self.bot.guild_settings[guild]["dissoku_role"]: 111 | r = c.guild.get_role(self.bot.guild_settings[guild]["dissoku_role"]) 112 | if r: 113 | m = r.mention 114 | try: 115 | await c.send( 116 | content=m, 117 | embed=e, 118 | allowed_mentions=discord.AllowedMentions(roles=True), 119 | ) 120 | except discord.Forbidden: 121 | pass 122 | del Dissoku_alerts[guild] 123 | 124 | @commands.group(invoke_without_command=True) 125 | @commands.has_permissions(manage_messages=True) 126 | async def bump(self, ctx): 127 | if ctx.invoked_subcommand is None: 128 | await self.bot.send_subcommands(ctx) 129 | 130 | @bump.command(name="activate", aliases=Activate_aliases) 131 | @commands.has_permissions(manage_messages=True) 132 | async def bump_activate(self, ctx): 133 | if self.bot.guild_settings[ctx.guild.id]["do_bump_alert"]: 134 | e = discord.Embed(title=get_txt(ctx.guild.id, "activate_fail"), color=Error) 135 | return await ctx.reply(embed=e) 136 | else: 137 | self.bot.guild_settings[ctx.guild.id]["do_bump_alert"] = True 138 | e = discord.Embed( 139 | title=get_txt(ctx.guild.id, "activate").format("Bump通知"), 140 | color=Success, 141 | ) 142 | return await ctx.reply(embed=e) 143 | 144 | @bump.command(name="deactivate", aliases=Deactivate_aliases) 145 | @commands.has_permissions(manage_messages=True) 146 | async def bump_deactivate(self, ctx): 147 | if not self.bot.guild_settings[ctx.guild.id]["do_bump_alert"]: 148 | e = discord.Embed(title=get_txt(ctx.guild.id, "deactivate_fail"), color=Error) 149 | return await ctx.reply(embed=e) 150 | else: 151 | self.bot.guild_settings[ctx.guild.id]["do_bump_alert"] = False 152 | e = discord.Embed( 153 | title=get_txt(ctx.guild.id, "deactivate").format("Bump通知"), 154 | color=Success, 155 | ) 156 | return await ctx.reply(embed=e) 157 | 158 | @bump.command(name="role") 159 | @commands.has_permissions(manage_messages=True) 160 | async def bump_role(self, ctx, role: Optional[discord.Role] = None): 161 | if role: 162 | self.bot.guild_settings[ctx.guild.id]["bump_role"] = role.id 163 | e = discord.Embed(title=get_txt(ctx.guild.id, "bump_role_set"), color=Success) 164 | else: 165 | self.bot.guild_settings[ctx.guild.id]["bump_role"] = None 166 | e = discord.Embed(title=get_txt(ctx.guild.id, "bump_role_set_none"), color=Success) 167 | return await ctx.reply(embed=e) 168 | 169 | @commands.group(invoke_without_command=True) 170 | @commands.has_permissions(manage_messages=True) 171 | async def dissoku(self, ctx): 172 | if ctx.invoked_subcommand is None: 173 | await self.bot.send_subcommands(ctx) 174 | 175 | @dissoku.command(name="activate", aliases=Activate_aliases) 176 | @commands.has_permissions(manage_messages=True) 177 | async def dissoku_activate(self, ctx): 178 | if self.bot.guild_settings[ctx.guild.id]["do_dissoku_alert"]: 179 | e = discord.Embed(title=get_txt(ctx.guild.id, "activate_fail"), color=Error) 180 | return await ctx.reply(embed=e) 181 | else: 182 | self.bot.guild_settings[ctx.guild.id]["do_dissoku_alert"] = True 183 | e = discord.Embed( 184 | title=get_txt(ctx.guild.id, "activate").format("ディス速通知"), 185 | color=Success, 186 | ) 187 | return await ctx.reply(embed=e) 188 | 189 | @dissoku.command(name="deactivate", aliases=Deactivate_aliases) 190 | @commands.has_permissions(manage_messages=True) 191 | async def dissoku_deactivate(self, ctx): 192 | if not self.bot.guild_settings[ctx.guild.id]["do_dissoku_alert"]: 193 | e = discord.Embed(title=get_txt(ctx.guild.id, "deactivate_fail"), color=Error) 194 | return await ctx.reply(embed=e) 195 | else: 196 | self.bot.guild_settings[ctx.guild.id]["do_dissoku_alert"] = False 197 | e = discord.Embed( 198 | title=get_txt(ctx.guild.id, "deactivate").format("ディス速通知"), 199 | color=Success, 200 | ) 201 | return await ctx.reply(embed=e) 202 | 203 | @dissoku.command(name="role") 204 | @commands.has_permissions(manage_messages=True) 205 | async def dissoku_role(self, ctx, role: Optional[discord.Role] = None): 206 | if role: 207 | self.bot.guild_settings[ctx.guild.id]["dissoku_role"] = role.id 208 | e = discord.Embed(title=get_txt(ctx.guild.id, "dissoku_role_set"), color=Success) 209 | else: 210 | self.bot.guild_settings[ctx.guild.id]["dissoku_role"] = None 211 | e = discord.Embed( 212 | title=get_txt(ctx.guild.id, "dissoku_role_set_none"), 213 | color=Success, 214 | ) 215 | return await ctx.reply(embed=e) 216 | 217 | def cog_unload(self): 218 | self.batch_bump_alert.stop() 219 | 220 | 221 | def setup(_bot): 222 | global bot 223 | bot = _bot 224 | _bot.add_cog(BumpCog(_bot), override=True) 225 | -------------------------------------------------------------------------------- /cogs/channel_settings.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | 4 | import _pathmagic # type: ignore # noqa 5 | from common_resources.consts import Activate_aliases, Deactivate_aliases, Error, Success 6 | 7 | 8 | class ChannelSettingCog(commands.Cog): 9 | def __init__(self, bot): 10 | global Texts 11 | global get_txt 12 | self.bot: commands.Bot = bot 13 | self.bot.guild_settings = bot.guild_settings 14 | Texts = bot.texts 15 | get_txt = bot.get_txt 16 | 17 | @commands.group(name="channel_settings", aliases=["ch"]) 18 | @commands.has_permissions(manage_channels=True) 19 | async def channel_setting(self, ctx): 20 | if ctx.invoked_subcommand is None: 21 | await self.bot.send_subcommands(ctx) 22 | 23 | @channel_setting.command(name="slowmode", aliases=["cooldown"]) 24 | @commands.has_permissions(manage_channels=True) 25 | async def ch_slowmode(self, ctx, sec: int): 26 | await ctx.channel.edit(slowmode_delay=sec) 27 | e = discord.Embed( 28 | title=get_txt(ctx.guild.id, "ch")["slowmode"][0].format(sec), 29 | color=Success, 30 | ) 31 | return await ctx.reply(embed=e) 32 | 33 | @channel_setting.group(name="commands", aliases=["cmd"]) 34 | @commands.has_permissions(manage_channels=True) 35 | async def ch_commands(self, ctx): 36 | if ctx.invoked_subcommand is None: 37 | await self.bot.send_subcommands(ctx) 38 | else: 39 | pass 40 | 41 | @channel_setting.group(name="auto_parse") 42 | @commands.has_permissions(manage_channels=True) 43 | async def ch_auto_parse(self, ctx): 44 | if ctx.invoked_subcommand is None: 45 | await self.bot.send_subcommands(ctx) 46 | else: 47 | pass 48 | 49 | @ch_auto_parse.command(name="deactivate", aliases=Deactivate_aliases) 50 | @commands.has_permissions(manage_channels=True) 51 | async def deactivate_auto_parse(self, ctx): 52 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["auto_parse"]: 53 | e = discord.Embed(title="既に無効化されています。", description="", color=Error) 54 | return await ctx.reply(embed=e) 55 | else: 56 | self.bot.guild_settings[ctx.guild.id]["auto_parse"].remove(ctx.channel.id) 57 | e = discord.Embed( 58 | title=f"`#{ctx.channel.name}`での自動パースを無効にしました。", 59 | description="", 60 | color=Success, 61 | ) 62 | return await ctx.reply(embed=e) 63 | 64 | @ch_auto_parse.command(name="activate", aliases=Activate_aliases) 65 | @commands.has_permissions(manage_channels=True) 66 | async def activate_auto_parse(self, ctx): 67 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["auto_parse"]: 68 | self.bot.guild_settings[ctx.guild.id]["auto_parse"].append(ctx.channel.id) 69 | e = discord.Embed( 70 | title=f"`#{ctx.channel.name}`での自動パースを有効にしました。", 71 | description="", 72 | color=Success, 73 | ) 74 | return await ctx.reply(embed=e) 75 | else: 76 | e = discord.Embed(title="既に有効です。", description="", color=Error) 77 | return await ctx.reply(embed=e) 78 | 79 | @channel_setting.group(name="2ch_link") 80 | @commands.has_permissions(manage_channels=True) 81 | async def ch_2ch_link(self, ctx): 82 | if ctx.invoked_subcommand is None: 83 | await self.bot.send_subcommands(ctx) 84 | else: 85 | pass 86 | 87 | @ch_2ch_link.command(name="deactivate", aliases=Deactivate_aliases) 88 | @commands.has_permissions(manage_channels=True) 89 | async def deactivate_2ch_link(self, ctx): 90 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["2ch_link"]: 91 | e = discord.Embed(title="既に無効化されています。", description="", color=Error) 92 | return await ctx.reply(embed=e) 93 | else: 94 | self.bot.guild_settings[ctx.guild.id]["2ch_link"].remove(ctx.channel.id) 95 | e = discord.Embed( 96 | title=f"`#{ctx.channel.name}`での2ch風メッセージリンクを無効にしました。", 97 | description="", 98 | color=Success, 99 | ) 100 | return await ctx.reply(embed=e) 101 | 102 | @ch_2ch_link.command(name="activate", aliases=Activate_aliases) 103 | @commands.has_permissions(manage_channels=True) 104 | async def activate_2ch_link(self, ctx): 105 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["2ch_link"]: 106 | self.bot.guild_settings[ctx.guild.id]["2ch_link"].append(ctx.channel.id) 107 | e = discord.Embed( 108 | title=f"`#{ctx.channel.name}`での2ch風メッセージリンクを有効にしました。", 109 | description="", 110 | color=Success, 111 | ) 112 | return await ctx.reply(embed=e) 113 | else: 114 | e = discord.Embed(title="既に有効です。", description="", color=Error) 115 | return await ctx.reply(embed=e) 116 | 117 | @channel_setting.group(name="lainan_talk", invoke_without_command=True) 118 | @commands.has_permissions(manage_channels=True) 119 | async def ch_lainan_talk(self, ctx): 120 | await self.bot.send_subcommands(ctx) 121 | 122 | @ch_lainan_talk.command(name="deactivate", aliases=Deactivate_aliases) 123 | @commands.has_permissions(manage_channels=True) 124 | async def deactivate_lainan_talk(self, ctx): 125 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["lainan_talk"]: 126 | e = discord.Embed(title="既に無効化されています。", description="", color=Error) 127 | return await ctx.reply(embed=e) 128 | else: 129 | self.bot.guild_settings[ctx.guild.id]["lainan_talk"].remove(ctx.channel.id) 130 | e = discord.Embed( 131 | title=f"`#{ctx.channel.name}`でのLainan APIの返信を無効にしました。", 132 | description="", 133 | color=Success, 134 | ) 135 | return await ctx.reply(embed=e) 136 | 137 | @ch_lainan_talk.command(name="activate", aliases=Activate_aliases) 138 | @commands.has_permissions(manage_channels=True) 139 | async def activate_lainan_talk(self, ctx): 140 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["lainan_talk"]: 141 | self.bot.guild_settings[ctx.guild.id]["lainan_talk"].append(ctx.channel.id) 142 | e = discord.Embed( 143 | title=f"`#{ctx.channel.name}`でのLainan APIの返信を有効にしました。", 144 | description="", 145 | color=Success, 146 | ) 147 | return await ctx.reply(embed=e) 148 | else: 149 | e = discord.Embed(title="既に有効です。", description="", color=Error) 150 | return await ctx.reply(embed=e) 151 | 152 | @ch_commands.command(name="deactivate", aliases=Deactivate_aliases) 153 | @commands.has_permissions(manage_channels=True) 154 | async def deactivate_command(self, ctx): 155 | if ctx.channel.id in self.bot.guild_settings[ctx.guild.id]["deactivate_command"]: 156 | e = discord.Embed(title="既に無効化されています。", description="", color=Error) 157 | return await ctx.reply(embed=e) 158 | else: 159 | self.bot.guild_settings[ctx.guild.id]["deactivate_command"].append(ctx.channel.id) 160 | e = discord.Embed( 161 | title=f"`#{ctx.channel.name}`での管理者以外のコマンドを無効にしました。", 162 | description="", 163 | color=Success, 164 | ) 165 | return await ctx.reply(embed=e) 166 | 167 | @ch_commands.command(name="activate", aliases=Activate_aliases) 168 | @commands.has_permissions(manage_channels=True) 169 | async def activate_command(self, ctx): 170 | if ctx.channel.id in self.bot.guild_settings[ctx.guild.id]["deactivate_command"]: 171 | self.bot.guild_settings[ctx.guild.id]["deactivate_command"].remove(ctx.channel.id) 172 | e = discord.Embed( 173 | title=f"`#{ctx.channel.name}`でのコマンドを有効にしました。", 174 | description="", 175 | color=Success, 176 | ) 177 | return await ctx.reply(embed=e) 178 | else: 179 | e = discord.Embed(title="既に有効です。", description="", color=Error) 180 | return await ctx.reply(embed=e) 181 | 182 | @channel_setting.group(name="level") 183 | @commands.has_permissions(manage_channels=True) 184 | async def ch_level(self, ctx): 185 | if ctx.invoked_subcommand is None: 186 | await self.bot.send_subcommands(ctx) 187 | else: 188 | pass 189 | 190 | @ch_level.command(name="activate", aliases=Activate_aliases) 191 | @commands.has_permissions(manage_channels=True) 192 | async def ch_level_activate(self, ctx): 193 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["level_ignore_channel"]: 194 | e = discord.Embed(title="既に有効です。", color=Error) 195 | return await ctx.reply(embed=e) 196 | else: 197 | self.bot.guild_settings[ctx.guild.id]["level_ignore_channel"].remove(ctx.channel.id) 198 | e = discord.Embed(title="このチャンネルでのレベリングが有効になりました。", description="", color=Success) 199 | return await ctx.reply(embed=e) 200 | 201 | @ch_level.command(name="deactivate", aliases=Deactivate_aliases) 202 | @commands.has_permissions(manage_channels=True) 203 | async def ch_level_deactivate(self, ctx): 204 | if ctx.channel.id in self.bot.guild_settings[ctx.guild.id]["level_ignore_channel"]: 205 | e = discord.Embed(title="既に無効です。", description="", color=Error) 206 | return await ctx.reply(embed=e) 207 | else: 208 | self.bot.guild_settings[ctx.guild.id]["level_ignore_channel"].append(ctx.channel.id) 209 | e = discord.Embed(title="このチャンネルでのレベリングが無効になりました。", description="", color=Success) 210 | return await ctx.reply(embed=e) 211 | 212 | @channel_setting.group(name="translate", aliases=["trans"]) 213 | @commands.has_permissions(manage_channels=True) 214 | async def ch_trans(self, ctx): 215 | if ctx.invoked_subcommand is None: 216 | await self.bot.send_subcommands(ctx) 217 | else: 218 | pass 219 | 220 | @ch_trans.command(name="activate", aliases=Activate_aliases) 221 | @commands.has_permissions(manage_channels=True) 222 | async def activate_translate(self, ctx, lang): 223 | if ctx.channel.id in list(self.bot.guild_settings[ctx.guild.id]["trans_channel"].keys()): 224 | e = discord.Embed(title="既に有効です。", color=Error) 225 | return await ctx.reply(embed=e) 226 | else: 227 | self.bot.guild_settings[ctx.guild.id]["trans_channel"][ctx.channel.id] = lang 228 | e = discord.Embed(title="自動翻訳が有効になりました。", description="", color=Success) 229 | return await ctx.reply(embed=e) 230 | 231 | @ch_trans.command(name="deactivate", aliases=Deactivate_aliases) 232 | @commands.has_permissions(manage_channels=True) 233 | async def deactivate_translate(self, ctx): 234 | if ctx.channel.id not in list(self.bot.guild_settings[ctx.guild.id]["trans_channel"].keys()): 235 | e = discord.Embed(title="既に無効です。", description="", color=Error) 236 | return await ctx.reply(embed=e) 237 | else: 238 | del self.bot.guild_settings[ctx.guild.id]["trans_channel"][ctx.channel.id] 239 | e = discord.Embed(title="自動翻訳が無効になりました。", description="", color=Success) 240 | return await ctx.reply(embed=e) 241 | 242 | @channel_setting.group(name="auto_publish") 243 | @commands.has_permissions(manage_channels=True) 244 | async def ch_autopub(self, ctx): 245 | if ctx.invoked_subcommand is None: 246 | await self.bot.send_subcommands(ctx) 247 | else: 248 | pass 249 | 250 | @ch_autopub.command(name="activate", aliases=Activate_aliases) 251 | @commands.has_permissions(manage_channels=True) 252 | async def activate_autopub(self, ctx): 253 | if ctx.channel.id in self.bot.guild_settings[ctx.guild.id]["autopub"]: 254 | e = discord.Embed(title="既に有効です。", color=Error) 255 | return await ctx.reply(embed=e) 256 | elif ctx.channel.type == discord.ChannelType.news: 257 | self.bot.guild_settings[ctx.guild.id]["autopub"].append(ctx.channel.id) 258 | e = discord.Embed(title="自動公開が有効になりました。", color=Success) 259 | return await ctx.reply(embed=e) 260 | else: 261 | e = discord.Embed(title="自動公開はアナウンスチャンネルでのみ使用できます。", color=Error) 262 | return await ctx.reply(embed=e) 263 | 264 | @ch_autopub.command(name="deactivate", aliases=Deactivate_aliases) 265 | @commands.has_permissions(manage_channels=True) 266 | async def deactivate_autopub(self, ctx): 267 | if ctx.channel.id not in self.bot.guild_settings[ctx.guild.id]["autopub"]: 268 | e = discord.Embed(title="既に無効です。", color=Error) 269 | return await ctx.reply(embed=e) 270 | else: 271 | self.bot.guild_settings[ctx.guild.id]["autopub"].remove(ctx.channel.id) 272 | e = discord.Embed(title="自動公開が無効になりました。", color=Success) 273 | return await ctx.reply(embed=e) 274 | 275 | 276 | def setup(_bot): 277 | global bot 278 | bot = _bot 279 | _bot.add_cog(ChannelSettingCog(_bot), override=True) 280 | -------------------------------------------------------------------------------- /cogs/expand_message.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import discord 4 | from discord.ext import commands, components 5 | 6 | import _pathmagic # type: ignore # noqa 7 | from common_resources.consts import ( 8 | Activate_aliases, 9 | Chat, 10 | Deactivate_aliases, 11 | Error, 12 | Success, 13 | ) 14 | 15 | Message_url_re = re.compile( 16 | r"(?)" 17 | ) 18 | 19 | 20 | class MessageExpandCog(commands.Cog): 21 | def __init__(self, bot): 22 | global Texts, GBan 23 | global get_txt 24 | self.bot: commands.Bot = bot 25 | self.bot.guild_settings = bot.guild_settings 26 | GBan = bot.raw_config["gb"] 27 | Texts = bot.texts 28 | get_txt = bot.get_txt 29 | 30 | @commands.Cog.listener("on_message") 31 | async def on_message_message_expand(self, message): 32 | if message.author.id in GBan.keys(): 33 | return 34 | if not self.bot.is_ready(): 35 | return 36 | if message.guild is None: 37 | return 38 | if message.guild.id not in self.bot.guild_settings.keys(): 39 | return 40 | if message.author.bot: 41 | return 42 | if ( 43 | re.match(Message_url_re, message.content) is not None 44 | and self.bot.guild_settings[message.guild.id]["expand_message"] 45 | ): 46 | rids = re.match(Message_url_re, message.content) 47 | ids = [int(i) for i in [rids[1], rids[2], rids[3]]] 48 | flag = ids[0] == message.guild.id 49 | if not flag: 50 | try: 51 | flag = bool(self.bot.get_guild(ids[0]).get_member(message.author.id)) 52 | except AttributeError: 53 | flag = False 54 | if flag: 55 | c = self.bot.get_channel(ids[1]) 56 | if c is None: 57 | e = discord.Embed( 58 | title=get_txt(message.guild.id, "expand_message_fail"), 59 | description="", 60 | color=Error, 61 | ) 62 | await message.channel.send(embed=e) 63 | try: 64 | try: 65 | m = await c.fetch_message(ids[2]) 66 | except (discord.NotFound, discord.Forbidden): 67 | return 68 | mc = m.content 69 | if not (m.guild == message.guild or m.author == message.author): 70 | return 71 | if len(mc.splitlines()) > 10: 72 | mc = "\n".join(mc.splitlines()[:10]) + "\n..." 73 | if len(mc) > 1000: 74 | mc = mc[:1000] + "..." 75 | em = discord.Embed(color=Chat, description=mc, timestamp=m.created_at) 76 | try: 77 | em.set_image(url=m.attachments[0].url) 78 | except BaseException: 79 | pass 80 | em.set_author( 81 | name=m.author.display_name, 82 | icon_url=getattr(m.author.display_avatar, "url", discord.Embed.Empty), 83 | ) 84 | footer = Texts[self.bot.guild_settings[message.guild.id]["lang"]]["expand_message_footer"].format( 85 | m.channel 86 | ) 87 | if not ids[0] == message.guild.id: 88 | footer = ( 89 | Texts[self.bot.guild_settings[message.guild.id]["lang"]]["expand_message_footer3"].format( 90 | m.guild.name 91 | ) 92 | + footer 93 | ) 94 | if m.embeds == []: 95 | em.set_footer( 96 | text=footer, 97 | icon_url=getattr( 98 | self.bot.get_guild(ids[0]).icon, 99 | "url", 100 | discord.Embed.Empty, 101 | ), 102 | ) 103 | await message.reply(embed=em) 104 | else: 105 | em.set_footer( 106 | text=footer 107 | + Texts[self.bot.guild_settings[message.guild.id]["lang"]]["expand_message_footer2"].format( 108 | len(m.embeds) 109 | ), 110 | icon_url=getattr( 111 | self.bot.get_guild(ids[0]).icon, 112 | "url", 113 | discord.Embed.Empty, 114 | ), 115 | ) 116 | await components.reply(message, embeds=[em] + m.embeds[:9]) 117 | except Exception as er: 118 | e = discord.Embed( 119 | title=get_txt(message.guild.id, "expand_message_fail"), 120 | description="", 121 | color=Error, 122 | ) 123 | await message.reply(str(er), embed=e) 124 | 125 | @commands.group() 126 | @commands.has_guild_permissions(manage_messages=True) 127 | async def expand_message(self, ctx): 128 | if ctx.invoked_subcommand is None: 129 | await self.bot.send_subcommands(ctx) 130 | 131 | @expand_message.command(name="activate", aliases=Activate_aliases) 132 | @commands.has_guild_permissions(manage_messages=True) 133 | async def expand_activate(self, ctx): 134 | gi = ctx.guild.id 135 | if self.bot.guild_settings[ctx.guild.id]["expand_message"]: 136 | e = discord.Embed( 137 | title=get_txt(gi, "activate_fail"), 138 | description=Texts[self.bot.guild_settings[gi]["lang"]]["activate_desc"].format( 139 | "sb#expand_message deactivate" 140 | ), 141 | color=Error, 142 | ) 143 | return await ctx.reply(embed=e) 144 | else: 145 | self.bot.guild_settings[ctx.guild.id]["expand_message"] = True 146 | e = discord.Embed( 147 | title=get_txt(gi, "activate").format("メッセージ展開"), 148 | description=get_txt(gi, "activate_desc").format("sb#expand_message deactivate"), 149 | color=Success, 150 | ) 151 | return await ctx.reply(embed=e) 152 | 153 | @expand_message.command(name="deactivate", aliases=Deactivate_aliases) 154 | @commands.has_guild_permissions(manage_messages=True) 155 | async def expand_deactivate(self, ctx): 156 | gi = ctx.guild.id 157 | if not self.bot.guild_settings[ctx.guild.id]["expand_message"]: 158 | e = discord.Embed( 159 | title=get_txt(gi, "deactivate_fail"), 160 | description=get_txt(ctx.guild.id, "deactivate_desc").format("sb#expand_message activate"), 161 | color=Error, 162 | ) 163 | return await ctx.reply(embed=e) 164 | else: 165 | self.bot.guild_settings[ctx.guild.id]["expand_message"] = False 166 | e = discord.Embed( 167 | title=get_txt(gi, "deactivate").format("メッセージ展開"), 168 | description=get_txt(gi, "deactivate_desc").format("sb#expand_message activate"), 169 | color=Success, 170 | ) 171 | return await ctx.reply(embed=e) 172 | 173 | 174 | def setup(_bot): 175 | global bot 176 | bot = _bot 177 | _bot.add_cog(MessageExpandCog(_bot), override=True) 178 | -------------------------------------------------------------------------------- /cogs/lock_message.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import discord 4 | import sembed 5 | from discord.ext import commands 6 | 7 | import _pathmagic # type: ignore # noqa 8 | from common_resources.consts import ( 9 | Activate_aliases, 10 | Chat, 11 | Deactivate_aliases, 12 | Error, 13 | Success, 14 | ) 15 | 16 | 17 | class LockMessageCog(commands.Cog): 18 | def __init__(self, bot): 19 | global Texts 20 | global get_txt 21 | self.bot: commands.Bot = bot 22 | self.working = set() 23 | Texts = bot.texts 24 | get_txt = bot.get_txt 25 | 26 | @commands.group(aliases=["lm"], invoke_without_command=True) 27 | async def lock_message(self, ctx): 28 | await self.bot.send_subcommands(ctx) 29 | 30 | @lock_message.command(name="activate", aliases=Activate_aliases + ["register"]) 31 | @commands.has_permissions(manage_messages=True) 32 | async def lock_message_activate(self, ctx, *, content): 33 | self.bot.guild_settings[ctx.guild.id]["lock_message_content"][ctx.channel.id] = { 34 | "content": content, 35 | "author": ctx.author.id, 36 | } 37 | await ctx.reply( 38 | embed=discord.Embed( 39 | title="メッセージを固定しました。", 40 | description=f"```\n{content}\n``` が最下部に表示されます。", 41 | color=Success, 42 | ) 43 | ) 44 | 45 | @lock_message.command(name="deactivate", aliases=Deactivate_aliases) 46 | @commands.has_permissions(manage_messages=True) 47 | async def lock_message_deactivate(self, ctx): 48 | try: 49 | del self.bot.guild_settings[ctx.guild.id]["lock_message_content"][ctx.channel.id] 50 | except KeyError: 51 | await ctx.reply( 52 | embed=discord.Embed( 53 | title="固定されているメッセージはありません。", 54 | color=Error, 55 | ) 56 | ) 57 | else: 58 | await ctx.reply( 59 | embed=discord.Embed( 60 | title="メッセージの固定を解除しました。", 61 | color=Success, 62 | ) 63 | ) 64 | 65 | @commands.Cog.listener() 66 | async def on_message(self, message: discord.Message): 67 | if message.guild is None: 68 | return 69 | if message.channel.id in self.working: 70 | return 71 | if message.author == self.bot.user: 72 | if embeds := message.embeds: 73 | if embeds[0].author.url and "?locked" in embeds[0].author.url: 74 | return 75 | if content := self.bot.guild_settings[message.guild.id]["lock_message_content"].get(message.channel.id): 76 | self.working.add(message.channel.id) 77 | if message_id := self.bot.guild_settings[message.guild.id]["lock_message_id"].get(message.channel.id): 78 | if time.time() - discord.Object(message_id).created_at.timestamp() < 10: 79 | return self.working.remove(message.channel.id) 80 | try: 81 | await discord.PartialMessage(channel=message.channel, id=message_id).delete() 82 | except discord.NotFound: 83 | pass 84 | author = message.guild.get_member(content["author"]) 85 | if author is None: 86 | del self.bot.guild_settings[message.guild.id]["lock_message_content"][message.channel.id] 87 | self.working.remove(message.channel.id) 88 | return 89 | msg = await message.channel.send( 90 | embed=sembed.SEmbed( 91 | description=content["content"], 92 | color=Chat, 93 | author=sembed.SAuthor(name=author.display_name, icon_url=author.display_avatar.url + "?locked"), 94 | ) 95 | ) 96 | self.bot.guild_settings[message.guild.id]["lock_message_id"][message.channel.id] = msg.id 97 | self.working.remove(message.channel.id) 98 | 99 | 100 | def setup(_bot): 101 | global bot 102 | bot = _bot 103 | _bot.add_cog(LockMessageCog(_bot), override=True) 104 | -------------------------------------------------------------------------------- /cogs/panel.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | import discord 5 | from discord.ext import commands 6 | 7 | import _pathmagic # type: ignore # noqa 8 | from common_resources.consts import Error, Success, Widget 9 | from common_resources.tools import convert_timedelta 10 | 11 | 12 | class PanelCog(commands.Cog): 13 | def __init__(self, bot): 14 | global Texts, Number_emojis 15 | global get_txt 16 | self.bot: commands.Bot = bot 17 | self.bot.guild_settings = bot.guild_settings 18 | Texts = bot.texts 19 | get_txt = bot.get_txt 20 | Number_emojis = [] 21 | for i in range(11): 22 | Number_emojis.append(self.bot.oemojis["b" + str(i)]) 23 | 24 | @commands.command(aliases=["poll"]) 25 | async def vote( 26 | self, 27 | ctx, 28 | title, 29 | time: Optional[convert_timedelta], 30 | multi: Optional[bool], 31 | *select, 32 | ): 33 | if multi is None: 34 | multi = True 35 | if time is None: 36 | time = datetime.timedelta(hours=1) 37 | if len(select) > 10: 38 | g = ctx.guild 39 | e = discord.Embed( 40 | title=get_txt(g.id, "vote_error")[0], 41 | description=get_txt(g.id, "vote_error")[0], 42 | color=Error, 43 | ) 44 | return await ctx.reply(embed=e) 45 | dt = discord.utils.utcnow() 46 | g = ctx.guild 47 | dt += time 48 | e = discord.Embed(title=get_txt(g.id, "voting")[0], color=Widget, timestamp=dt) 49 | e.add_field( 50 | name=Texts[self.bot.guild_settings[g.id]["lang"]]["voting"][1], 51 | value=title, 52 | inline=False, 53 | ) 54 | e.add_field( 55 | name=get_txt(g.id, "voting")[2], 56 | value=( 57 | Texts[self.bot.guild_settings[g.id]["lang"]]["voting"][3][0] if multi else get_txt(g.id, "voting")[3][1] 58 | ), 59 | inline=False, 60 | ) 61 | s = "" 62 | for sfi, sf in enumerate(select): 63 | em = self.bot.oemojis["b" + str(sfi + 1)] 64 | s += f":black_small_square:|{em}:{sf}\n" 65 | e.add_field( 66 | name=Texts[self.bot.guild_settings[g.id]["lang"]]["voting"][4], 67 | value=s, 68 | inline=False, 69 | ) 70 | e.set_author( 71 | name=f"{ctx.author.display_name}(ID:{ctx.author.id})", 72 | icon_url=ctx.author.display_avatar.url, 73 | ) 74 | e.set_footer(text=get_txt(ctx.guild.id, "voting")[5]) 75 | m = await ctx.reply(embed=e) 76 | for sfi in range(len(select)): 77 | await m.add_reaction(Number_emojis[sfi + 1]) 78 | 79 | @commands.command(aliases=["recruit", "apply"]) 80 | async def party(self, ctx, title, time: Optional[convert_timedelta], max: int): 81 | if time is None: 82 | time = datetime.timedelta(hours=1) 83 | dt = discord.utils.utcnow() 84 | dt += time 85 | e = discord.Embed(title="募集", color=Widget, timestamp=dt) 86 | e.add_field(name="タイトル", value=title, inline=False) 87 | e.add_field(name="最大人数", value=f"{max}人", inline=False) 88 | e.add_field(name="参加者(現在0人)", value="現在参加者はいません", inline=False) 89 | e.set_author( 90 | name=f"{ctx.author}(ID:{ctx.author.id})", 91 | icon_url=ctx.author.display_avatar.url, 92 | ) 93 | e.set_footer(text=get_txt(ctx.guild.id, "voting")[5]) 94 | m = await ctx.reply(embed=e) 95 | await m.add_reaction(self.bot.oemojis["check5"]) 96 | await m.add_reaction(self.bot.oemojis["check6"]) 97 | 98 | @commands.command(name="lang") 99 | @commands.has_guild_permissions(manage_guild=True) 100 | async def change_lang(self, ctx, lang_to): 101 | if lang_to not in Texts.keys(): 102 | e = discord.Embed( 103 | title=get_txt(ctx.guild.id, "change_lang")[0][0], 104 | description=get_txt(ctx.guild.id, "change_lang")[0][1].format(lang_to), 105 | color=Error, 106 | ) 107 | return await ctx.reply(embed=e) 108 | self.bot.guild_settings[ctx.guild.id]["lang"] = lang_to 109 | e = discord.Embed( 110 | title=get_txt(ctx.guild.id, "change_lang")[1][0], 111 | description=get_txt(ctx.guild.id, "change_lang")[1][1].format(lang_to), 112 | color=Success, 113 | ) 114 | return await ctx.reply(embed=e) 115 | 116 | @commands.command(name="top") 117 | @commands.has_permissions(manage_messages=True) 118 | async def top(self, ctx: commands.Context, *, text="最上部へ移動"): 119 | await ctx.send( 120 | embed=discord.Embed( 121 | title=text, 122 | color=Widget, 123 | url=(await ctx.history(limit=1, oldest_first=True).flatten())[0].jump_url, 124 | ) 125 | ) 126 | 127 | 128 | def setup(_bot): 129 | global bot 130 | bot = _bot 131 | _bot.add_cog(PanelCog(_bot), override=True) 132 | -------------------------------------------------------------------------------- /cogs/role_link.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.errors import NotFound 3 | from discord.ext import commands 4 | from texttable import Texttable 5 | 6 | import _pathmagic # type: ignore # noqa 7 | from common_resources.consts import Error, Info, Success 8 | from common_resources.tools import remove_emoji 9 | 10 | 11 | class RoleLinkCog(commands.Cog): 12 | def __init__(self, bot): 13 | global Texts 14 | global get_txt 15 | self.bot: commands.Bot = bot 16 | self.bot.guild_settings = bot.guild_settings 17 | Texts = bot.texts 18 | get_txt = bot.get_txt 19 | 20 | @commands.Cog.listener() 21 | async def on_member_update(self, before, after): 22 | if not self.bot.guild_settings.get(after.guild.id): 23 | return 24 | for r in after.roles: 25 | if r.id in self.bot.guild_settings[after.guild.id]["role_link"].keys(): 26 | for rl in self.bot.guild_settings[after.guild.id]["role_link"][r.id]: 27 | try: 28 | await self.bot.get_guild(rl[0]).get_member(after.id).add_roles( 29 | self.bot.get_guild(rl[0]).get_role(rl[1]), 30 | reason=get_txt(after.guild.id, "role_link")["reason"].format( 31 | self.bot.get_guild(rl[0]).name, 32 | self.bot.get_guild(rl[0]).get_role(rl[1]).name, 33 | ), 34 | ) 35 | except AttributeError: 36 | pass 37 | 38 | @commands.group(aliases=["rl"]) 39 | @commands.has_guild_permissions(manage_roles=True) 40 | async def role_link(self, ctx): 41 | if ctx.invoked_subcommand is None: 42 | await self.bot.send_subcommands(ctx) 43 | 44 | @role_link.command(name="add", aliases=["set"]) 45 | async def role_link_add(self, ctx, role: discord.Role, target: int, target_role: int): 46 | target = bot.get_guild(target) 47 | if not target: 48 | e = discord.Embed( 49 | title=get_txt(ctx.guild.id, "role_link")["unknown_server"], 50 | color=Error, 51 | ) 52 | return await ctx.reply(embed=e) 53 | if target.get_member(ctx.author.id): 54 | if not target.get_member(ctx.author.id).guild_permissions.manage_roles: 55 | e = discord.Embed( 56 | title=get_txt(ctx.guild.id, "role_link")["no_role_perm"], 57 | color=Error, 58 | ) 59 | return await ctx.reply(embed=e) 60 | else: 61 | e = discord.Embed( 62 | title=get_txt(ctx.guild.id, "role_link")["not_in_server"], 63 | color=Error, 64 | ) 65 | return await ctx.reply(embed=e) 66 | target_role = target.get_role(target_role) 67 | if not target_role: 68 | e = discord.Embed( 69 | title=get_txt(ctx.guild.id, "role_link")["unknown_role"], 70 | color=Error, 71 | ) 72 | return await ctx.reply(embed=e) 73 | elif ( 74 | target.get_member(ctx.author.id).top_role.position <= target_role.position 75 | and not target.owner_id == ctx.author.id 76 | ): 77 | e = discord.Embed( 78 | title=get_txt(ctx.guild.id, "role_link")["no_role_perm"], 79 | color=Error, 80 | ) 81 | return await ctx.reply(embed=e) 82 | if role.id not in self.bot.guild_settings[ctx.guild.id]["role_link"].keys(): 83 | self.bot.guild_settings[ctx.guild.id]["role_link"][role.id] = [] 84 | self.bot.guild_settings[ctx.guild.id]["role_link"][role.id].append([target.id, target_role.id]) 85 | if target_role.id not in self.bot.guild_settings[target.id]["role_link"].keys(): 86 | self.bot.guild_settings[target.id]["role_link"][target_role.id] = [] 87 | self.bot.guild_settings[target.id]["role_link"][target_role.id].append([ctx.guild.id, role.id]) 88 | e = discord.Embed( 89 | title=get_txt(ctx.guild.id, "role_link")["add"].format(role.name), 90 | description=get_txt(ctx.guild.id, "role_link")["add_desc"].format( 91 | ctx.guild.name, 92 | role.mention, 93 | target.name, 94 | "@" + target_role.name, 95 | ), 96 | color=Success, 97 | ) 98 | return await ctx.reply(embed=e) 99 | 100 | @role_link.command(name="remove", aliases=["del", "delete", "rem"]) 101 | async def role_link_remove(self, ctx, role: discord.Role, target_role: int): 102 | res = [] 103 | g = 0 104 | for rl in self.bot.guild_settings[ctx.guild.id]["role_link"][role.id]: 105 | if rl[1] != target_role: 106 | res.append(rl) 107 | else: 108 | g = rl[0] 109 | if g == 0: 110 | e = discord.Embed( 111 | title=get_txt(ctx.guild.id, "role_link")["remove_fail"], 112 | color=Error, 113 | ) 114 | return 115 | self.bot.guild_settings[ctx.guild.id]["role_link"][role.id] = res.copy() 116 | res = [] 117 | for rl in self.bot.guild_settings[g]["role_link"][target_role]: 118 | if rl[1] != role.id: 119 | res.append(rl) 120 | else: 121 | g = rl[0] 122 | self.bot.guild_settings[g]["role_link"][role.id] = res.copy() 123 | e = discord.Embed( 124 | title=get_txt(ctx.guild.id, "role_link")["remove"].format(role.name), 125 | color=Success, 126 | ) 127 | return await ctx.reply(embed=e) 128 | 129 | @role_link.command(name="update") 130 | async def role_link_update(self, ctx): 131 | for m in ctx.guild.members: 132 | for r in m.roles: 133 | if r.id in self.bot.guild_settings[m.guild.id]["role_link"].keys(): 134 | for rl in self.bot.guild_settings[m.guild.id]["role_link"][r.id]: 135 | try: 136 | await self.bot.get_guild(rl[0]).get_member(m.id).add_roles( 137 | self.bot.get_guild(rl[0]).get_role(rl[1]), 138 | reason=get_txt(m.guild.id, "role_link")["reason"].format( 139 | self.bot.get_guild(rl[0]).name, 140 | self.bot.get_guild(rl[0]).get_role(rl[1]).name, 141 | ), 142 | ) 143 | except (NotFound, AttributeError): 144 | pass 145 | e = discord.Embed(title=get_txt(ctx.guild.id, "role_link")["update"], color=Success) 146 | return await ctx.reply(embed=e) 147 | 148 | @role_link.command(name="list") 149 | async def role_link_list(self, ctx): 150 | g = ctx.guild.id 151 | 152 | gs = self.bot.guild_settings[g] 153 | if gs["role_link"] == {}: 154 | e = discord.Embed( 155 | title="登録されていません。", 156 | description="`sb#role_link add`で登録してください。", 157 | color=Error, 158 | ) 159 | return await ctx.reply(embed=e) 160 | else: 161 | table = Texttable() 162 | table.set_deco(Texttable.HEADER) 163 | table.set_cols_dtype(["t", "t", "t"]) 164 | table.set_cols_align(["c", "l", "c"]) 165 | res = [["ロール", "サーバー", "ロール"]] 166 | for k, v in gs["role_link"].items(): 167 | for v2 in v: 168 | if ctx.guild.get_role(k) is None: 169 | continue 170 | elif self.bot.get_guild(v2[0]) is None: 171 | continue 172 | elif self.bot.get_guild(v2[0]).get_role(v2[1]) is None: 173 | continue 174 | res.append( 175 | [ 176 | "@" + remove_emoji(ctx.guild.get_role(k).name), 177 | remove_emoji(self.bot.get_guild(v2[0]).name), 178 | "@" + remove_emoji(self.bot.get_guild(v2[0]).get_role(v2[1]).name), 179 | ] 180 | ) 181 | table.add_rows(res) 182 | e = discord.Embed( 183 | title=get_txt(ctx.guild.id, "role_link")["list"], 184 | description=f"```asciidoc\n{table.draw()}```", 185 | color=Info, 186 | ) 187 | return await ctx.reply(embed=e) 188 | 189 | 190 | def setup(_bot): 191 | global bot 192 | bot = _bot 193 | _bot.add_cog(RoleLinkCog(_bot), override=True) 194 | -------------------------------------------------------------------------------- /cogs/server_stats.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import discord 4 | from discord import CategoryChannel 5 | from discord.errors import Forbidden, NotFound 6 | from discord.ext import commands, tasks 7 | 8 | import _pathmagic # type: ignore # noqa 9 | from common_resources.consts import ( 10 | Activate_aliases, 11 | Deactivate_aliases, 12 | Error, 13 | Stat_dict, 14 | Success, 15 | ) 16 | 17 | 18 | class ServerStatCog(commands.Cog): 19 | def __init__(self, bot): 20 | global Texts 21 | global get_txt 22 | self.bot: commands.Bot = bot 23 | self.bot.guild_settings = bot.guild_settings 24 | Texts = bot.texts 25 | get_txt = bot.get_txt 26 | self.batch_update_stat_channel.start() 27 | 28 | @commands.group() 29 | @commands.has_permissions(manage_guild=True) 30 | async def server_stat(self, ctx): 31 | if ctx.invoked_subcommand is None: 32 | await self.bot.send_subcommands(ctx) 33 | 34 | @server_stat.command(name="activate", aliases=Activate_aliases) 35 | @commands.has_permissions(manage_guild=True) 36 | async def ss_activate(self, ctx, *stats): 37 | cat = None 38 | if not stats: 39 | e = discord.Embed( 40 | title="統計が指定されていません。", 41 | description="`sb#help server_stat`で確認してください。", 42 | color=Error, 43 | ) 44 | return await ctx.reply(embed=e) 45 | for stat in stats: 46 | if stat not in Stat_dict.keys(): 47 | e = discord.Embed( 48 | title=f"統計{stat}が見付かりませんでした。", 49 | description="`sb#help server_stat`で確認してください。", 50 | color=Error, 51 | ) 52 | return await ctx.reply(embed=e) 53 | if self.bot.guild_settings[ctx.guild.id]["do_stat_channels"]: 54 | for sc in self.bot.guild_settings[ctx.guild.id]["stat_channels"].values(): 55 | c = self.bot.get_channel(sc) 56 | if isinstance(c, CategoryChannel): 57 | cat = c 58 | continue 59 | if c is not None: 60 | await c.delete() 61 | else: 62 | overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(connect=False)} 63 | cat = await ctx.guild.create_category("統計", overwrites=overwrites) 64 | for stat in stats: 65 | n = await ctx.guild.create_voice_channel(f"{Stat_dict[stat]}: --", category=cat) 66 | self.bot.guild_settings[ctx.guild.id]["stat_channels"][stat] = n.id 67 | self.bot.guild_settings[ctx.guild.id]["stat_channels"]["category"] = cat.id 68 | self.bot.guild_settings[ctx.guild.id]["do_stat_channels"] = True 69 | e = discord.Embed( 70 | title="統計チャンネルが有効になりました。", 71 | description="無効化するには`sb#server_stat deactivate`を実行してください。", 72 | color=Success, 73 | ) 74 | return await ctx.reply(embed=e) 75 | 76 | @server_stat.command(name="deactivate", aliases=Deactivate_aliases) 77 | @commands.has_permissions(manage_guild=True) 78 | async def ss_deactivate(self, ctx): 79 | if self.bot.guild_settings[ctx.guild.id]["do_stat_channels"]: 80 | for sc in self.bot.guild_settings[ctx.guild.id]["stat_channels"].values(): 81 | c = self.bot.get_channel(sc) 82 | if c is not None: 83 | await c.delete() 84 | self.bot.guild_settings[ctx.guild.id]["stat_channels"] = {} 85 | self.bot.guild_settings[ctx.guild.id]["do_stat_channels"] = False 86 | e = discord.Embed( 87 | title="統計チャンネルが無効になりました。", 88 | description="もう一度有効にするには`sb#server_stat activate`を使用してください。", 89 | color=Success, 90 | ) 91 | return await ctx.reply(embed=e) 92 | 93 | @tasks.loop(minutes=5, seconds=10) 94 | async def batch_update_stat_channel(self): 95 | for g in self.bot.guilds: 96 | try: 97 | if self.bot.guild_settings[g.id]["do_stat_channels"]: 98 | for sck, scv in self.bot.guild_settings[g.id]["stat_channels"].items(): 99 | if sck == "category": 100 | continue 101 | s = self.bot.get_channel(scv) 102 | if s is None: 103 | continue 104 | 105 | val = "--" 106 | if sck == "members": 107 | val = g.member_count 108 | elif sck == "humans": 109 | val = len(list(filter(lambda m: not m.bot, g.members))) 110 | elif sck == "bots": 111 | val = len(list(filter(lambda m: m.bot, g.members))) 112 | elif sck == "channels": 113 | val = ( 114 | len(g.text_channels) 115 | + len(g.voice_channels) 116 | - len(self.bot.guild_settings[g.id]["stat_channels"].keys()) 117 | + 1 118 | ) 119 | elif sck == "text_channels": 120 | val = len(g.text_channels) 121 | elif sck == "voice_channels": 122 | val = len(g.voice_channels) - len(self.bot.guild_settings[g.id]["stat_channels"].keys()) + 1 123 | elif sck == "roles": 124 | val = len(g.roles) 125 | else: 126 | try: 127 | await s.delete() 128 | except (NotFound, Forbidden): 129 | pass 130 | 131 | continue 132 | try: 133 | await s.edit(name=f"{Stat_dict[sck]}: {val}") 134 | except (NotFound, Forbidden): 135 | pass 136 | await asyncio.sleep(5) 137 | except Exception: 138 | pass 139 | 140 | def cog_unload(self): 141 | self.batch_update_stat_channel.stop() 142 | 143 | 144 | def setup(_bot): 145 | global bot 146 | bot = _bot 147 | _bot.add_cog(ServerStatCog(_bot), override=True) 148 | -------------------------------------------------------------------------------- /cogs/template.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | import discord 3 | from discord.ext import commands 4 | 5 | import _pathmagic # type: ignore # noqa 6 | from common_resources.consts import ( 7 | Activate_aliases, 8 | Deactivate_aliases, 9 | Error, 10 | Info, 11 | Success, 12 | ) 13 | 14 | 15 | class TemplateCog(commands.Cog): 16 | def __init__(self, bot): 17 | global Texts 18 | global get_txt 19 | self.bot: commands.Bot = bot 20 | self.bot.guild_settings = bot.guild_settings 21 | Texts = bot.texts 22 | get_txt = bot.get_txt 23 | 24 | 25 | def setup(_bot): 26 | global bot 27 | bot = _bot 28 | _bot.add_cog(TemplateCog(_bot), override=True) 29 | -------------------------------------------------------------------------------- /cogs/ticket.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | import discord 5 | from discord.ext import commands, components 6 | from sembed import SEmbed 7 | 8 | import _pathmagic # type: ignore # noqa 9 | from cogs.moderation import delta_to_text 10 | from common_resources.consts import ( # Activate_aliases,; Deactivate_aliases,; Info,; Success, 11 | Error, 12 | Widget, 13 | ) 14 | from common_resources.tools import convert_timedelta 15 | 16 | 17 | class TicketCog(commands.Cog): 18 | def __init__(self, bot): 19 | global Texts 20 | global get_txt 21 | self.bot: commands.Bot = bot 22 | Texts = bot.texts 23 | get_txt = bot.get_txt 24 | 25 | @commands.Cog.listener("on_raw_reaction_add") 26 | async def on_raw_reaction_add(self, pl: discord.RawReactionActionEvent): 27 | if pl.user_id == self.bot.user.id: 28 | return 29 | channel = self.bot.get_channel(pl.channel_id) 30 | try: 31 | message = await channel.fetch_message(pl.message_id) 32 | except (discord.errors.NotFound, discord.errors.Forbidden): 33 | return 34 | guild = self.bot.get_guild(pl.guild_id) 35 | user = guild.get_member(pl.user_id) 36 | if message.embeds == []: 37 | return 38 | if message.author.id != self.bot.user.id: 39 | return 40 | 41 | m0 = message.embeds[0] 42 | if m0.footer.text == "下の南京錠ボタンを押して終了": 43 | if await self.bot.db.tickets.find_one({"message": message.id}) is not None: 44 | await message.remove_reaction(pl.emoji, user) 45 | if pl.emoji.name == "lock": 46 | await self.close_ticket(message, user, guild) 47 | if message.embeds[0].title == "チケット作成": 48 | await message.remove_reaction(pl.emoji, user) 49 | if pl.emoji.name == "add": 50 | success, status, data = await self.create_ticket(message, user, guild) 51 | if not success: 52 | if status == "in_cooldown": 53 | await message.channel.send( 54 | user.mention, 55 | embed=SEmbed( 56 | color=Error, 57 | description=f"作成の上限を超えています。に再度お試しください。", 58 | footer="このメッセージは5秒後に削除されます。", 59 | ), 60 | delete_after=5, 61 | ) 62 | 63 | @commands.Cog.listener("on_button_click") 64 | async def on_button_click(self, com: components.ButtonResponse): 65 | if com.custom_id == "ticket_lock": 66 | await com.defer_update() 67 | await self.close_ticket(com.message, com.guild) 68 | elif com.custom_id == "ticket_create": 69 | success, status, data = await self.create_ticket(com.message, com.fired_by, com.guild) 70 | if not success: 71 | if status == "in_cooldown": 72 | await com.send( 73 | com.fired_by.mention, 74 | embed=SEmbed(color=Error, description=f"作成の上限を超えています。に再度お試しください。"), 75 | hidden=True, 76 | ) 77 | 78 | @commands.command(name="ticket") 79 | @commands.has_guild_permissions(manage_guild=True, manage_channels=True) 80 | async def ticket(self, ctx, subject, description, cooldown: convert_timedelta = datetime.timedelta(hours=1)): 81 | if self.bot.guild_settings[ctx.guild.id]["ticket_category"] == 0: 82 | overwrites = { 83 | ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), 84 | ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), 85 | } 86 | cat = await ctx.guild.create_category("チケット", overwrites=overwrites) 87 | self.bot.guild_settings[ctx.guild.id]["ticket_category"] = cat.id 88 | else: 89 | cat = self.bot.get_channel(self.bot.guild_settings[ctx.guild.id]["ticket_category"]) 90 | e = discord.Embed(title="チケット作成", description=subject, color=Widget) 91 | e.set_footer(text=f"{delta_to_text(cooldown, ctx)}に1回") 92 | m = await components.send( 93 | ctx, 94 | embed=e, 95 | components=[ 96 | components.Button( 97 | label="作成", 98 | style=components.ButtonType.secondary, 99 | custom_id="ticket_create", 100 | ), 101 | ], 102 | ) 103 | await self.bot.db.tickets.insert_one( 104 | { 105 | "message": m.id, 106 | "subject": subject, 107 | "description": description, 108 | "count": 0, 109 | "cooldown": cooldown.total_seconds(), 110 | } 111 | ) 112 | 113 | async def create_ticket(self, message: discord.Message, user: discord.User, guild: discord.Guild): 114 | ticket_data = await self.bot.db.tickets.find_one({"message": message.id}) 115 | if ticket_time := self.bot.consts["ticket_time"].get(f"{guild.id}-{user.id}"): 116 | if time.time() - ticket_time < ticket_data["cooldown"]: 117 | return ( 118 | False, 119 | "in_cooldown", 120 | self.bot.consts["ticket_time"][f"{guild.id}-{user.id}"] + ticket_data["cooldown"], 121 | ) 122 | 123 | self.bot.consts["ticket_time"][f"{guild.id}-{user.id}"] = time.time() 124 | cat = self.bot.get_channel(self.bot.guild_settings[guild.id]["ticket_category"]) 125 | if cat is None: 126 | ow = { 127 | guild.default_role: discord.PermissionOverwrite(read_messages=False), 128 | guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), 129 | } 130 | cat = await guild.create_category_channel(name="チケット", overwrites=ow) 131 | self.bot.guild_settings[guild.id]["ticket_category"] = cat.id 132 | ow = cat.overwrites 133 | ow[user] = discord.PermissionOverwrite(read_messages=True, send_messages=True) 134 | await self.bot.db.tickets.update_one( 135 | {"message": message.id}, 136 | {"$inc": {"count": 1}}, 137 | ) 138 | tc = await guild.create_text_channel( 139 | category=cat, 140 | name=f"チケット#{str(ticket_data['count']+1).zfill(4)}-アクティブ", 141 | overwrites=ow, 142 | ) 143 | e = discord.Embed(title=ticket_data["subject"], description=ticket_data["description"], color=Widget) 144 | e.set_footer(text="下のボタンを押して終了") 145 | message = await components.send( 146 | tc, 147 | user.mention, 148 | embed=e, 149 | components=[components.Button("終了", "ticket_lock", components.ButtonType.danger)], 150 | ) 151 | return True, message, tc 152 | 153 | async def close_ticket(self, message: discord.Message, guild: discord.Guild): 154 | if message.channel.name.endswith("-クローズ"): 155 | return 156 | overwrites = { 157 | guild.default_role: discord.PermissionOverwrite(read_messages=False), 158 | guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), 159 | } 160 | await message.channel.edit(overwrites=overwrites, name=message.channel.name[0:-5] + "クローズ") 161 | await message.channel.send("チケットをクローズしました。") 162 | return True 163 | 164 | 165 | def setup(_bot): 166 | global bot 167 | bot = _bot 168 | _bot.add_cog(TicketCog(_bot), override=True) 169 | -------------------------------------------------------------------------------- /cogs/timed_role.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import discord 5 | from discord.ext import commands, components, tasks 6 | from texttable import Texttable 7 | 8 | import _pathmagic # type: ignore # noqa 9 | from common_resources.consts import Error, Info, Success 10 | from common_resources.tools import chrsize_len, convert_timedelta, remove_emoji, to_lts 11 | 12 | 13 | class TimedRoleCog(commands.Cog): 14 | def __init__(self, bot): 15 | global Texts, Timed_roles 16 | global get_txt 17 | self.bot: commands.Bot = bot 18 | Timed_roles = self.bot.raw_config["tr"] 19 | self.bot.guild_settings = bot.guild_settings 20 | Texts = bot.texts 21 | get_txt = bot.get_txt 22 | self.remove_timed_role.start() 23 | 24 | @commands.Cog.listener("on_member_update") 25 | async def on_member_update(self, before, after): 26 | nr = set(after.roles) - set(before.roles) 27 | for r in nr: 28 | if r.id in self.bot.guild_settings[after.guild.id]["timed_role"]: 29 | t = self.bot.guild_settings[after.guild.id]["timed_role"][r.id] 30 | Timed_roles.append( 31 | { 32 | "time": time.time() + t, 33 | "guild": after.guild.id, 34 | "role": r.id, 35 | "member": after.id, 36 | } 37 | ) 38 | 39 | @commands.group(aliases=["tr"]) 40 | async def timed_role(self, ctx): 41 | if ctx.invoked_subcommand is None: 42 | await self.bot.send_subcommands(ctx) 43 | 44 | @timed_role.command(name="add", aliases=["set"]) 45 | @commands.has_guild_permissions(manage_messages=True) 46 | async def tr_add(self, ctx, role: discord.Role, limit: convert_timedelta): 47 | self.bot.guild_settings[ctx.guild.id]["timed_role"][role.id] = limit.total_seconds() 48 | e = discord.Embed( 49 | title=f"時間つきロールに`@{role.name}`を追加しました。", 50 | description=f"戻すには`sb#timed_role remove @{role.name}`を使用してください。", 51 | color=Success, 52 | ) 53 | return await ctx.reply(embed=e) 54 | 55 | @timed_role.command(name="remove", aliases=["del", "delete", "rem"]) 56 | @commands.has_guild_permissions(manage_messages=True) 57 | async def tr_remove(self, ctx, *role: discord.Role): 58 | count = 0 59 | for r in role: 60 | try: 61 | del self.bot.guild_settings[ctx.guild.id]["timed_role"][r.id] 62 | count += 1 63 | except KeyError: 64 | pass 65 | if count == 0: 66 | e = discord.Embed(title="何も削除されませんでした。", color=Error) 67 | else: 68 | e = discord.Embed(title=f"{count}個の時間付きロールを削除しました。", color=Success) 69 | return await ctx.reply(embed=e) 70 | 71 | @timed_role.command(name="list") 72 | async def tr_list(self, ctx): 73 | g = ctx.guild.id 74 | if g not in self.bot.guild_settings: 75 | await self.reset(ctx) 76 | gs = self.bot.guild_settings[g] 77 | if gs["timed_role"] == {}: 78 | e = discord.Embed( 79 | title=get_txt(ctx.guild.id, "tr_list_no"), 80 | description=get_txt(ctx.guild.id, "tr_list_desc"), 81 | color=Error, 82 | ) 83 | return await ctx.reply(embed=e) 84 | else: 85 | 86 | def make_new(): 87 | table = Texttable(max_width=80) 88 | table.set_deco(Texttable.HEADER) 89 | table.set_cols_width( 90 | [ 91 | max([chrsize_len(str(c)) for c in gs["timed_role"].keys()]), 92 | max([chrsize_len(str(to_lts(c))) for c in gs["timed_role"].values()]), 93 | ] 94 | ) 95 | table.set_cols_dtype(["t", "t"]) 96 | table.set_cols_align(["l", "l"]) 97 | table.add_row(get_txt(ctx.guild.id, "tr_list_row")) 98 | return table 99 | 100 | table = make_new() 101 | res = [] 102 | for k, v in gs["timed_role"].items(): 103 | b = table.draw() 104 | if ctx.guild.get_role(k): 105 | rn = ctx.guild.get_role(k).name 106 | else: 107 | continue 108 | table.add_row([f"@{remove_emoji(rn)}", to_lts(v)]) 109 | if len(table.draw()) > 2000: 110 | res.append(b) 111 | table = make_new() 112 | table.add_row( 113 | [ 114 | k, 115 | v[0].replace("\n", get_txt(ctx.guild.id, "tr_list_br")), 116 | v[1].replace("\n", get_txt(ctx.guild.id, "tr_list_br")), 117 | ] 118 | ) 119 | res.append(table.draw()) 120 | e = discord.Embed( 121 | title=get_txt(ctx.guild.id, "tr_list") + f" - {1}/{len(res)}", 122 | description=f"```asciidoc\n{res[0]}```", 123 | color=Info, 124 | ) 125 | buttons = [ 126 | components.Button("前のページ", "left", style=2), 127 | components.Button("次のページ", "right", style=2), 128 | components.Button("終了", "exit", style=4), 129 | ] 130 | msg = await components.reply(ctx, embed=e, components=buttons) 131 | page = 0 132 | while True: 133 | try: 134 | com = await self.bot.wait_for( 135 | "button_click", 136 | check=lambda com: com.message == msg and com.member == ctx.author, 137 | timeout=60, 138 | ) 139 | await com.defer_update() 140 | if com.custom_id == "left": 141 | if page > 0: 142 | page -= 1 143 | buttons[0].enabled = page != 0 144 | elif com.custom_id == "right": 145 | if page < (len(res) - 1): 146 | page += 1 147 | buttons[1].enabled = page != (len(res) - 1) 148 | elif com.custom_id == "exit": 149 | break 150 | e = discord.Embed( 151 | title=get_txt(ctx.guild.id, "tr_list") + f" - {page+1}/{len(res)}", 152 | description=f"```asciidoc\n{res[page]}```", 153 | color=Info, 154 | ) 155 | await msg.edit(embed=e) 156 | except asyncio.TimeoutError: 157 | break 158 | for c in buttons: 159 | c.enabled = False 160 | await components.edit(msg, components=buttons) 161 | 162 | @tasks.loop(minutes=1) 163 | async def remove_timed_role(self): 164 | nt = [] 165 | for t in Timed_roles: 166 | if t["time"] < time.time(): 167 | try: 168 | guild = self.bot.get_guild(t["guild"]) 169 | await guild.get_member(t["member"]).remove_roles(guild.get_role(t["role"])) 170 | except (AttributeError, discord.Forbidden): 171 | pass 172 | else: 173 | nt.append(t) 174 | 175 | Timed_roles.clear() 176 | Timed_roles.extend(nt) 177 | 178 | def cog_unload(self): 179 | self.remove_timed_role.stop() 180 | 181 | 182 | def setup(_bot): 183 | global bot 184 | bot = _bot 185 | _bot.add_cog(TimedRoleCog(_bot), override=True) 186 | -------------------------------------------------------------------------------- /cogs/topgg.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands, tasks 2 | 3 | import _pathmagic # type: ignore # noqa 4 | 5 | 6 | class TopGG(commands.Cog): 7 | """ 8 | This example uses dblpy's autopost feature to post guild count to top.gg every 30 minutes. 9 | """ 10 | 11 | def __init__(self, bot): 12 | self.bot: commands.Bot = bot 13 | # Autopost will post your guild count every 30 minutes 14 | if not self.bot.debug: 15 | self.dblpy = self.bot.DBL_client 16 | self.task = self.update_stats.start() 17 | 18 | @tasks.loop(minutes=30) 19 | async def update_stats(self): 20 | """This function runs every 30 minutes to automatically update your server count.""" 21 | await self.bot.wait_until_ready() 22 | try: 23 | server_count = len(self.bot.guilds) 24 | await self.dblpy.post_guild_count(server_count) 25 | except Exception as e: 26 | raise e 27 | 28 | def cog_unload(self): 29 | self.upload_stats.stop() 30 | 31 | 32 | def setup(bot): 33 | bot.add_cog(TopGG(bot)) 34 | -------------------------------------------------------------------------------- /common_resources/consts.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | import discord 4 | 5 | from . import lang_en, lang_ja 6 | 7 | importlib.reload(lang_en) 8 | importlib.reload(lang_ja) 9 | Texts = {"ja": lang_ja.JA_TEXTS, "en": lang_en.EN_TEXTS} 10 | Info = discord.Color.blue() 11 | Attention = discord.Color.orange() 12 | Level = discord.Color.gold() 13 | Error = discord.Color.red() 14 | Widget = discord.Color.darker_grey() 15 | Gaming = discord.Color.greyple() 16 | Alert = discord.Color.dark_orange() 17 | Success = discord.Color.green() 18 | Process = discord.Color.dark_green() 19 | Premium_color = 0xF76FF2 20 | Bot_info = 0x00CCFF 21 | Chat = discord.Color.lighter_grey() 22 | Time_format = "%Y-%m-%d %H:%M:%S" 23 | Activate_aliases = ["on", "active", "true"] 24 | Deactivate_aliases = ["off", "disable", "false"] 25 | Event_dict = lang_ja.Event_dict 26 | Stat_dict = lang_ja.Stat_dict 27 | Official_discord_id = 715540925081714788 28 | Sub_discord_id = 723276556629442590 29 | Owner_ID = 686547120534454315 30 | -------------------------------------------------------------------------------- /common_resources/lang_en.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | Event_dict2 = {"join": "On join", "leave": "On leave"} 5 | Stat_dict2 = { 6 | "members": "Members", 7 | "humans": "Users", 8 | "bots": "Bots", 9 | "onlines": "Onlines", 10 | "online_per": "Online percent", 11 | "channels": "Channels", 12 | "text_channels": "Text channels", 13 | "voice_channels": "Voice channels", 14 | "roles": "Roles", 15 | } 16 | en2 = "" 17 | for ek, ev in Event_dict2.items(): 18 | en2 += f"`{ek}`: {ev}\n" 19 | st2 = "" 20 | for ek, ev in Stat_dict2.items(): 21 | st2 += f"`{ek}`: {ev}\n" 22 | with open( 23 | os.path.dirname(__file__) + "/../translations/en/main.json", 24 | "r", 25 | encoding="utf8", 26 | ) as f: 27 | EN_TEXTS = json.load(f) 28 | -------------------------------------------------------------------------------- /common_resources/lang_ja.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | Event_dict = {"join": "参加時", "leave": "退出時"} 5 | Stat_dict = { 6 | "members": "メンバー数", 7 | "humans": "ユーザー数", 8 | "bots": "ボット数", 9 | "onlines": "オンライン数", 10 | "online_per": "オンライン割合", 11 | "channels": "チャンネル", 12 | "text_channels": "テキストチャンネル", 13 | "voice_channels": "ボイスチャンネル", 14 | "roles": "ロール", 15 | } 16 | en = "" 17 | for ek, ev in Event_dict.items(): 18 | en += f"{ek} - {ev}\n" 19 | st = "" 20 | for ek, ev in Stat_dict.items(): 21 | st += f"{ek} - {ev}\n" 22 | with open( 23 | os.path.dirname(__file__) + "/../translations/ja/main.json", 24 | "r", 25 | encoding="utf8", 26 | ) as f: 27 | JA_TEXTS = json.load(f) 28 | -------------------------------------------------------------------------------- /common_resources/settings.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | import typing 5 | from typing import Literal, NewType, TypedDict, Union 6 | 7 | Snowflake = NewType("Snowflake", int) 8 | 9 | 10 | class GuildSettings(TypedDict): 11 | autoreply: dict[str, list[str, str]] 12 | muted: dict[Snowflake, int] 13 | deactivate_command: list[Snowflake] 14 | auth_role: Snowflake 15 | trans_channel: dict[Snowflake, str] 16 | event_messages: EventMessages 17 | event_message_channel: Snowflake 18 | level_counts: dict[Snowflake, int] 19 | levels: dict[Snowflake, int] 20 | level_roles: dict[int, Snowflake] 21 | level_active: bool 22 | level_channel: False | Snowflake 23 | level_ignore_channel: list[Snowflake] 24 | level_boosts: dict[Snowflake, float] 25 | do_bump_alert: False | Snowflake 26 | bump_role: False | Snowflake 27 | do_dissoku_alert: False | Snowflake 28 | dissoku_role: False | Snowflake 29 | prefix: None | str 30 | autopub: list[Snowflake] 31 | anchor_link: list[Snowflake] 32 | role_link: dict[Snowflake, list[list[Snowflake, Snowflake]]] 33 | archive_category: Snowflake 34 | ww_role: WWRole 35 | ticket_category: Snowflake 36 | warns: dict[Snowflake, int] 37 | warn_settings: WarnSettings 38 | timed_role: dict[Snowflake, int] 39 | gban_enabled: bool 40 | lock_message_content: dict[Snowflake, LockMessageContent] 41 | lock_message_id: dict[Snowflake, Snowflake | None] 42 | 43 | 44 | def _get_int_keys(): 45 | def convert_union(txt: re.Match): 46 | return "Union[" + txt[0].replace(" | ", ", ") + "]" 47 | 48 | def pass_arg(arg: typing.ForwardRef): 49 | arg_str = arg.__forward_arg__ 50 | arg_str = ( 51 | re.sub(r"(\w+ \| )+(\w+)", convert_union, arg_str) 52 | .replace("False", "Literal[False]") 53 | .replace("True", "Literal[True]") 54 | .replace("Snowflake", "int") 55 | ) 56 | return eval(arg_str) 57 | 58 | settings = dict(map(lambda a: [a[0], pass_arg(a[1])], GuildSettings.__annotations__.items())) 59 | int_keys = [] 60 | 61 | def get_key_type(k, v): 62 | if isinstance(v, typing._TypedDictMeta): 63 | val_types = dict(map(lambda a: [a[0], pass_arg(a[1])], v.__annotations__.items())) 64 | for k2, v2 in val_types.items(): 65 | get_key_type(k + "." + k2, v2) 66 | return 67 | if not hasattr(v, "__origin__"): 68 | return 69 | if v.__origin__ is dict: 70 | if v.__args__[0] is int: 71 | int_keys.append(k) 72 | if isinstance(v.__args__[1], typing._TypedDictMeta): 73 | val_types = dict(map(lambda a: [a[0], pass_arg(a[1])], v.__args__[1].__annotations__.items())) 74 | for k2, v2 in val_types.items(): 75 | get_key_type(k + "." + k2, v2) 76 | 77 | for k, v in settings.items(): 78 | get_key_type(k, v) 79 | return int_keys 80 | 81 | 82 | DEFAULT_SETTINGS: GuildSettings = { 83 | "autoreply": {}, 84 | "muted": {}, 85 | "deactivate_command": [], 86 | "last_everyone": {}, 87 | "everyone_count": {}, 88 | "hasnt_admin": "権限がありません。", 89 | "do_announce": True, 90 | "announce_channel": False, 91 | "auth_role": 0, 92 | "trans_channel": {}, 93 | "event_messages": {"join": False, "leave": False}, 94 | "event_message_channel": 0, 95 | "alarm_channels": 0, 96 | "level_counts": {}, 97 | "levels": {}, 98 | "level_roles": {}, 99 | "level_active": False, 100 | "level_channel": False, 101 | "level_ignore_channel": [], 102 | "bump_role": False, 103 | "do_dissoku_alert": False, 104 | "dissoku_role": False, 105 | "do_stat_channels": False, 106 | "stat_channels": {}, 107 | "stat_update_counter": 0, 108 | "ticket_category": 0, 109 | "auto_parse": [], 110 | "do_everyone_alert": True, 111 | "lang": "ja", 112 | "expand_message": False, 113 | "do_bump_alert": True, 114 | "invites": [], 115 | "prefix": None, 116 | "autopub": [], 117 | "alarms": {}, 118 | "2ch_link": [], 119 | "role_link": {}, 120 | "role_keep": False, 121 | "timezone": 0, 122 | "archive_category": 0, 123 | "ww_role": {"alive": None, "dead": None}, 124 | "lainan_talk": [], 125 | "auth_channel": { 126 | "type": None, 127 | "channel": 0, 128 | }, 129 | "starboards": {}, 130 | "level_boosts": {}, 131 | "warns": {}, 132 | "warn_settings": {"punishments": {}, "auto": 0}, 133 | "economy": {}, 134 | "timed_role": {}, 135 | "auto_text": [], 136 | "gban_enabled": False, 137 | "lock_message_content": {}, 138 | "lock_message_id": {}, 139 | } 140 | 141 | 142 | class EventMessages(TypedDict): 143 | join: Union[False, str] 144 | leave: Union[False, str] 145 | 146 | 147 | class WWRole(TypedDict): 148 | alive: None | Snowflake 149 | dead: None | Snowflake 150 | 151 | 152 | class WarnSettings(TypedDict): 153 | punishments: dict[int, WarnSettingsAction] 154 | 155 | 156 | class WarnSettingsAction(TypedDict): 157 | action: Literal["mute", "kick", "ban", "role_add", "role_remove"] 158 | 159 | 160 | class WarnSettingsActionMute(WarnSettingsAction, total=False): 161 | length: int 162 | 163 | 164 | class WarnSettingsActionRole(WarnSettingsAction, total=False): 165 | role: Snowflake 166 | 167 | 168 | class LockMessageContent(TypedDict): 169 | content: str 170 | author: Snowflake 171 | 172 | 173 | class AutoMod(TypedDict): 174 | token_spam: AutoModItem 175 | invite_spam: AutoModItem 176 | 177 | 178 | class AutoModItem(TypedDict, total=False): 179 | enabled: bool 180 | warn: int | None 181 | disabled_channels: list[Snowflake] 182 | 183 | 184 | class AutoModGlobal(TypedDict): 185 | warn: int 186 | disabled_channels: list[Snowflake] 187 | 188 | 189 | DEFAULT_AUTOMOD_ITEM: AutoModItem = { 190 | "enabled": False, 191 | "warn": 0, 192 | "disabled_channels": [], 193 | } 194 | 195 | DEFAULT_AUTOMOD_GLOBAL: AutoModGlobal = { 196 | "warn": 1, 197 | "disabled_channels": [], 198 | } 199 | 200 | GuildSettings.int_keys = _get_int_keys() 201 | -------------------------------------------------------------------------------- /common_resources/tokens.py.sample: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | DEBUG_TOKEN = "Th1sIsN0tT0k3n.B3cause.If15howB0tW1llG3tH4cked" 4 | TOKEN = 'Th1sIsN0tT0k3n.B3cause.If15howB0tW1llG3tH4cked' 5 | YOUTUBE_API_KEY = 'YouTube API token here' 6 | dbl_token = "DBL token here" 7 | cstr = "MongoDB connection string here" 8 | twitter_consumer_key = "Twitter consumer key here" 9 | twitter_consumer_secret = "Twitter consumer secret here" 10 | twitter_bearer = "Twitter Bearer token here" 11 | botdd_token = "botdd token here" 12 | web_pass = "web password here" 13 | sentry_url = "sentry url here" 14 | emergency = False 15 | -------------------------------------------------------------------------------- /common_resources/tools.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import collections 3 | import datetime 4 | import math 5 | import re 6 | import unicodedata 7 | 8 | import discord 9 | from discord.ext import commands 10 | from discord_emoji.table import UNICODE_TO_DISCORD 11 | 12 | Datetime_re = re.compile( 13 | r"(\d+[w(週間?)(weeks?)])?(\d+[d(日)(days?)])? ?(\d+[:h(時間)(hours?)])?(\d+[:m(分)(minutes?)])?(\d+[s(秒)seconds?)]?)?" 14 | ) 15 | Datetime_args = ["weeks", "days", "hours", "minutes", "seconds"] 16 | Shard_re = re.compile(r"(\d+)([^\d]+)?") 17 | 18 | 19 | def flatten(li): 20 | for el in li: 21 | if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)): 22 | yield from flatten(el) 23 | else: 24 | yield el 25 | 26 | 27 | def to_lts(s): 28 | s = math.floor(s) 29 | res = str((s // 60) % 60).zfill(2) + ":" + str(s % 60).zfill(2) 30 | if s > 3600: 31 | if s > 3600 * 24: 32 | res = str(s // 3600 // 24) + "d " + str(s // 3600 % 24).zfill(2) + ":" + res 33 | else: 34 | res = str(s // 3600).zfill(2) + ":" + res 35 | return res 36 | 37 | 38 | def remove_emoji(src_str): 39 | return "".join("?" if c in UNICODE_TO_DISCORD.keys() else c for c in src_str) 40 | 41 | 42 | def recr_keys(d): 43 | res = [] 44 | 45 | def _recr_keys(p, d2): 46 | for k, v in d2.items(): 47 | if isinstance(v, dict): 48 | _recr_keys(p + [k], v) 49 | else: 50 | res.append(tuple(p + [k])) 51 | 52 | _recr_keys([], d) 53 | return res 54 | 55 | 56 | def recr_items(d): 57 | res = [] 58 | 59 | def _recr_items(p, d2): 60 | for k, v in d2.items(): 61 | if isinstance(v, dict): 62 | _recr_items(p + [k], v) 63 | else: 64 | res.append((tuple(p + [k]), v)) 65 | 66 | _recr_items([], d) 67 | return res 68 | 69 | 70 | class NotADatetimeFormat(commands.BadArgument): 71 | def __init__(self, argument): 72 | self.argument = argument 73 | super().__init__('"{}" is not a datetime format.'.format(argument)) 74 | 75 | 76 | def convert_timedelta(arg) -> datetime.timedelta: 77 | fm = Datetime_re.fullmatch(arg) 78 | if not fm: 79 | raise NotADatetimeFormat(arg) 80 | res = {} 81 | for i, f in enumerate(fm.groups()): 82 | if not f: 83 | continue 84 | sh = Shard_re.fullmatch(f) 85 | res[Datetime_args[i]] = int(sh[1]) 86 | return datetime.timedelta(**res) 87 | 88 | 89 | def chrsize_len(text): 90 | count = 0 91 | for c in text: 92 | if unicodedata.east_asian_width(c) in "FWA": 93 | count += 2 94 | else: 95 | count += 1 96 | return count 97 | 98 | 99 | async def send_subcommands(ctx): 100 | desc = "" 101 | for c in ctx.command.commands: 102 | desc += ( 103 | f"**`{c.name}`** " 104 | + ( 105 | ctx.bot.get_txt(ctx.guild.id, "help_detail").get( 106 | str(c), 107 | "_" + ctx.bot.get_txt(ctx.guild.id, "help_detail_none") + "_", 108 | ) 109 | ).split("\n")[0] 110 | + "\n" 111 | ) 112 | e = discord.Embed( 113 | title=ctx.bot.get_txt(ctx.guild.id, "subcommand").format(ctx.command.name), 114 | description=desc, 115 | color=0x00CCFF, 116 | ) 117 | await ctx.send(embed=e) 118 | 119 | 120 | async def delay_react_remove(message, emoji, user, sec): 121 | await asyncio.sleep(sec) 122 | await message.remove_reaction(emoji, user) 123 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | commit_message: "Crowdin: %original_file_name%の翻訳を更新" 2 | append_commit_message: false 3 | files: 4 | - source: translations/ja/*.json 5 | translation: /translations/%two_letters_code%/%file_name%.%file_extension% 6 | -------------------------------------------------------------------------------- /fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/fonts/.gitkeep -------------------------------------------------------------------------------- /images/5000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/5000.png -------------------------------------------------------------------------------- /images/795568167534723102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/795568167534723102.png -------------------------------------------------------------------------------- /images/795569425361010719.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/795569425361010719.png -------------------------------------------------------------------------------- /images/7botml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/7botml.png -------------------------------------------------------------------------------- /images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/add.png -------------------------------------------------------------------------------- /images/b0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b0.png -------------------------------------------------------------------------------- /images/b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b1.png -------------------------------------------------------------------------------- /images/b10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b10.png -------------------------------------------------------------------------------- /images/b2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b2.png -------------------------------------------------------------------------------- /images/b3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b3.png -------------------------------------------------------------------------------- /images/b4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b4.png -------------------------------------------------------------------------------- /images/b5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b5.png -------------------------------------------------------------------------------- /images/b6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b6.png -------------------------------------------------------------------------------- /images/b7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b7.png -------------------------------------------------------------------------------- /images/b8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b8.png -------------------------------------------------------------------------------- /images/b9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/b9.png -------------------------------------------------------------------------------- /images/barc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc1.png -------------------------------------------------------------------------------- /images/barc21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc21.png -------------------------------------------------------------------------------- /images/barc210.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc210.png -------------------------------------------------------------------------------- /images/barc22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc22.png -------------------------------------------------------------------------------- /images/barc23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc23.png -------------------------------------------------------------------------------- /images/barc24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc24.png -------------------------------------------------------------------------------- /images/barc25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc25.png -------------------------------------------------------------------------------- /images/barc26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc26.png -------------------------------------------------------------------------------- /images/barc27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc27.png -------------------------------------------------------------------------------- /images/barc28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc28.png -------------------------------------------------------------------------------- /images/barc29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barc29.png -------------------------------------------------------------------------------- /images/barcg21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg21.png -------------------------------------------------------------------------------- /images/barcg210.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg210.png -------------------------------------------------------------------------------- /images/barcg22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg22.png -------------------------------------------------------------------------------- /images/barcg23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg23.png -------------------------------------------------------------------------------- /images/barcg24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg24.png -------------------------------------------------------------------------------- /images/barcg25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg25.png -------------------------------------------------------------------------------- /images/barcg26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg26.png -------------------------------------------------------------------------------- /images/barcg27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg27.png -------------------------------------------------------------------------------- /images/barcg28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg28.png -------------------------------------------------------------------------------- /images/barcg29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcg29.png -------------------------------------------------------------------------------- /images/barcy21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy21.png -------------------------------------------------------------------------------- /images/barcy210.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy210.png -------------------------------------------------------------------------------- /images/barcy22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy22.png -------------------------------------------------------------------------------- /images/barcy23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy23.png -------------------------------------------------------------------------------- /images/barcy24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy24.png -------------------------------------------------------------------------------- /images/barcy25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy25.png -------------------------------------------------------------------------------- /images/barcy26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy26.png -------------------------------------------------------------------------------- /images/barcy27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy27.png -------------------------------------------------------------------------------- /images/barcy28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy28.png -------------------------------------------------------------------------------- /images/barcy29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barcy29.png -------------------------------------------------------------------------------- /images/barl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barl.png -------------------------------------------------------------------------------- /images/barr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/barr.png -------------------------------------------------------------------------------- /images/bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/bot.png -------------------------------------------------------------------------------- /images/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/cat.png -------------------------------------------------------------------------------- /images/category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/category.png -------------------------------------------------------------------------------- /images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check.png -------------------------------------------------------------------------------- /images/check2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check2.png -------------------------------------------------------------------------------- /images/check3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check3.png -------------------------------------------------------------------------------- /images/check4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check4.png -------------------------------------------------------------------------------- /images/check5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check5.png -------------------------------------------------------------------------------- /images/check6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check6.png -------------------------------------------------------------------------------- /images/check7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check7.png -------------------------------------------------------------------------------- /images/check8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/check8.png -------------------------------------------------------------------------------- /images/dnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/dnd.png -------------------------------------------------------------------------------- /images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/down.png -------------------------------------------------------------------------------- /images/downa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/downa.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon.png -------------------------------------------------------------------------------- /images/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon2.png -------------------------------------------------------------------------------- /images/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon3.png -------------------------------------------------------------------------------- /images/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon4.png -------------------------------------------------------------------------------- /images/icon5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon5.png -------------------------------------------------------------------------------- /images/icon6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon6.png -------------------------------------------------------------------------------- /images/icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/icon_round.png -------------------------------------------------------------------------------- /images/idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/idle.png -------------------------------------------------------------------------------- /images/img_auth_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/img_auth_base.png -------------------------------------------------------------------------------- /images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/info.png -------------------------------------------------------------------------------- /images/join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/join.png -------------------------------------------------------------------------------- /images/lainan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/lainan.png -------------------------------------------------------------------------------- /images/leave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/leave.png -------------------------------------------------------------------------------- /images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/left.png -------------------------------------------------------------------------------- /images/lefta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/lefta.png -------------------------------------------------------------------------------- /images/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/lock.png -------------------------------------------------------------------------------- /images/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/love.png -------------------------------------------------------------------------------- /images/network.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/network.gif -------------------------------------------------------------------------------- /images/offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/offline.png -------------------------------------------------------------------------------- /images/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/online.png -------------------------------------------------------------------------------- /images/othello0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/othello0.png -------------------------------------------------------------------------------- /images/othello1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/othello1.png -------------------------------------------------------------------------------- /images/othello2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/othello2.png -------------------------------------------------------------------------------- /images/othello3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/othello3.png -------------------------------------------------------------------------------- /images/othello4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/othello4.png -------------------------------------------------------------------------------- /images/ox1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/ox1.png -------------------------------------------------------------------------------- /images/ox2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/ox2.png -------------------------------------------------------------------------------- /images/per.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/per.png -------------------------------------------------------------------------------- /images/premium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/premium.png -------------------------------------------------------------------------------- /images/queue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/queue.gif -------------------------------------------------------------------------------- /images/queue.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/queue.psd -------------------------------------------------------------------------------- /images/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/remove.png -------------------------------------------------------------------------------- /images/reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/reply.png -------------------------------------------------------------------------------- /images/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/report.png -------------------------------------------------------------------------------- /images/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/right.png -------------------------------------------------------------------------------- /images/righta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/righta.png -------------------------------------------------------------------------------- /images/skip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/skip.png -------------------------------------------------------------------------------- /images/tmp/queue0000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0000.png -------------------------------------------------------------------------------- /images/tmp/queue0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0001.png -------------------------------------------------------------------------------- /images/tmp/queue0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0002.png -------------------------------------------------------------------------------- /images/tmp/queue0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0003.png -------------------------------------------------------------------------------- /images/tmp/queue0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0004.png -------------------------------------------------------------------------------- /images/tmp/queue0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0005.png -------------------------------------------------------------------------------- /images/tmp/queue0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0006.png -------------------------------------------------------------------------------- /images/tmp/queue0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0007.png -------------------------------------------------------------------------------- /images/tmp/queue0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0008.png -------------------------------------------------------------------------------- /images/tmp/queue0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0009.png -------------------------------------------------------------------------------- /images/tmp/queue0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0010.png -------------------------------------------------------------------------------- /images/tmp/queue0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0011.png -------------------------------------------------------------------------------- /images/tmp/queue0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0012.png -------------------------------------------------------------------------------- /images/tmp/queue0013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0013.png -------------------------------------------------------------------------------- /images/tmp/queue0014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0014.png -------------------------------------------------------------------------------- /images/tmp/queue0015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0015.png -------------------------------------------------------------------------------- /images/tmp/queue0016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0016.png -------------------------------------------------------------------------------- /images/tmp/queue0017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/tmp/queue0017.png -------------------------------------------------------------------------------- /images/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/toggle.png -------------------------------------------------------------------------------- /images/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/unknown.png -------------------------------------------------------------------------------- /images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/up.png -------------------------------------------------------------------------------- /images/upa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/upa.png -------------------------------------------------------------------------------- /images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/user.png -------------------------------------------------------------------------------- /images/vc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/vc.png -------------------------------------------------------------------------------- /images/voice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/voice.gif -------------------------------------------------------------------------------- /images/voice_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/voice_info.png -------------------------------------------------------------------------------- /images/warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/warn.png -------------------------------------------------------------------------------- /images/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SevenBot-dev/SevenBot/41fdfdfb74b716c27f493ad34e66b469a4a6219a/images/white.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import asyncio 3 | from collections import defaultdict 4 | import copy 5 | import datetime 6 | import importlib 7 | import io 8 | import json 9 | import logging 10 | import os 11 | import sys 12 | import traceback 13 | from typing import DefaultDict, Union 14 | 15 | import discord 16 | import pymongo 17 | import requests 18 | from sembed import SEmbed 19 | import sentry_sdk 20 | import topgg 21 | from discord.ext import commands, levenshtein 22 | from motor import motor_asyncio as motor 23 | from watchdog.events import FileSystemEvent, FileSystemEventHandler 24 | from watchdog.observers import Observer 25 | 26 | from common_resources import consts as common_resources 27 | from common_resources.consts import Official_discord_id, Sub_discord_id 28 | from common_resources.settings import GuildSettings, DEFAULT_SETTINGS 29 | from common_resources.tokens import ( 30 | DEBUG_TOKEN, 31 | TOKEN, 32 | cstr, 33 | dbl_token, 34 | emergency, 35 | sentry_url, 36 | web_pass, 37 | ) 38 | from common_resources.tools import flatten 39 | 40 | logger = logging.getLogger("discord") 41 | logger.setLevel(logging.INFO) 42 | # handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w') 43 | # handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) 44 | # logger.addHandler(handler) 45 | handler = logging.StreamHandler() 46 | handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")) 47 | logger.addHandler(handler) 48 | if "debug" in sys.argv: 49 | os.environ["DEBUG"] = "True" 50 | elif "prod" in sys.argv: 51 | os.environ["DEBUG"] = "False" 52 | else: 53 | os.environ["DEBUG"] = "True" if sys.platform == "win32" else "False" 54 | if os.environ["DEBUG"] == "True": 55 | db_name = "development" 56 | token = DEBUG_TOKEN 57 | else: 58 | sentry_sdk.init( 59 | sentry_url, 60 | traces_sample_rate=1.0, 61 | ) 62 | token = TOKEN 63 | db_name = "production" 64 | 65 | 66 | class ReloadEventHandler(FileSystemEventHandler): 67 | def __init__(self, loop, bot, *args, **kwargs): 68 | self._loop = loop 69 | self.bot = bot 70 | self.times = {} 71 | super().__init__(*args, **kwargs) 72 | 73 | def will_run(self, event: FileSystemEvent): 74 | if not self.bot.is_ready(): 75 | return False 76 | if "__pycache__" in event.src_path: 77 | return False 78 | if event.is_directory: 79 | return False 80 | if not event.src_path.endswith(".py"): 81 | return False 82 | self.check_loop() 83 | 84 | if event.src_path not in self.times: 85 | self.times[event.src_path] = 0 86 | self.times[event.src_path] += 1 87 | if self.times[event.src_path] > 1: 88 | self.times[event.src_path] = 0 89 | return True 90 | else: 91 | return False 92 | 93 | def check_loop(self): 94 | try: 95 | asyncio.get_event_loop() 96 | except RuntimeError: 97 | asyncio.set_event_loop(self._loop) 98 | 99 | def on_created(self, event: FileSystemEvent): 100 | if not self.will_run(event): 101 | return 102 | try: 103 | self.bot.load_extension("cogs." + event.src_path.split("\\")[-1].split(".")[0]) 104 | except Exception: 105 | print(traceback.format_exc()) 106 | else: 107 | print(f"-- Loaded: {event.src_path}") 108 | 109 | def on_modified(self, event: FileSystemEvent, import_fail: bool = False): 110 | if not self.will_run(event): 111 | return 112 | try: 113 | self.bot.reload_extension("cogs." + event.src_path.split("\\")[-1].split(".")[0]) 114 | except discord.ExtensionFailed as e: 115 | if isinstance(e.original, ImportError) and not import_fail: 116 | message = e.original.args[0] 117 | cls = message.split("'")[3] 118 | print("-- Detected import error in the module: " + cls) 119 | print("-- Attempting to reload the module...") 120 | importlib.reload(sys.modules[cls]) 121 | self.on_modified(event, True) 122 | else: 123 | traceback.print_exc() 124 | except discord.ExtensionNotLoaded: 125 | try: 126 | self.bot.load_extension("cogs." + event.src_path.split("\\")[-1].split(".")[0]) 127 | except Exception: 128 | print(traceback.format_exc()) 129 | except discord.NoEntryPointError: 130 | self.on_modified(event, True) 131 | except Exception: 132 | print(traceback.format_exc()) 133 | else: 134 | print(f"-- Reloaded: {event.src_path}") 135 | 136 | def on_deleted(self, event: FileSystemEvent): 137 | if not self.will_run(event): 138 | return 139 | try: 140 | self.bot.unload_extension("cogs." + event.src_path.split("\\")[-1].split(".")[0]) 141 | except Exception: 142 | print(traceback.format_exc()) 143 | else: 144 | print(f"-- Unloaded: {event.src_path}") 145 | 146 | 147 | Channel_ids = { 148 | "log": 756254787191963768, 149 | "announce": 756254915441197206, 150 | "emoji_print": 756254956817743903, 151 | "global_report": 756255003341225996, 152 | "boot_log": 747764100922343554, 153 | "error": 763877469928554517, 154 | } 155 | Premium_guild = 715540925081714788 156 | Premium_role = 779685018964066336 157 | Save_channel_id = 765489262123548673 158 | 159 | 160 | intent = discord.Intents.all() 161 | intent.typing = False 162 | # intent.members=True 163 | # intent.messages=True 164 | # intent.reactions=True 165 | 166 | # Save_game = discord.Game(name="Saving..." + "⠀" * 100) 167 | # Save_game2 = discord.Game(name="Complete!" + "⠀" * 100) 168 | print("-- Loading save from attachment: ", end="") 169 | try: 170 | r = requests.get( 171 | f"https://discord.com/api/v9/channels/{Save_channel_id}/messages?limit=1", 172 | headers={"authorization": "Bot " + TOKEN}, 173 | ) 174 | r.raise_for_status() 175 | s = requests.get(r.json()[0]["attachments"][0]["url"]) 176 | s.raise_for_status() 177 | raw_save = s.content.decode("utf8") 178 | except requests.exceptions.HTTPError: 179 | print("\n!! Unable to get save, loaded save.sample.") 180 | with open("./save.sample", "r", encoding="utf8") as f: 181 | raw_save = f.read() 182 | print("Done") 183 | 184 | 185 | class SevenBot(commands.AutoShardedBot): 186 | def __init__(self): 187 | super().__init__( 188 | command_prefix=self.prefix, 189 | help_command=None, 190 | allowed_mentions=discord.AllowedMentions(everyone=False, replied_user=False), 191 | intents=intent, 192 | strip_after_prefix=True, 193 | case_insensitive=True, 194 | enable_debug_events=True, 195 | ) 196 | self.consts = {"qu": {}, "ch": {}, "ne": [], "tc": {}, "pci": {}, "ticket_time": {}} 197 | self.raw_config = ast.literal_eval(raw_save) 198 | self.dbclient = motor.AsyncIOMotorClient(cstr) 199 | self.sync_dbclient = pymongo.MongoClient(cstr) 200 | self.db = self.dbclient[db_name] 201 | self.sync_db = self.sync_dbclient[db_name] 202 | self.web_pass = web_pass 203 | self.debug = os.environ["DEBUG"] == "True" 204 | self.texts = common_resources.Texts 205 | self.default_user_settings = { 206 | "level_dm": False, 207 | } 208 | self.oemojis: dict[str, discord.Emoji] = {} 209 | self.guild_settings: DefaultDict[int, GuildSettings] = defaultdict(lambda: copy.deepcopy(DEFAULT_SETTINGS)) 210 | print("-- Loading saves from db: ", end="") 211 | self.load_saves() 212 | print("Done") 213 | print("Debug mode: " + str(self.debug)) 214 | if self.debug: 215 | self.loop.create_task(self.auto_reload()) 216 | self.check(commands.cooldown(2, 2)) 217 | 218 | def prefix(self, bot, message): 219 | if self.guild_settings[message.guild.id]["prefix"] is None: 220 | if self.debug: 221 | return ["sb/"] 222 | else: 223 | return ["sb#", "sb."] 224 | else: 225 | return self.guild_settings[message.guild.id]["prefix"] 226 | 227 | def load_saves(self): 228 | if self.debug: 229 | self.loop.create_task(self.load_saves_debug()) 230 | 231 | for g in self.sync_dbclient[db_name].guild_settings.find({}, {"_id": False}): 232 | for ik in GuildSettings.int_keys: 233 | t = g 234 | for ikc in ik.split("."): 235 | t = t[ikc] 236 | t2 = dict([(int(k), v) for k, v in t.items()]) 237 | t.clear() 238 | t.update(t2) 239 | self.guild_settings[g["gid"]] = g 240 | 241 | async def to_coro(self, f: asyncio.Future): 242 | await f 243 | 244 | async def load_saves_debug(self): 245 | pass 246 | # for c in await self.dbclient["production"].list_collection_names(): 247 | # async for r in self.dbclient["production"][c].find(): 248 | # self.loop.create_task( 249 | # self.to_coro(self.dbclient["development"][c].replace_one({"_id": r["_id"]}, r, upsert=True)) 250 | # ) 251 | 252 | async def auto_reload(self): 253 | event_handler = ReloadEventHandler(self.loop, self) 254 | observer = Observer() 255 | observer.schedule(event_handler, "./cogs", recursive=False) 256 | observer.start() 257 | 258 | async def on_ready(self): 259 | print("on_ready fired") 260 | for k, v in Channel_ids.items(): 261 | self.consts["ch"][k] = self.get_channel(v) 262 | g = self.get_guild(Official_discord_id) 263 | for oe in g.emojis: 264 | self.oemojis[oe.name] = oe 265 | g = self.get_guild(Sub_discord_id) 266 | for oe in g.emojis: 267 | self.oemojis[oe.name] = oe 268 | for i in range(11): 269 | self.consts["ne"].append(self.oemojis["b" + str(i)]) 270 | if not emergency: 271 | await self.get_channel(934611880146780200).send("em:shutdown") 272 | bot.load_extension("jishaku") 273 | bot.load_extension("dpy_peper") 274 | bot.load_extension("discord.ext.components") 275 | if not self.debug: 276 | self.DBL_client = topgg.DBLClient(self, dbl_token, autopost=True) 277 | for o in os.listdir("./cogs"): 278 | if o.endswith(".py") and not o.startswith("_"): 279 | with open(f"./cogs/{o}") as f: 280 | if f.read().startswith("# -*- ignore_on_debug -*-") and self.debug: 281 | continue 282 | try: 283 | bot.load_extension("cogs." + os.path.splitext(os.path.basename(o))[0]) 284 | except Exception as e: 285 | print("!! Failed to load extension: ", e) 286 | traceback.print_exc() 287 | self.levenshtein = levenshtein.Levenshtein(self, max_length=1) 288 | print("on_ready done") 289 | 290 | def is_premium(self, user: Union[discord.User, discord.Member]): 291 | return self.get_guild(Premium_guild).get_member(user.id) and ( 292 | self.get_guild(Premium_guild).get_member(user.id).premium_since 293 | or Premium_role in [r.id for r in self.get_guild(715540925081714788).get_member(user.id).roles] 294 | ) 295 | 296 | async def save(self): 297 | # await self.change_presence(activity=Save_game, status=discord.Status.dnd) 298 | if self.debug: 299 | return 300 | gs2 = list(self.guild_settings.keys()) 301 | for gs in gs2: 302 | if self.get_guild(gs) is None: 303 | del self.guild_settings[gs] 304 | # r = str(self.raw_config) 305 | # ar = [] 306 | # PastebinAPI.paste(PB_key, r, paste_private = "private",paste_expire_date = None) 307 | 308 | # file = open('Save.txt', 'w+', encoding='utf-8') 309 | 310 | # file.write(r) 311 | # file.close() 312 | for gk, gv in self.guild_settings.copy().items(): 313 | r = json.loads(json.dumps(gv)) 314 | r["gid"] = gk 315 | if self.find_overflow(r): 316 | guild = self.get_guild(gk) 317 | try: 318 | await self.get_user(guild.owner_id).send( 319 | embed=SEmbed( 320 | "攻撃を発見しました", 321 | f"本サービスへの意図的な攻撃が確認されたため、`{guild.name}`から退出しました。\n" 322 | "Ban解除は[公式サーバー](https://discord.gg/GknwhnwbAV)まで。", 323 | color=discord.Colour.gold(), 324 | ) 325 | ) 326 | except Exception: 327 | pass 328 | await guild.leave() 329 | self.bot.raw_config["il"].append(gk) 330 | 331 | else: 332 | await self.db.guild_settings.replace_one({"gid": gk}, r, upsert=True) 333 | if not self.debug: 334 | async for gk in self.db.guild_settings.find(): 335 | if not self.get_guild(gk["gid"]): 336 | await self.db.guild_settings.delete_one({"gid": gk["gid"]}) 337 | 338 | sio = await self.loop.run_in_executor( 339 | None, 340 | lambda: io.StringIO(str({k: v for k, v in self.raw_config.items() if k != "gs"})), 341 | ) 342 | await (await self.fetch_channel(765489262123548673)).send( 343 | file=discord.File(sio, filename=f"save_{datetime.datetime.now()}.txt") 344 | ) 345 | sio.close() 346 | # print(games) 347 | try: 348 | pass # await self.change_presence(activity=Save_game2, status=discord.Status.online) 349 | except BaseException: 350 | pass 351 | 352 | def find_overflow(self, data): 353 | if isinstance(data, int): 354 | if data >= 2 ** 64 - 1: 355 | return True 356 | else: 357 | return False 358 | elif isinstance(data, str): 359 | return False 360 | elif isinstance(data, list): 361 | for i in data: 362 | if self.find_overflow(i): 363 | return True 364 | return False 365 | elif isinstance(data, dict): 366 | for i in data.values(): 367 | if self.find_overflow(i): 368 | return True 369 | return False 370 | 371 | def get_txt(self, guild_id, name): 372 | try: 373 | return Texts[self.guild_settings[guild_id]["lang"]][name] 374 | except KeyError: 375 | return Texts["ja"].get(name, "*" + name + "*") 376 | 377 | def is_command(self, msg): 378 | return msg.content.startswith(tuple(self.command_prefix(bot, msg))) 379 | 380 | async def send_subcommands(self, ctx): 381 | desc = "" 382 | for c in ctx.command.commands: 383 | desc += ( 384 | f"**`{c.name}`** " 385 | + ( 386 | self.get_txt(ctx.guild.id, "help_detail").get( 387 | str(c), 388 | "_" + self.get_txt(ctx.guild.id, "help_detail_none") + "_", 389 | ) 390 | ).split("\n")[0] 391 | + "\n" 392 | ) 393 | e = discord.Embed( 394 | title=self.get_txt(ctx.guild.id, "subcommand").format(ctx.command.name), 395 | description=desc, 396 | color=0x00CCFF, 397 | ) 398 | await ctx.send(embed=e) 399 | 400 | async def init_user_settings(self, uid): 401 | nd = copy.deepcopy(self.default_user_settings) 402 | nd["uid"] = uid 403 | await self.db.user_settings.insert_one(nd) 404 | 405 | @property 406 | def global_chats(self): 407 | return ( 408 | set(self.raw_config["snc"]) 409 | | set(self.raw_config["gc"]) 410 | | set(flatten(c["channels"] for c in self.consts["pci"].values())) 411 | ) 412 | 413 | 414 | bot = SevenBot() 415 | 416 | 417 | @bot.command() 418 | async def reload_lang(ctx): 419 | global Texts 420 | importlib.reload(common_resources) 421 | Texts = common_resources.Texts 422 | bot.texts.update(common_resources.Texts) 423 | await ctx.reply("Done") 424 | 425 | 426 | @bot.event 427 | async def on_message(msg): 428 | pass 429 | 430 | 431 | @bot.event 432 | async def on_socket_raw_receive(event): 433 | bot.dispatch("socket_response", json.loads(event)) 434 | 435 | 436 | Texts = common_resources.Texts 437 | 438 | if __name__ == "__main__": 439 | print("*********************") 440 | print(" SevenBot ") 441 | print(" Created by 名無し。 ") 442 | print("*********************") 443 | print("ログイン中…") 444 | bot.run(token) 445 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "sevenbot" 3 | version = "0.1.0" 4 | description = "A multifunctional bot for discord" 5 | authors = ["sevenc-nanashi "] 6 | license = "GPLv3" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | async-google-trans-new = "~=1.4.2" 11 | aiohttp = "^3.7.4" 12 | authlib = "~=0.15.5" 13 | beautifulsoup4 = "~=4.9.3" 14 | discord-emoji = "~=1.3.1" 15 | discord-ext-levenshtein = "~=0.3.0" 16 | dnspython = "~=2.1.0" 17 | dpy-components = "~=0.4.2" 18 | dpy-peper = "~=1.0.1" 19 | dpy-syntaxer = "~=1.1.0" 20 | google-api-python-client = "~=2.9.0" 21 | httpx = "~=0.18.2" 22 | jishaku = "~=2.0.0" 23 | lxml = "~=4.6.5" 24 | motor = "~=2.4.0" 25 | pillow = "~=9.0.0" 26 | psutil = "~=5.8.0" 27 | py = "~=1.10.0" 28 | pylint = "~=2.8.3" 29 | pynacl = "~=1.4.0" 30 | pyre2 = "~=0.3.6" 31 | # py-cord = { git = "https://github.com/pycord-development/pycord" } 32 | py-cord = "~=2.1.1" 33 | 34 | sembed = "~=1.1.2" 35 | sentry-sdk = "~=1.5.0" 36 | texttable = "~=1.6.4" 37 | topggpy = "~=1.1.0" 38 | websockets = "~=10.1" 39 | xmltodict = "~=0.12.0" 40 | youtube-dl = "~=2021.6.6" 41 | 42 | [tool.poetry.dev-dependencies] 43 | pip-chill = "~=1.0.1" 44 | flake8 = "~=4.0.1" 45 | black = "~=21.11b1" 46 | pyproject-flake8 = "^0.0.1-alpha.2" 47 | isort = "^5.10.1" 48 | watchdog = "^2.1.6" 49 | 50 | [tool.black] 51 | line-length = 120 52 | extend-exclude = "__gi_*" 53 | 54 | [tool.flake8] 55 | max-line-length = 120 56 | extend-ignore = "E203,W503" 57 | extend-exclude = ".venv,__gi_*" 58 | 59 | [tool.isort] 60 | profile = "black" 61 | 62 | [build-system] 63 | requires = ["poetry-core>=1.0.0", "setuptools", "wheel"] 64 | build-backend = "poetry.core.masonry.api" 65 | -------------------------------------------------------------------------------- /save.sample: -------------------------------------------------------------------------------- 1 | {'gc': [], 'gm': [], 'il': [], 'gb': {}, 'rl': {}, 'al': {}, 'pc': {}, 'pp': {}, 'pa': {}, 'snc': [], 'snp': {}, 'fs': {}, 'ba': {}, 'bs': [], 'cc': {}, 'qu': {}, 'sp': {}, 'ts': {}} --------------------------------------------------------------------------------