├── .gitignore ├── Codes ├── config.json5 ├── main.py ├── ter │ ├── __init__.py │ ├── bot.py │ └── cogs │ │ ├── __init__.py │ │ ├── __lang.py │ │ ├── base_cog.py │ │ ├── bouyoumi_cog.py │ │ ├── raid_cog.py │ │ └── trans_cog.py ├── util.py └── window.py ├── LICENSE ├── PyInstaller ├── .gitignore └── build-release.sh ├── README.md ├── README.pdf └── Venvs ├── .gitignore └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # My 2 | .vscode/ 3 | 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | # *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # pytype static type analyzer 139 | .pytype/ 140 | 141 | # Cython debug symbols 142 | cython_debug/ 143 | -------------------------------------------------------------------------------- /Codes/config.json5: -------------------------------------------------------------------------------- 1 | // Twitch EventSub Response Bot - Config (v3.0.0--) 2 | { 3 | // ■ メッセージ送信先となるチャンネルに関する設定たち 4 | "messageChannel": { 5 | // チャンネル配信者のユーザー名(チャンネルURLの末尾) 6 | // (* 全て英小文字でも、英大文字と英小文字が混在していても、どちらでも可) 7 | "broadcasterUserName": "yourchannelname", 8 | }, 9 | // 10 | // ■ メッセージ送信を行うボットに関する設定たち 11 | "bot": { 12 | // ボットとして運用するユーザーのOAuthアクセストークン 13 | // (* 使う機能が要求する権限をボットとなるユーザーが持っていること) 14 | // (* 使う機能が要求する権限をトークンが持っていること) 15 | "oAuthAccessToken": "9y0urb0tuser0authacceesst0ken9", 16 | // 17 | // チャンネルで表示されるボットの名前の色 18 | // (* トークンが "user:manage:chat_color" 権限を持っていること) 19 | // "blue", "blue_violet", "cadet_blue", "chocolate", "coral", 20 | // "dodger_blue", "firebrick", "golden_rod", "green", "hot_pink", 21 | // "orange_red", "red", "sea_green", "spring_green", "yellow_green", 22 | // "#RRGGBB" (* Turboユーザーのみ設定可), "doNotChange" (* 色を変えない), 23 | "nameColor": "blue", 24 | }, 25 | // 26 | // ■ イベントたちに対する応答たちに関する設定 27 | "responses": { 28 | // レイド 29 | "/raid": [ 30 | // [ 31 | // 送信前の待機時間(秒), 公式コマンド・メッセージ (* ユーザーコマンドを含む), 32 | // (* 必要あれば追加情報1, 追加情報2, ...,) 33 | // ] の組たち 34 | // (* 上から順に1つずつ実行) 35 | // 36 | // コマンドやメッセージの中で置換される文字列たち 37 | // {{raidBroadcasterUserName}} -> レイド元のユーザー名(チャンネルURLの末尾) 38 | // 39 | // メッセージ (* ユーザーコマンドを含む) の例 40 | [ 5, "!raided {{raidBroadcasterUserName}}", ], 41 | // 42 | // 公式コマンドの例 43 | // /shoutout 44 | // (* ボットとなるユーザーが モデレーター 以上であること) 45 | // (* トークンが "moderator:manage:shoutouts" 権限を持っていること) 46 | [10, "/shoutout", ], 47 | // 48 | // (* 公式コマンド・メッセージを実行しない場合は、 49 | // 該当する [ ] の行を削除するか、行の頭に // を挿入(コメントアウト)) 50 | // [10, "Sample message", ], 51 | // 52 | // (* ほかのコマンドは、要望があれば追加対応するかも) 53 | ], 54 | // 55 | // (* ほかのイベントは、要望があれば追加対応するかも) 56 | }, 57 | // 58 | // ■ メッセージたちに対する翻訳に関する設定 59 | "translation": { 60 | // 使用する翻訳サービスたちと優先使用順位 61 | // (* 各メッセージについて、翻訳できるまで、上に設定したサービスから順に使用) 62 | "servicesWithKeyOrURL": [ 63 | // DeepL翻訳で、認証キーを使用しない場合 (* 不具合がなければ変更不要) 64 | // エラーが出て翻訳されない場合が多いため、コメントアウト中 65 | // ["deeplTranslate", "https://www2.deepl.com/jsonrpc", ], 66 | // 67 | // Google翻訳で、 Google アカウント にひも付いた設定を必要としない場合 68 | // "translate.google.????/" 69 | // (* いずれかの国のURLを設定するが、不具合がなければ変更不要) 70 | ["googleTrans", "translate.google.co.jp", ], 71 | // 72 | // (* サービスを使用しない場合は、 73 | // 該当する [ ] の行を削除するか、行の頭に // を挿入(コメントアウト)) 74 | // 75 | // DeepL翻訳で、認証キーを使用する場合 76 | // ["deeplKey", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx", ], 77 | // 78 | // Google翻訳で、 Google Apps Script (GAS) を使用する場合 79 | // "https://script.google.com/macros/s/????/exec" (* GASのURL) 80 | // ["googleGAS", "https://script.google.com/macros/s/????/exec", ], 81 | ], 82 | // 83 | // 翻訳元言語の判定に使用するサービス (* 不具合がなければ変更不要) 84 | "fromLanguageDetection": "translate.google.co.jp", 85 | // 86 | // 翻訳しないメッセージたち 87 | "messagesToIgnore": { 88 | // 送信ユーザー名たち 89 | // (* 英大文字と英小文字が混在していても可) 90 | // (* ボットとして運用するユーザーについては、ここに記載がない場合、 91 | // 手動で投稿したメッセージたちは翻訳を実行) 92 | "senderUserNames": ["nightbot", "", ], 93 | // 94 | // 翻訳元言語たち 95 | // (* ノルウェー語, 中国語は、 96 | // DeepL翻訳とGoogle翻訳とで略称が異なるため、 97 | // これらの言語を設定する場合は両方の略称を併記するのがよい) 98 | // DeepL翻訳(認証キー使用, 不使用), Google翻訳すべてで利用可能な言語たち 99 | // "BG" (Bulgarian), "CS" (Czech), "DA" (Danish), 100 | // "DE" (German), "EL" (Greek), "EN" (English), "ES" (Spanish), 101 | // "ET" (Estonian), "FI" (Finnish), "FR" (French), 102 | // "HU" (Hungarian), "IT" (Italian), "JA" (Japanese), 103 | // "LT" (Lithuanian), "LV" (Latvian), "NL" (Dutch), 104 | // "PL" (Polish), "PT" (Portuguese), "RO" (Romanian), 105 | // "RU" (Russian), "SK" (Slovak), "SL" (Slovenian), 106 | // "SV" (Swedish), 107 | // DeepL翻訳(認証キー使用), Google翻訳で利用可能な言語たち 108 | // "ID" (Indonesian), "KO" (Korean), "TR" (Turkish), 109 | // "UK" (Ukrainian), 110 | // DeepL翻訳(認証キー使用, 不使用)で利用可能な言語たち 111 | // "ZH" (Chinese) 112 | // (* Google翻訳では "zh-cn" または "zh-tw" ), 113 | // DeepL翻訳(認証キー使用)でのみ利用可能な言語たち 114 | // "NB" (Norwegian), 115 | // (* Google翻訳では "no" ) 116 | // Google翻訳でのみ利用可能な言語たち 117 | // "af" (afrikaans), "sq" (albanian), "am" (amharic), 118 | // "ar" (arabic), "hy" (armenian), "az" (azerbaijani), 119 | // "eu" (basque), "be" (belarusian), "bn" (bengali), 120 | // "bs" (bosnian), "ca" (catalan), "ceb" (cebuano), 121 | // "ny" (chichewa), 122 | // "zh-cn" (chinese (simplified)), "zh-tw" (chinese (traditional)), 123 | // (* DeepL翻訳(認証キー使用, 不使用)では "ZH" ) 124 | // "co" (corsican), "hr" (croatian), "eo" (esperanto), 125 | // "tl" (filipino), "fy" (frisian), "gl" (galician), 126 | // "ka" (georgian), "gu" (gujarati), "ht" (haitian creole), 127 | // "ha" (hausa), "haw" (hawaiian), "iw" (hebrew), 128 | // "he" (hebrew), "hi" (hindi), "hmn" (hmong), 129 | // "is" (icelandic), "ig" (igbo), "ga" (irish), 130 | // "jw" (javanese), "kn" (kannada), "kk" (kazakh), 131 | // "km" (khmer), "ku" (kurdish (kurmanji)), "ky" (kyrgyz), 132 | // "lo" (lao), "la" (latin), "lb" (luxembourgish), 133 | // "mk" (macedonian), "mg" (malagasy), "ms" (malay), 134 | // "ml" (malayalam), "mt" (maltese), "mi" (maori), 135 | // "mr" (marathi), "mn" (mongolian), "my" (myanmar (burmese)), 136 | // "ne" (nepali), 137 | // "no" (norwegian), 138 | // (* DeepL翻訳(認証キー使用)では "NB" ) 139 | // "or" (odia), "ps" (pashto), "fa" (persian), "pa" (punjabi), 140 | // "sm" (samoan), "gd" (scots gaelic), "sr" (serbian), 141 | // "st" (sesotho), "sn" (shona), "sd" (sindhi), 142 | // "si" (sinhala), "so" (somali), "su" (sundanese), 143 | // "sw" (swahili), "tg" (tajik), "ta" (tamil), "te" (telugu), 144 | // "th" (thai), "ur" (urdu), "ug" (uyghur), "uz" (uzbek), 145 | // "vi" (vietnamese), "cy" (welsh), "xh" (xhosa), 146 | // "yi" (yiddish), "yo" (yoruba), "zu" (zulu), 147 | // (* ほかにもあるが、本ボットでは未対応) 148 | "fromLanguages": ["", ], 149 | // 150 | // ユーザーコマンドの接頭辞たち (* "_" は記載がなくても翻訳を不実行) 151 | "userCommandPrefixes": ["!", "", ], 152 | // 153 | // メッセージ内に含まれる文字列たち 154 | "stringsInMessage": ["http", "", ], 155 | }, 156 | // 157 | // 翻訳先言語(たち) 158 | // (* 英語, ノルウェー語, ポルトガル語, 中国語は、 159 | // DeepL翻訳(認証キー使用, 不使用)とGoogle翻訳とで略称が異なるため、 160 | // これらの言語を設定する場合は両方の略称を併記するのがよい) 161 | // DeepL翻訳(認証キー使用, 不使用), Google翻訳すべてで利用可能な言語たち 162 | // "BG" (Bulgarian), "CS" (Czech), "DA" (Danish), "DE" (German), 163 | // "EL" (Greek), "ES" (Spanish), "ET" (Estonian), "FI" (Finnish), 164 | // "FR" (French), "HU" (Hungarian), "IT" (Italian), 165 | // "JA" (Japanese), "LT" (Lithuanian), "LV" (Latvian), 166 | // "NL" (Dutch), "PL" (Polish), "RO" (Romanian), "RU" (Russian), 167 | // "SK" (Slovak), "SL" (Slovenian), "SV" (Swedish), 168 | // DeepL翻訳(認証キー使用), Google翻訳で利用可能な言語たち 169 | // "ID" (Indonesian), "KO" (Korean), "TR" (Turkish), 170 | // "UK" (Ukrainian), 171 | // DeepL翻訳(認証キー不使用), Google翻訳で利用可能な言語たち 172 | // "EN" (English), 173 | // (* DeepL翻訳(認証キー使用)では "EN-GB" または "EN-US" ) 174 | // "PT" (Portuguese), 175 | // (* DeepL翻訳(認証キー使用)では "PT-BR" または "PT-PT" ) 176 | // DeepL翻訳(認証キー使用, 不使用)で利用可能な言語たち 177 | // "ZH" (Chinese) 178 | // (* Google翻訳では "zh-cn" または "zh-tw" ), 179 | // DeepL翻訳(認証キー使用)でのみ利用可能な言語たち 180 | // "EN-GB" (English (British)), "EN-US" (English (American)), 181 | // (* DeepL翻訳(認証キー不使用), Google翻訳では "en" ) 182 | // "NB" (Norwegian), 183 | // (* Google翻訳では "no" ) 184 | // "PT-BR" (Portuguese (Brazilian)), "PT-PT" (Portuguese (European)), 185 | // (* DeepL翻訳(認証キー不使用), Google翻訳では "pt") 186 | // Google翻訳でのみ利用可能な言語たち 187 | // "af" (afrikaans), "sq" (albanian), "am" (amharic), 188 | // "ar" (arabic), "hy" (armenian), "az" (azerbaijani), 189 | // "eu" (basque), "be" (belarusian), "bn" (bengali), 190 | // "bs" (bosnian), "ca" (catalan), "ceb" (cebuano), 191 | // "ny" (chichewa), 192 | // "zh-cn" (chinese (simplified)), "zh-tw" (chinese (traditional)), 193 | // (* DeepL翻訳(認証キー使用, 不使用)では "ZH" ) 194 | // "co" (corsican), "hr" (croatian), "eo" (esperanto), 195 | // "tl" (filipino), "fy" (frisian), "gl" (galician), 196 | // "ka" (georgian), "gu" (gujarati), "ht" (haitian creole), 197 | // "ha" (hausa), "haw" (hawaiian), "iw" (hebrew), "he" (hebrew), 198 | // "hi" (hindi), "hmn" (hmong), "is" (icelandic), "ig" (igbo), 199 | // "ga" (irish), "jw" (javanese), "kn" (kannada), "kk" (kazakh), 200 | // "km" (khmer), "ku" (kurdish (kurmanji)), "ky" (kyrgyz), 201 | // "lo" (lao), "la" (latin), "lb" (luxembourgish), 202 | // "mk" (macedonian), "mg" (malagasy), "ms" (malay), 203 | // "ml" (malayalam), "mt" (maltese), "mi" (maori), "mr" (marathi), 204 | // "mn" (mongolian), "my" (myanmar (burmese)), "ne" (nepali), 205 | // "no" (norwegian), 206 | // (* DeepL翻訳(認証キー使用)では "NB" ) 207 | // "or" (odia), "ps" (pashto), "fa" (persian), "pa" (punjabi), 208 | // "sm" (samoan), "gd" (scots gaelic), "sr" (serbian), 209 | // "st" (sesotho), "sn" (shona), "sd" (sindhi), "si" (sinhala), 210 | // "so" (somali), "su" (sundanese), "sw" (swahili), "tg" (tajik), 211 | // "ta" (tamil), "te" (telugu), "th" (thai), "ur" (urdu), 212 | // "ug" (uyghur), "uz" (uzbek), "vi" (vietnamese), "cy" (welsh), 213 | // "xh" (xhosa), "yi" (yiddish), "yo" (yoruba), "zu" (zulu), 214 | // (* ほかにもあるが、本ボットでは未対応) 215 | "toLanguages": { 216 | // 既定の翻訳先言語(たち) 217 | "defaults": ["JA", "", ], 218 | // 219 | // 翻訳元言語が既定の翻訳先言語であった場合の、代わりの翻訳先言語(たち) 220 | "onesIfFromLanguageIsInDefaults": ["EN-US", "EN", "", ], 221 | }, 222 | // 223 | // 翻訳先メッセージの構成 224 | // 225 | // 翻訳先メッセージの中で置換される文字列たち 226 | // {{senderUserName}} -> 送信ユーザー名(チャンネルURLの末尾) 227 | // {{senderDisplayName}} -> 送信ユーザーの表示名 228 | // {{toMessage}} -> 翻訳された送信ユーザーのメッセージ 229 | // {{fromLanguage}} -> 翻訳元言語 230 | // {{toLanguage}} -> 翻訳先言語 231 | "messagesFormat": "{{senderUserName}}: {{toMessage}} ({{fromLanguage}} > {{toLanguage}})", 232 | }, 233 | // 234 | // ■ メッセージたちの 棒読みちゃん への受け渡しに関する設定 235 | "bouyomiChan": { 236 | // メッセージたちを受け渡すか否か 237 | // true (受け渡す), false (受け渡さない) 238 | "sendsMessages": false, 239 | // 240 | // 本ボット起動時に、自動で 棒読みちゃん を起動させる場合の、 241 | // 棒読みちゃん の実行ファイルの絶対パス 242 | // (* 例: "C:\\Users\\youru\\Documents\\SoftwareWithoutInstaller\\BouyomiChan_0_1_11_0_Beta21\\BouyomiChan.exe" ) 243 | // (* 「 \ 」(フォルダー区切りの記号)は 「 \\ 」(同じ記号2つ)に変更すること) 244 | // (* "" とした場合や、間違ったパスを設定した場合は、自動で起動されない) 245 | // (* 自動で起動させない場合は、本ボット起動前に 棒読みちゃん を手動で起動させておくこと) 246 | // (* 自動で起動させた場合は、本ボットの停止と共に 棒読みちゃん も自動で停止) 247 | "autoRunKillPath": "C:\\Users\\youru\\Documents\\SoftwareWithoutInstaller\\BouyomiChan_0_1_11_0_Beta21\\BouyomiChan.exe", 248 | // 249 | // 使用するローカル(本ボットを動かすPC)HTTPサーバ (localhost) のポート番号 250 | // (* 棒読みちゃん での設定値 (49152~65535) に合わせること) 251 | "portNo": 60080, 252 | // 253 | // 受け渡すメッセージたちに対する制限 254 | "limitsWhenPassing": { 255 | // 送信ユーザーのユーザー名ないし表示名の、末尾の算用数字部分を省略するか否か 256 | // true (省略する), false (省略しない) 257 | "ignoresSenderNameSuffixNum": true, 258 | // 259 | // 送信ユーザーのユーザー名ないし表示名の、先頭からの文字数の上限 (* 0~25) 260 | "numSenderNameCharacters": 25, 261 | // 262 | // 先頭からのエモート(スタンプ)数の上限 (* 0~125) 263 | "numEmotes": 3, 264 | }, 265 | // 266 | // 受け渡さないメッセージたち 267 | "messagesToIgnore": { 268 | // 送信ユーザー名たち 269 | // (* 英大文字と英小文字が混在していても可) 270 | // (* ボットとして運用するユーザーについては、ここに記載がない場合、 271 | // 手動で投稿したメッセージたちは受け渡しを実行) 272 | "senderUserNames": ["nightbot", "", ], 273 | // 274 | // ユーザーコマンドの接頭辞たち (* "_" は記載がなくても受け渡しを不実行) 275 | "userCommandPrefixes": ["!", "", ], 276 | // 277 | // メッセージ内に含まれる文字列たち 278 | "stringsInMessage": ["", ], 279 | }, 280 | // 281 | // 受け渡すメッセージの構成 282 | // 283 | // 受け渡すメッセージの中で置換される文字列たち 284 | // {{senderUserName}} -> 送信ユーザー名(チャンネルURLの末尾) 285 | // {{senderDisplayName}} -> 送信ユーザーの表示名 286 | // {{senderMessage}} -> 送信ユーザーのメッセージ 287 | "messagesFormat": "{{senderDisplayName}} san: {{senderMessage}}", 288 | }, 289 | } 290 | -------------------------------------------------------------------------------- /Codes/main.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import os 6 | import pathlib 7 | import signal 8 | import sys 9 | import time 10 | from typing import Any 11 | 12 | import ter 13 | import util 14 | import window 15 | 16 | # Constants 17 | ver_no: str = ter.__version__.casefold().strip() 18 | uses_tkinter_window: bool = True 19 | return_code_for_restrt: int = 3 20 | sleep_before_restart_s: int = 4 21 | 22 | 23 | # Main 24 | def main(_uses_tkinter_window: bool) -> int: 25 | if ( 26 | _uses_tkinter_window is True 27 | and window.TkinterConsoleWindow.is_aleady_opened() is False 28 | ): 29 | window.TkinterConsoleWindow.lets_thread_close_window = False 30 | asyncio.new_event_loop().run_in_executor( 31 | None, window.TkinterConsoleWindow.open, ver_no 32 | ) 33 | while window.TkinterConsoleWindow.is_aleady_opened() is False: 34 | time.sleep(1.0 / 64.0) 35 | sys.stdout = window.TkinterConsoleWindow 36 | sys.stderr = window.TkinterConsoleWindow 37 | # 38 | title: str = ( 39 | "-" * 20 + f" Twitch EventSub Response Bot (v{ver_no}) " + "-" * 20 40 | ) 41 | print(f"{title}") 42 | # 43 | print("[Preprocess]") 44 | base_dir: pathlib.Path = pathlib.Path( 45 | rf"{os.path.abspath(os.path.dirname(sys.argv[0]))}" 46 | ) 47 | # 48 | cj_file: pathlib.Path = base_dir.joinpath("config.json5") 49 | print(f" JSON5 file path = {cj_file}") 50 | print(" parsing this file ... ", end="") 51 | cj_obj: dict[str, Any] = util.JSON5Reader.open_and_load(cj_file) 52 | cj_obj["verNo"] = ver_no 53 | cj_obj["returnCodeForRestrt"] = return_code_for_restrt 54 | print("done.") 55 | print("") 56 | # 57 | print("[Activation of Bot]") 58 | b: ter.TERBot = ter.TERBot(cj_obj) 59 | 60 | def __post_process(_b: ter.TERBot, _title: str) -> None: 61 | signal.signal(signal.SIGBREAK, signal.SIG_IGN) 62 | signal.signal(signal.SIGINT, signal.SIG_IGN) 63 | signal.signal(signal.SIGTERM, signal.SIG_IGN) 64 | # 65 | print("[Postprocess]") 66 | print(f" Return code = {_b.return_code}", end="") 67 | if _b.return_code == return_code_for_restrt: 68 | print(" (Restart)") 69 | else: 70 | print("") 71 | _b.kill_bouyomi_process() 72 | print("") 73 | print("-" * len(_title)) 74 | # 75 | if _b.return_code != return_code_for_restrt: 76 | window.TkinterConsoleWindow.lets_thread_close_window = True 77 | # 78 | signal.signal(signal.SIGBREAK, signal.SIG_DFL) 79 | signal.signal(signal.SIGINT, signal.SIG_DFL) 80 | signal.signal(signal.SIGTERM, signal.SIG_DFL) 81 | 82 | print("[Run of Bot]") 83 | signal.signal(signal.SIGBREAK, lambda *_: __post_process(b, title)) 84 | signal.signal(signal.SIGTERM, lambda *_: __post_process(b, title)) 85 | try: 86 | if _uses_tkinter_window is True: 87 | window.TkinterConsoleWindow.add_func_kill_bot( 88 | b.kill_self_without_print 89 | ) 90 | b.run() 91 | finally: 92 | __post_process(b, title) 93 | return b.return_code 94 | 95 | 96 | if __name__ == "__main__": 97 | return_code_main: int = 0 98 | # 99 | while True: 100 | asyncio.set_event_loop(asyncio.new_event_loop()) 101 | return_code_main = main(uses_tkinter_window) 102 | # 103 | if return_code_main == return_code_for_restrt: 104 | print("") 105 | print(f"Restart after {sleep_before_restart_s} s.") 106 | print("") 107 | time.sleep(float(sleep_before_restart_s)) 108 | else: 109 | break 110 | # 111 | sys.exit(return_code_main) 112 | -------------------------------------------------------------------------------- /Codes/ter/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.3.2" 2 | 3 | from .bot import TERBot 4 | 5 | __all__ = ["TERBot"] 6 | -------------------------------------------------------------------------------- /Codes/ter/bot.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | from typing import Any 5 | 6 | from twitchio import Channel, Chatter, PartialUser 7 | from twitchio.ext import commands 8 | 9 | from .cogs import TERBouyomiCog, TERRaidCog, TERTransCog 10 | 11 | 12 | # Classes 13 | class TERBot(commands.Bot): 14 | def __init__(self, _j: dict[str, Any]) -> None: 15 | print(" Initializing bot ...") 16 | self.__j: dict[str, Any] = _j 17 | self.__token: str = str(self.__j["bot"]["oAuthAccessToken"]).strip() 18 | self.__prefix: str = "_" 19 | self.__return_code: int = 1 20 | # 21 | broadcaster_user_name: str = ( 22 | str(self.__j["messageChannel"]["broadcasterUserName"]) 23 | .casefold() 24 | .strip() 25 | ) 26 | print(f" Message channel user name = {broadcaster_user_name}") 27 | print(f" Bot token length = {len(self.__token)}") 28 | super().__init__( 29 | self.__token, 30 | prefix=self.__prefix, 31 | initial_channels=[ 32 | broadcaster_user_name, 33 | ], 34 | ) 35 | print(" done.") 36 | print("") 37 | 38 | @property 39 | def return_code(self) -> int: 40 | return self.__return_code 41 | 42 | async def event_channel_joined(self, channel: Channel): 43 | print(" Joining channel ...") 44 | print(f" Channel name = {channel.name}") 45 | print(" done.") 46 | print("") 47 | # 48 | await channel.send(f"/me bot for {self.__prefix} has joined.") 49 | 50 | async def event_ready(self) -> None: 51 | print(" Making bot ready ...") 52 | assert self.user_id is not None, "Bot user ID is unknown." 53 | print(f" Bot user ID = {self.user_id}") 54 | assert self.nick is not None, "Bot user name is unknown." 55 | print(f" Bot user name = {self.nick}") 56 | # 57 | print(" Bot commands") 58 | for c_name in self.commands.keys(): 59 | print(f" {self.__prefix}{c_name}") 60 | # 61 | print(" Bot cogs") 62 | pu: PartialUser = self.create_user(self.user_id, self.nick) 63 | self.add_cog( 64 | TERRaidCog( 65 | self.__token, 66 | pu, 67 | {}, 68 | self.__j["responses"]["/raid"], 69 | ) 70 | ) 71 | self.add_cog( 72 | TERBouyomiCog( 73 | self.__token, 74 | pu, 75 | self.__j["bouyomiChan"], 76 | [], 77 | self.__prefix, 78 | ) 79 | ) 80 | self.add_cog( 81 | TERTransCog( 82 | self.__token, 83 | pu, 84 | self.__j["translation"], 85 | [], 86 | self.__prefix, 87 | ) 88 | ) 89 | # 90 | # ToDo: ★ (Cog) 別のCogを開発した場合は、登録処理をここに実装する 91 | # self.add_cog( 92 | # TER????Cog( 93 | # self.__token, 94 | # pu, 95 | # {}, 96 | # [], 97 | # ) 98 | # ) 99 | # 100 | name_color: str = str(self.__j["bot"]["nameColor"]).casefold().strip() 101 | if name_color != "doNotChange".casefold().strip(): 102 | print(f" Setting bot name color = {name_color} ... ", end="") 103 | # 104 | await self.update_chatter_color( 105 | self.__token, self.user_id, name_color 106 | ) 107 | print("done.") 108 | print(" done.") 109 | print("") 110 | 111 | async def event_command_error( 112 | self, context: commands.Context, error: Exception 113 | ) -> None: 114 | return 115 | 116 | @commands.command(name="test") 117 | async def __test(self, ctx: commands.Context): 118 | print( 119 | f" Testing bot (v{str(self.__j['verNo']).casefold().strip()}) ..." 120 | ) 121 | print(f" Channel name = {ctx.channel.name}") 122 | print(f" Bot user ID = {self.user_id}") 123 | print(f" Bot user name = {self.nick}") 124 | print(" Bot commands") 125 | for c_name in self.commands.keys(): 126 | print(f" {self.__prefix}{c_name}") 127 | print(" Bot cogs") 128 | for c_name in self.cogs.keys(): 129 | print(f" {c_name}") 130 | # 131 | await ctx.send(f"/me bot for {self.__prefix} is alive.") 132 | print(" done.") 133 | print("") 134 | 135 | @commands.command(name="restart") 136 | async def __restart(self, ctx: commands.Context): 137 | ctx.message.content = ( 138 | f"{self.__prefix}kill {str(self.__j['returnCodeForRestrt'])}" 139 | ) 140 | # 141 | await self.__kill(ctx) 142 | 143 | @commands.command(name="kill") 144 | async def __kill(self, ctx: commands.Context): 145 | if self.__is_by_channel_broadcaster_or_myself(ctx) is True: 146 | print(" Killing bot ... ") 147 | self.loop.stop() 148 | # 149 | await ctx.send(f"/me bot for {self.__prefix} has stopped.") 150 | # 151 | is_valid_return_code: bool = False 152 | return_code_str: str = ( 153 | str(ctx.message.content) 154 | .strip() 155 | .removeprefix(f"{self.__prefix}kill") 156 | .strip() 157 | ) 158 | if ( 159 | len(return_code_str) <= 3 160 | and return_code_str.isdecimal() is True 161 | ): 162 | return_code_int: int = int(return_code_str) 163 | if 0 <= return_code_int <= 255: 164 | self.__return_code = return_code_int 165 | is_valid_return_code = True 166 | else: 167 | self.__return_code = 0 168 | is_valid_return_code = False 169 | else: 170 | self.__return_code = 0 171 | is_valid_return_code = ( 172 | True if len(return_code_str) == 0 else False 173 | ) 174 | print(f" Return code = {self.__return_code}", end="") 175 | if self.__return_code == int(self.__j["returnCodeForRestrt"]): 176 | print(" (Restart)") 177 | else: 178 | print("") 179 | if is_valid_return_code is False: 180 | print(" * Invalid return code input") 181 | print(" done.") 182 | print("") 183 | 184 | def __is_by_channel_broadcaster_or_myself( 185 | self, ctx: commands.Context 186 | ) -> bool: 187 | if type(ctx.author) is Chatter: 188 | if ctx.author.is_broadcaster is True: 189 | return True 190 | elif str(ctx.author.id) == str(self.user_id): 191 | return True 192 | else: 193 | return False 194 | else: 195 | return False 196 | 197 | def kill_self_without_print(self) -> None: 198 | self.__return_code = 0 199 | self.loop.stop() 200 | 201 | def kill_bouyomi_process(self) -> None: 202 | c: commands.Cog | None = self.get_cog("TERBouyomiCog") 203 | if type(c) is TERBouyomiCog: 204 | c.kill_process() 205 | -------------------------------------------------------------------------------- /Codes/ter/cogs/__init__.py: -------------------------------------------------------------------------------- 1 | from .bouyoumi_cog import TERBouyomiCog 2 | from .raid_cog import TERRaidCog 3 | from .trans_cog import TERTransCog 4 | 5 | __all__ = ["TERBouyomiCog", "TERRaidCog", "TERTransCog"] 6 | -------------------------------------------------------------------------------- /Codes/ter/cogs/__lang.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import enum 5 | from typing import ClassVar 6 | 7 | import deepl 8 | import googletrans 9 | import regex 10 | 11 | 12 | # Classes 13 | @enum.unique 14 | class TERTransService(enum.Enum): 15 | DEEPLKEY = enum.auto() 16 | DEEPLTRANSLATE = enum.auto() 17 | GOOGLETRANS = enum.auto() 18 | GOOGLEGAS = enum.auto() 19 | 20 | @classmethod 21 | def get(cls, s: str) -> TERTransService | None: 22 | for e in [_e for _e in TERTransService]: 23 | if s.casefold().strip() == e.name.casefold().strip(): 24 | return e 25 | return None 26 | 27 | 28 | class TERLang: 29 | __JAPANESE_KANJIS_SEQUENCE_1ST: ClassVar[str] = ( 30 | "一丁七万丈三上下不与丑且世丘丙丞両並中串丸丹主乃久之乍乎乏乗乙九乞也乱乳乾亀" 31 | "了予争事二云互五井亘亙些亜亡交亥亦亨享京亭亮人什仁仇今介仏仔仕他付仙代令以仮" 32 | "仰仲件任企伊伍伎伏伐休会伝伯伴伶伸伺似伽佃但位低住佐佑体何余作佳併佼使侃例侍" 33 | "供依侠価侭侮侯侵侶便係促俄俊俗保信俣修俳俵俸俺倉個倍倒倖候借倣値倦倫倭倶倹偉" 34 | "偏停健偲側偵偶偽傍傑傘備催傭債傷傾僅働像僑僕僚僧僻儀億儒償優儲允元兄充兆兇先" 35 | "光克免兎児党兜入全八公六共兵其具典兼内円冊再冒冗写冠冥冨冬冴冶冷凄准凋凌凍凝" 36 | "凡処凧凪凱凶凸凹出函刀刃分切刈刊刑列初判別利到制刷券刺刻剃則削前剖剛剣剤剥副" 37 | "剰割創劃劇劉力功加劣助努劫励労効劾勃勅勇勉動勘務勝募勢勤勧勲勺勾勿匁匂包化北" 38 | "匙匝匠匡匪匹区医匿十千升午半卑卒卓協南単博卜占卦卯印危即却卵卸卿厄厘厚原厨厩" 39 | "厭厳去参又叉及友双反収叔取受叙叛叡叢口古句叩只叫召可台叱史右叶号司吃各合吉吊" 40 | "吋同名后吏吐向君吟吠否含吸吹吻吾呂呆呈呉告呑周呪味呼命咋和咲咳咽哀品哉員哨哩" 41 | "哲唄唆唇唐唖唯唱唾啄商問啓善喉喋喚喜喝喧喪喫喬喰営嗣嘆嘉嘗嘘嘩嘱噂噌噛器噴噸" 42 | "噺嚇嚢囚四回因団困囲図固国圃圏園土圧在圭地坂均坊坐坑坤坦坪垂型垢垣埋城埜域埠" 43 | "埴執培基埼堀堂堅堆堕堤堪堰報場堵堺塀塁塊塑塔塗塘塙塚塞塩填塵塾境墓増墜墨墳墾" 44 | "壁壇壊壌壕士壬壮声壱売壷変夏夕外夙多夜夢大天太夫央失夷奄奇奈奉奏契奔套奥奨奪" 45 | "奮女奴好如妃妄妊妓妖妙妥妨妬妹妻妾姉始姐姑姓委姥姦姪姫姶姻姿威娃娘娠娩娯娼婁" 46 | "婆婚婦婿媒媛嫁嫉嫌嫡嬉嬢嬬嬰子孔字存孜孝孟季孤学孫宅宇守安宋完宍宏宕宗官宙定" 47 | "宛宜宝実客宣室宥宮宰害宴宵家容宿寂寄寅密富寒寓寛寝察寡寧審寮寵寸寺対寿封専射" 48 | "将尉尊尋導小少尖尚尤尭就尺尻尼尽尾尿局居屈届屋屍屑展属屠屡層履屯山岐岡岨岩岬" 49 | "岱岳岸峠峡峨峯峰島峻崇崎崖崩嵐嵩嵯嶋嶺巌川州巡巣工左巧巨差己巳巴巷巻巽巾市布" 50 | "帆希帖帝帥師席帯帰帳常帽幅幌幕幡幣干平年幸幹幻幼幽幾庁広庄庇床序底庖店庚府度" 51 | "座庫庭庵庶康庸廃廉廊廓廟廠延廷建廻廼廿弁弄弊式弐弓弔引弗弘弛弟弥弦弧弱張強弼" 52 | "弾彊当形彦彩彪彫彬彰影役彼往征径待律後徐徒従得御復循微徳徴徹徽心必忌忍志忘忙" 53 | "応忠快念忽怒怖怜思怠急性怨怪怯恋恐恒恕恢恥恨恩恭息恰恵悉悌悔悟悠患悦悩悪悲悶" 54 | "悼情惇惑惚惜惟惣惨惰想惹愁愈愉意愚愛感慈態慌慎慕慢慣慧慨慮慰慶慾憂憎憐憤憧憩" 55 | "憲憶憾懇懐懲懸戊戎成我戒或戚戟戦戯戴戸戻房所扇扉手才打払托扮扱扶批承技抄把抑" 56 | "投抗折抜択披抱抵抹押抽担拍拐拒拓拘拙招拝拠拡括拭拳拶拷拾持指按挑挙挟挨挫振挺" 57 | "挽挿捉捌捕捗捜捧捨据捲捷捺捻掃授掌排掘掛掠採探接控推掩措掬掲掴掻揃描提揖揚換" 58 | "握揮援揺損搬搭携搾摂摘摩摸摺撃撒撚撞撤撫播撮撰撲撹擁操擢擦擬擾支改攻放政故敏" 59 | "救敗教敢散敦敬数整敵敷文斉斌斎斐斑斗料斜斡斤斥斧斬断斯新方於施旅旋族旗既日旦" 60 | "旧旨早旬旭旺昂昆昇昌明昏易昔星映春昧昨昭是昼時晃晋晒晦晩普景晴晶智暁暇暑暖暗" 61 | "暢暦暫暮暴曇曙曜曝曲曳更書曹曽曾替最月有朋服朔朕朗望朝期木未末本札朱朴机朽杉" 62 | "李杏材村杓杖杜束条杢来杭杯東杵杷松板枇析枕林枚果枝枠枢枯架柁柄柊柏某柑染柔柘" 63 | "柚柱柳柴柵査柾柿栂栃栄栓栖栗校栢株栴核根格栽桁桂桃案桐桑桓桔桜桝桟桧桶梁梅梓" 64 | "梗梢梧梨梯械梱梶梼棄棉棋棒棚棟森棲棺椀椅椋植椎椙椛検椴椿楊楓楕楚楠楢業楯楳極" 65 | "楼楽概榊榎榔榛構槌槍様槙槻槽樋樗標樟模権横樫樵樹樺樽橋橘機橡橿檀檎櫓櫛櫨欄欝" 66 | "欠次欣欧欲欺欽款歌歎歓止正此武歩歪歯歳歴死殆殉殊残殖殴段殺殻殿毅母毎毒比毘毛" 67 | "氏民気水氷永氾汀汁求汎汐汗汚汝江池汰汲決汽沃沈沌沓沖沙没沢沫河沸油治沼沿況泉" 68 | "泊泌法泡波泣泥注泰泳洋洗洛洞津洩洪洲活派流浄浅浜浦浩浪浬浮浴海浸消涌涙涛涜涯" 69 | "液涼淀淋淑淘淡淫深淳淵混添清渇済渉渋渓渚減渠渡渥渦温測港湊湖湘湛湧湯湾湿満溌" 70 | "源準溜溝溢溶溺滅滋滑滝滞滴漁漂漆漉漏演漕漠漢漣漫漬漸潅潔潜潟潤潮潰澄澗澱激濁" 71 | "濃濠濡濫濯瀕瀞瀦瀧瀬灘火灯灰灸灼災炉炊炎炭点為烈烏烹焔焚無焦然焼煉煎煙煤照煩" 72 | "煮煽熊熔熟熱燃燈燐燕燥燦燭爆爪爵父爺爽爾片版牌牒牙牛牝牟牡牢牧物牲特牽犀犠犬" 73 | "犯状狂狐狗狙狛狩独狭狸狼狽猛猟猪猫献猶猷猿獄獅獣獲玄率玉王玖玩玲珂珊珍珠珪班" 74 | "現球理琉琢琳琴琵琶瑚瑛瑞瑠瑳璃環璽瓜瓢瓦瓶甑甘甚甜生産甥用甫田由甲申男町画界" 75 | "畏畑畔留畜畝畠畢略畦番異畳畷畿疋疎疏疑疫疲疹疾病症痔痕痘痛痢痩痴療癌癒癖発登" 76 | "白百的皆皇皐皮皿盃盆盈益盗盛盟監盤目盲直相盾省眉看県真眠眺眼着睡督睦瞥瞬瞭瞳" 77 | "矛矢知矧矩短矯石砂研砕砥砦砧砲破砺砿硝硫硬硯硲碁碇碍碑碓碕碗碧碩確磁磐磨磯礁" 78 | "礎示礼社祁祇祈祉祐祖祝神祢祥票祭祷禁禄禅禍禎福禦禰禽禾禿秀私秋科秒秘租秤秦秩" 79 | "称移稀程税稔稗稚稜種稲稼稽稿穀穂穆積穎穏穐穣穫穴究空穿突窃窄窒窓窟窪窮窯窺竃" 80 | "立竜章竣童竪端競竹竺竿笈笑笛笠笥符第笹筆筈等筋筏筑筒答策箆箇箔箕算管箪箭箱箸" 81 | "節範篇築篠篤篭簡簸簾簿籍米籾粁粂粉粋粍粒粕粗粘粛粟粥粧精糊糎糖糞糟糠糧糸系糾" 82 | "紀約紅紋納紐純紗紘紙級紛素紡索紫紬累細紳紹紺終絃組経結絞絡絢給統絵絶絹継続綜" 83 | "綬維綱網綴綻綾綿緊緋総緑緒線締編緩緬緯練縁縄縛縞縦縫縮績繁繊繋繍織繕繭繰纂纏" 84 | "缶罪罫置罰署罵罷羅羊美群羨義羽翁翌習翠翫翰翻翼耀老考者而耐耕耗耳耶耽聖聞聡聯" 85 | "聴職聾肇肉肋肌肖肘肝股肢肥肩肪肯肱育肴肺胃胆背胎胞胡胤胴胸能脂脅脆脇脈脊脚脱" 86 | "脳脹腎腐腔腕腫腰腸腹腺腿膏膚膜膝膨膳膿臆臓臣臥臨自臭至致臼興舌舎舗舘舛舜舞舟" 87 | "航般舵舶舷船艇艦艮良色艶芋芙芝芥芦芭芯花芳芸芹芽苅苑苓苔苗苛若苦苧苫英茂茄茅" 88 | "茎茜茨茶茸草荊荏荒荘荷荻莞莫莱菅菊菌菓菖菜菟菩華菰菱萄萌萎萩萱落葉葎著葛葡董" 89 | "葦葬葱葵葺蒋蒐蒔蒙蒜蒲蒸蒼蓄蓉蓋蓑蓬蓮蔀蔑蔓蔚蔦蔭蔵蔽蕃蕉蕊蕎蕗蕨蕩蕪薄薗薙" 90 | "薦薩薪薫薬薮薯藁藍藤藩藷藻蘇蘭虎虐虚虜虞虫虹虻蚊蚕蚤蛇蛋蛍蛎蛙蛤蛭蛮蛸蛾蜂蜘" 91 | "蜜蝉蝋蝕蝦蝶蝿融螺蟹蟻血衆行術街衛衝衡衣表衰衷衿袈袋袖被袴袷裁裂装裏裕補裟裡" 92 | "裳裸製裾複褐褒襖襟襲西要覆覇見規視覗覚覧親観角解触言訂計訊討訓託記訟訣訪設許" 93 | "訳訴診註証詐詑詔評詞詠詣試詩詫詮詰話該詳誇誉誌認誓誕誘語誠誤説読誰課誹誼調談" 94 | "請諌諏諒論諜諦諭諮諸諺諾謀謁謂謄謎謙講謝謡謬謹識譜警議譲護讃讐谷豆豊豚象豪豹" 95 | "貌貝貞負財貢貧貨販貫責貯貰貴買貸費貼貿賀賂賃賄資賊賎賑賓賛賜賞賠賢賦質賭購贈" 96 | "贋赤赦赫走赴起超越趣趨足距跡跨路跳践踊踏蹄蹟蹴躍身躯車軌軍軒軟転軸軽較載輔輝" 97 | "輩輪輯輸輿轄轍轟轡辛辞辰辱農辺辻込辿迂迄迅迎近返迦迩迫迭述迷追退送逃逆透逐逓" 98 | "途逗這通逝速造逢連逮週進逸逼遁遂遅遇遊運遍過道達違遜遠遡遣遥適遭遮遵遷選遺遼" 99 | "避還邑那邦邪邸郁郊郎郡部郭郵郷都鄭酉酋酌配酎酒酔酢酪酬酵酷酸醇醍醐醒醗醜醤醸" 100 | "釆采釈里重野量金釘釜針釣釦釧鈍鈎鈴鈷鉄鉛鉢鉦鉱鉾銀銃銅銑銘銚銭鋒鋤鋪鋭鋲鋳鋸" 101 | "鋼錆錐錘錠錦錨錫錬錯録鍋鍍鍔鍛鍬鍵鍾鎌鎖鎗鎚鎧鎮鏑鏡鐘鐙鐸鑑鑓長門閃閉開閏閑" 102 | "間関閣閤閥閲闇闘阜阪防阻阿陀附降限陛院陣除陥陪陰陳陵陶陸険陽隅隆隈隊階随隔隙" 103 | "際障隠隣隷隻隼雀雁雄雅集雇雌雑雛離難雨雪雫雰雲零雷電需震霊霜霞霧露青靖静非面" 104 | "革靭靴鞄鞍鞘鞠鞭韓韮音韻響頁頂頃項順須預頑頒頓頗領頚頬頭頴頻頼題額顎顔顕願顛" 105 | "類顧風飛食飢飯飲飴飼飽飾餅養餌餐餓館饗首香馨馬馳馴駁駄駅駆駈駐駒駕駿騎騒験騨" 106 | "騰驚骨骸髄高髪髭鬼魁魂魅魔魚魯鮎鮒鮪鮫鮭鮮鯉鯖鯛鯨鯵鰍鰐鰭鰯鰹鰻鱈鱒鱗鳥鳩鳳" 107 | "鳴鳶鴇鴎鴛鴨鴫鴬鴻鵜鵠鵡鵬鶏鶴鷲鷹鷺鹸鹿麓麗麟麦麹麺麻麿黄黍黒黙黛鼎鼓鼠鼻齢" 108 | "龍" 109 | ) 110 | __JAPANESE_KANJIS_SEQUENCE_2ND_JOYO: ClassVar[str] = ( 111 | "丼傲刹哺喩嗅嘲彙恣惧慄憬拉摯曖楷毀璧瘍箋籠緻羞訃諧貪踪辣錮鬱" 112 | ) 113 | __JAPANESE_KANJIS_SEQUENCE_3RD_JOYO: ClassVar[str] = "頰塡剝𠮟" 114 | __p_ja_kana: ClassVar[regex.Pattern[str]] = regex.compile( 115 | r"[\p{Script=Hiragana}\p{Script=Katakana}]" 116 | ) 117 | __p_ja_kanji: ClassVar[regex.Pattern[str]] = regex.compile( 118 | rf"[{__JAPANESE_KANJIS_SEQUENCE_1ST}" 119 | + rf"{__JAPANESE_KANJIS_SEQUENCE_2ND_JOYO}" 120 | + rf"{__JAPANESE_KANJIS_SEQUENCE_3RD_JOYO}]" 121 | ) 122 | # 123 | __CHINESE_KANJIS_SEQUENCE_SIMPLIFIED: ClassVar[str] = ( 124 | "㐷㐹㐽㑇㑈㑔㑩㑺㓥㔉㖊㖞㘎㚯㛀㛟㛠㛣㛤㛿㝦㟆㟜㟥㡎㤖㤘㤭㤽㥪㦈㧏㧐㧑㧛㧟㧰㨫" 125 | "㭎㭏㭣㭤㭴㮠㱩㱮㲿㳔㳕㳠㳡㳢㳽㴋㶉㶶㶽㷪㺍㻅㻏㻘㻪㾡䀥䁖䂵䃅䅉䅟䅪䇚䇲䉤䌶䌷" 126 | "䌸䌹䌺䌻䌼䌽䌾䌿䍀䍁䎬䏝䐪䓓䓕䓖䓨䖼䗖䘛䘞䙊䙌䙓䛓䜣䜤䜥䜧䜩䝙䞌䞍䞎䞐䟢䢀䢁" 127 | "䢂䥺䥽䥾䥿䦀䦁䦂䦃䦅䦆䦶䦷䩄䭪䯃䯄䯅䲝䲞䲟䲠䲡䲢䲣䲤䴓䴔䴕䴖䴗䴘䴙䶮万与丑专" 128 | "业丛东丝丢两严丧个丰临为丽举么义乌乐乔习乡书买乱了争于亏云亚产亩亲亵亸亿仅仆" 129 | "从仑仓仪们价众优伙会伛伞伟传伡伣伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨" 130 | "俩俪俫俭借债倾偬偻偾偿傥傧储傩儿克兑兖党兰关兴兹养兽冁内冈册写军农冬冯冲决况" 131 | "冻净准凉减凑凛几凤凫凭凯出击凿刍划刘则刚创删别刬刭刮制刹刽刾刿剀剂剐剑剥剧劝" 132 | "办务劢动励劲劳势勋勚匀匦匮区医千华协单卖卜卢卤卫却卷厂厅历厉压厌厍厐厕厘厢厣" 133 | "厦厨厩厮县叁参叆叇双发变叙叠只台叶号叹叽吁合同后向吓吕吗吣吨听启吴呐呒呓呕呖" 134 | "呗员呙呛呜咏咙咛咝咤咸响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唤啧啬啭啮啯啰啴啸喷喽" 135 | "喾嗫嗳嘘嘤嘱噜嚣回团园困囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垩垫" 136 | "垭垯垱垲垴埘埙埚埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥奨奸妆妇妈妩妪" 137 | "妫姗姜姹娄娅娆娇娈娱娲娴婳婴婵婶媪媭嫒嫔嫱嬷孙学孪宁宝实宠审宪宫家宽宾寝对寻" 138 | "导寿将尔尘尝尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岽岿峃峄峡峣峤峥峦崂崃" 139 | "崄崭嵘嵚嵝巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂干并广庄庆庐庑库应庙庞废庼廪开异" 140 | "弃弑张弥弪弯弹强归当录彝彟彦彨彻征径徕御忆忏志忧忾怀态怂怃怄怅怆怜总怼怿恋恒" 141 | "恳恶恸恹恺恻恼恽悦悫悬悭悮悯惊惧惨惩惫惬惭惮惯愠愤愦愿慑慭懑懒懔戆戋戏戗战戬" 142 | "戯户才扑执扩扪扫扬扰折抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挙挚挛挜挝挞挟挠挡" 143 | "挢挣挤挥挦挽捝捞损捡换捣据掳掴掷掸掺掼揽揾揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸" 144 | "撺擜擞攒敌敛敩数斋斓斗斩断旋无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暅暧曲术朱朴机杀" 145 | "杂权杆条来杨杩杰松板极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖栗样" 146 | "栾桠桡桢档桤桥桦桧桨桩桪梦梼梾梿检棁棂椁椝椟椠椢椤椫椭椮楼榄榅榇榈榉槚槛槟槠" 147 | "横樯樱橥橱橹橼檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵毶氇气氢氩氲汇汉汤汹沄沈" 148 | "沟没沣沤沥沦沧沨沩沪泞注泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒" 149 | "浓浔浕涂涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温湾湿溁溃溅溆溇滗滚滞" 150 | "滟滠满滢滤滥滦滨滩滪漓漤潆潇潋潍潜潴澛澜濑濒灏灭灯灵灶灾灿炀炉炜炝点炼炽烁烂" 151 | "烃烛烟烦烧烨烩烫烬热焕焖焘煴爱爷牍牦牵牺犊状犷犸犹狈狝狞独狭狮狯狰狱狲猃猎猕" 152 | "猡猪猫猬献獭玑玙玚玛玮环现玱玺珐珑珰珲琎琏琐琼瑶瑷瑸璎瓒瓯电画畅畴疖疗疟疠疡" 153 | "疬疭疮疯疱疴症痈痉痒痖痨痪痫瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫皑皱皲盏盐监盖盗盘眍眦眬着" 154 | "睁睐睑瞆瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硕硖硗硙硚确硵硷碍碛碜礼祃祎祢祯祷" 155 | "祸禀禄禅离秃秆秋种积称秽秾稆税稣稳穑穷窃窍窎窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑" 156 | "筚筛筜筝筹筼签简箓箦箧箨箩箪箫篑篓篮篯篱簖籁籴类籼粜粝粤粪粮糁糇系紧累絷纟纠" 157 | "纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细" 158 | "织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬" 159 | "续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒" 160 | "缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗" 161 | "罚罢罴羁羟翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肮肴肾肿胀胁胆胜胡胧胨胪胫胶脉脍" 162 | "脏脐脑脓脔脚脱脶脸腊腘腭腻腼腽腾膑臜致舆舍舣舰舱舻艰艳艺节芈芗芜芦芸苁苇苈苋" 163 | "苌苍苎苏苧苹范茎茏茑茔茕茧荆荐荙荚荛荜荝荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅" 164 | "莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蒙蓝蓟蓠蓣蓥蓦蔂蔑蔷蔹蔺蔼蕰蕲蕴" 165 | "薮藓蘖虏虑虚虫虬虮虽虾虿蚀蚁蚂蚃蚕蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝼蝾螀螨蟏" 166 | "衅衔补表衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褝褴襕见观觃规觅视觇览觉觊觋觌觍觎觏" 167 | "觐觑觞触觯訚詟誉誊讠计订讣认讥讦讧讨让讪讫讬训议讯记讱讲讳讴讵讶讷许讹论讻讼" 168 | "讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询" 169 | "诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈" 170 | "谉谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮" 171 | "谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸" 172 | "费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞" 173 | "赟赠赡赢赣赪赵赶趋趱趸跃跄跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫" 174 | "转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑" 175 | "辒输辔辕辖辗辘辙辚辞辟辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥" 176 | "邓邝邬邮邹邺邻郁郏郐郑郓郦郧郸酂酝酦酱酽酾酿采释里鉴銮錾钅钆钇针钉钊钋钌钍钎" 177 | "钏钐钑钒钓钔钕钖钗钘钙钚钛钜钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴" 178 | "钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铇铈铉铊铋铌铍铎铏铐铑铒铓铔铕铖铗铘铙铚" 179 | "铛铜铝铞铟铠铡铢铣铤铥铦铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销" 180 | "锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锜锝锞锟锠锡锢锣锤锥锦" 181 | "锧锨锩锪锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镄镅镆镇镈镉镊镋镌" 182 | "镍镎镏镐镑镒镓镔镕镖镗镘镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲" 183 | "镳镴镵镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈" 184 | "阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陦陧陨险随隐隶隽" 185 | "难雏雠雳雾霁霉霡霭靓静面靥鞑鞒鞯韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂" 186 | "颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风" 187 | "飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饣饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹" 188 | "饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵" 189 | "驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛" 190 | "骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓鬶魇魉鱼鱽鱾鱿鲀鲁鲂鲃鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎" 191 | "鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴" 192 | "鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳚" 193 | "鳛鳜鳝鳞鳟鳠鳡鳢鳣鳤鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺" 194 | "鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹙鹚鹛鹜鹝鹞鹟鹠" 195 | "鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹮鹯鹰鹱鹲鹳鹴鹾麦麸麹黄黉黡黩黪黾鼋鼍鼗鼹齐齑齿龀" 196 | "龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟鿎鿏鿒鿔鿕" 197 | ) 198 | # '龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟鿎鿏鿒鿔鿕鿟鿭鿰鿲鿴鿵鿶鿷鿸鿹鿺 E​2‍3‛2‛F”0'\ 199 | # '‟9․2‥7356E₆0₲4₽F₾0⃃7⃅E⃊5⃍E⃒2⃗8⃗E℡B℩1ℬ0ℭ7℮4ℼ6ⅈ4ⅶ0ⅸBⅻ1ↁFↄ7↖7↵C↶C⇌3⇍2⇕D⇛4⇠3'\ 200 | # '⇨3⇭8∁6∬8≌5≝3≡9≡D≡E≥0≥1≥2≥3≮F≿C⊝0⊩3⊩7⊬A⊭8⊭E⊮C⊰D⊲6⊴F⋚3⋷E⌌1⌙0⌢3⌧C⌶8⌶F⌷0⌹1⌾2⍁5⍂4'\ 201 | # '⍅D⍇6⍈C⍉7⍏F⍗2⍜A⍜B⍡0⍡3⍣4⍣7⍣E⍦5⍩A⍸E⎣C⎶4⎾3⏅D⏉7⏉8⏌6⏚9⏚B⏢3⏫C⏫D⏷7⏸D␚1␚2␜3␜4␞D␟B␣6␣7'\ 202 | # '␨0␬F␻A␻B⑦F⑳5⑶2⑸3⑺4⒀B⒘0⒧DⓌ4ⓘAⓚ7ⓧA⓬A⓶F⓸0⓿2┆2┕8┗4┚7┞2┹D╁F╂F╃0╃B╖4╺6╼2▜2▧A▰0▰8▱E'\ 203 | # '▲0▴9▸B▹C▻E◅4◦5◨5◨7☠8☠9☠B☠C☠E☠F☡0☡1☡2☡3☡4☡5☡6☡7☡8☡9☡A☡B☡C☡D☡E☡F☢0☢1☶0♮8♷C♸C♽7'\ 204 | # '⚢9⚱9⛃4⛐7⛭5✣4✣F✥0✥E✽6✽7❄F❊D❲1❲D❵D➒4➔5➺A⟍5⟥1⟥2⟥3⟥4⟥5⟥6⟥7⟪3⟼8⟽B⠀1⠃1⠇4⠋A⠐4⠕B⠖B⠖C'\ 205 | # '⠥7⡀5⡀6⡀7⡀8⡀9⡀A⡇9⡵5⡿3⢂8⢅9⢇A⢋8⢓0⢞E⣃E⣃F⣄0⣄1⣄2⣄3⣄4⣄5⣄6⣄7⣄8⣄9⣄A⣄B⣄C⣄D⣄E⣄F⣅0⣅1⣅2⣅3'\ 206 | # '⣅4⣅5⣅6⣟F⣠0⣠1⣠2⣠3⣠4⣠5⣠6⣠7⣠9⣠A⣠B⣠C⣠E⣡8⣡F⣯9⤿C⤿D⤿E⤿F⥀0⥙5⥙6⥙7⥦5⥦6⥦7⥦8⥦9⥦A⥦B⥦C⥦D⥦E'\ 207 | # '⥦F⥧0⥿F⦀0⦀1⦀2⦀3⦀5⦀6⦀7⦀8⦀9⦀A⦀B⦀C⦀E⦀F⦂0⦅6⦅A⦞6⦞8⦞9⦞A⦞B⦞C⦞D⦞E⦞F⦟0⦟1⦟2⦟3⦟4⦟5⦟6⦟8⦟A'\ 208 | # '⦟B⦟C⦟F⦠0⦠1⦠3⦠4⦠5⦠6⦠7⦠8⦠9⦠A⦠B⦠C⦠D⦠E⦠F⦡0⦤8⦲3⦲4⦳E⦷9⦽2⧃0⧉2⧐C⧷9⧷A⧷B⧷C⧷D⧷E⧷F⧸1⧸2⧸3'\ 209 | # '⧸4⧸5⧸6⧸7⧸8⧸A⧸B⧸C⧸E⨤2⨤3⨤4⨤5⨤6⨤8⨤9⨤A⨤B⨤C⨤D⨤E⨤F⨥0⨥1⨥2⨥4⨥5⨸8⨸9⨸A⨸B⨸C⩄5⩒D⩨F⩩0⩭E⩰E'\ 210 | # '⩳A⩹D⩼E⩽D⩿2⪀0⪀3⪀F⪁F⪂1⪃3⪃5⪃8⪃D⪄0⪄3⪄B⪄F⪅B⪅E⪇A⪈8⪈B⪈C⪉0⪉2⪉5⪊0⪊E⪌6⪍2⪏B⪐4⪐5⪑A⪖0⪖B⪗0'\ 211 | # '⪗F⪜0⪝8⪠7⪠A⪡7⪢7⪢9⪣6⪣7⪣9⪤7⪤E⪥8⪥B⪧8⪩1⪩E⪫4⪬C⪮1⪯7⪯8⪯A⪱A⪲F⪵D⪶2⪶7⪶F⪷5⪷E⪸3⪸B⪹6⪻3⪻6⪼B'\ 212 | # '⫃6⫆5⫇7⫈E⫉4⫉B⫊E⫌D⫑9⫒F⫔7⫕1⫖3⫗1⫘4⫙2⫚E⫝̸D⫟D⫡5⫢9⫤0⫦0⫧3⫧9⫪3⫪A⫪D⫫7⫫8⫫B⫫D⫭0⫮8⫯2⫯A⫰B⫳4'\ 213 | # '⫴2⫵D⫶A⫶D⫶E⫷4⫷7⫹4⫺2⫺6⫻8⫼A⫽E⫾B⫿5⬀C⬁3⬂8⬂C⬂E⬄2⬅F⬆1⬇2⬇3⬇7⬇A⬈3⬈6⬈8⬉6⬋F⬍7⬑9⬑A⬑B⬑C⬑D'\ 214 | # '⬑E⬑F⬒0⬒1⬒2⬒3⬒4⬒5⬒6⬒7⬒8⬒9⬒A⬒B⬒C⬒D⬒E⬒F⬓0⬓1⬓2⬓3⬓4⬓5⬓6⬓7⬓8⬓9⬔5⬕7⬖5⬖D⬗C⬘F⬙D⬚B⬝8⬞6'\ 215 | # '⬞A⬞D⬟4⬟D⬠9⬠E⬡F⬣5⬤1⬤4⬪A⬪E⬫1⬫8⬫9⬫B⬬7⬬C⬯2⬯7⬯9⬯B⬰0⬰7⬰B⬲8⬲9⬲A⬲B⬲C⬲D⬲F⬴F⬵0⬵9⬵A⬵B⬵C'\ 216 | # '⬵E⬵F⬶0⬶1⬶2⬶3⬶4⬶5⬶6⬶7⬶8⬶9⬶A⬶B⬶C⬶D⬶E⬶F⬷0⬷1⬷2⬷3⬷4⬷5⬷6⬷7⬷8⬷9⬷A⬷B⬷C⬷D⬷E⬷F⬸6⬸C⬺6⬺7'\ 217 | # '⬺8⬺9⬺A⬺B⬺C⬺D⬻1⬻3⬻8⬻A⬼3⬼6⬼B⬼C⬽0⬽1⬽5⬽E⬾8⭀4⭀5⭀6⭀7⭀8⭀9⭀A⭀B⭀C⭀D⭀E⭀F⭁0⭁1⭁2⭁3⭁4⭁5⭁6'\ 218 | # '⭁7⭁8⭁9⭃7⭅8⭆1⭇7⭎5⭎6⭎7⭎8⭎9⭎A⭎B⭎C⭎D⭎E⭎F⭏0⭏1⭏2⭏3⭏4⭏5⭏6⭏7⭏8⭏9⭏A⭏B⭏C⭏D⭏E⭏F⭐0⭐1⭐2⭐3'\ 219 | # '⭐4⭐5⭐6⭐7⭐8⭐9⭐A⭐B⭐C⭐D⭐E⭐F⭑0⭑1⭑2⭑3⭑4⭑5⭑6⭒D⭒F⭓0⭓1⭓2⭓4⭓5⭓6⭓D⭕A⭖5⭖8⭘3⭘5⭘7⭙1⭙2⭙3⭙4'\ 220 | # '⭙5⭙6⭚A⭚B⭚C⭚D⭚E⭚F⭛0⭛1⭛2⭛3⭛4⭛5⭛6⭛7⭛8⭛9⭛A⭜7⭜8⭜9⭜A⭜B⭝A⭝E⭝F⭞0⭞1⭞2⭞3⭞4⭞5⭞6⭞7⭞8⭞9⭞A'\ 221 | # '⭞B⭞C⭞D⭞E⭞F⭟0⭟1⭟2⭟3⭟4⭟5⭡B⭡C⭡D⭡E⭡F⭢0⭢1⭢3⭢4⭢5⭢6⭢7⭢8⭢9⭢A⭢B⭢C⭢D⭢E⭢F⭣0⭣1⭣D⭤2⭨8⭨9⭨A'\ 222 | # '⭨B⭨C⭨D⭨E⭨F⭩0⭩1⭩2⭩3⭩4⭩5⭩6⭩7⭩8⭩9⭩A⭩B⭩C⭩D⭩E⭩F⭪0⭪1⭪2⭪3⭪4⭪5⭪6⭪7⭪8⭪9⭪A⭪B⭪C⭪D⭭A⭭B⭭C'\ 223 | # '⭭D⭭E⭭F⭮0⭮1⭮2⭮3⭮4⭮5⭮6⭮7⭮8⭮9⭮A⭮B⭮C⭮D⭮E⭮F⭯0⭯1⭯2⭯3⭯4⭯5⭯6⭯7⭯8⭯9⭯A⭯B⭯C⭯D⭯E⭰0⭰1⭰2⭰3'\ 224 | # '⭰4⭰5⭰A⭱1⭱2⭱3⭱4⭱5⭱9⭱F⭲8⭲9⭲A⭲B⭲C⭲D⭲E⭲F⭳0⭳2⭳3⭳7⭴8⭴B⭶1⭶6⭶7⭶8⭶9⭶A⭶B⭶C⭶D⭶E⭷5⭸5⭹7⭹A'\ 225 | # '⭹B⭹D⭺0⭺1⭺2⭺3⭺5⭺6⭺7⭺8⭺9⭻7⭼3⭼4⭼5⭼6⭼7⭽1⭽5⭽E⭽F⭾0⭾1⭾2⭾4⭾5⭾6⭾B⭾C⭿2⭿3⭿4⭿5⭿6⭿7⭿8⭿9⭿A'\ 226 | # '⭿B⭿C⭿D⭿E⭿F⮀0⮀1⮀2⮀5⮀6⮀7⮀8⮀A⮀B⮀C⮀F⮁0⮁1⮁2⮁6⮁C⮆C⮇6⮉2⮉4⮉8⮉9⮉C⮉F⮊8⮊A⮊C⮊D⮋2⮋8⮋9⮋A⮌9'\ 227 | # '⮌A⮍B⮎B⮓8⮓D⮔D⮕4⮗3⮗5⮗A⮗C⮗D⮘1⮘5⮘9⮘B⮘C⮚9⮛0⮛3⮜3⮞E⮞F⮟7⮟F⮠6⮥5⮥6⮥A⮥B⮦4⮦5⮦9⮦B⮦F⮧3⮧8⮧A'\ 228 | # '⮨0⮨1⮨2⮨3⮨4⮨5⮩1⮩8⮩A⮪7⮪A⮫A⮫D⮬7⮬F⮮6⮯5⮯E⮱0⮱9⮱F⮵E⮵F⮶2⮶8⮶A⮶E⮶F⮷2⮷C⮸3⮸5⮹C⮽2⮾5⮿6⯀2⯀D'\ 229 | # '⯁B⯂0⯂1⯂2⯂3⯂8⯃0⯃9⯅5⯇F⯉7⯋8⯌3⯓C⯗5⯗6⯗7⯗8⯗9⯘4⯘5⯘7⯘A⯙5⯛2⯜5⯜8⯜9⯜C⯝8⯞C⯞E⯟7⯟9⯟E⯢9⯦E⯧4'\ 230 | # '⯧7⯧C⯧D⯨1⯨2⯨6⯨A⯨C⯩2⯩3⯩8⯪A⯪B⯫7⯫9⯬1⯬7⯱7⯱D⯲3⯲4⯲5⯲7⯲B⯲E⯳1⯳2⯳5⯳6⯳D⯳E⯴0⯴1⯴7⯴A⯴B⯵0⯵4'\ 231 | # '⯵9⯶2⯶3⯶5⯶7⯶B⯶E⯷2⯷3⯸1⯸3⯸9⯸F⯻2⯻3⯼2⯽7Ⰲ5Ⰲ9ⰂAⰂEⰃ1Ⰵ1Ⰵ8Ⰷ3Ⰷ5Ⰷ8ⰇAⰇDⰈ0Ⰸ2Ⰸ5Ⰸ9Ⰺ0Ⰺ9ⰊEⰋ0Ⰻ1'\ 232 | # 'ⰋBⰌ0ⰌAⰌFⰍ8ⰍBⰎ6ⰎBⰎEⰏ2Ⰿ3ⰑEⰒ9ⰒCⰖ2Ⱆ5ⰖBⰘ2Ⱉ9Ⱊ6ⰚEⰛEⰜ3Ⱌ4Ⱌ7Ⱍ5Ⱍ8Ⱍ9ⰞCⰟ0Ⱏ9ⰟCⰠ1ⰠFⰡ5Ⱒ7Ⱓ1ⰣE'\ 233 | # 'Ⱔ2Ⱔ7ⰤBⰦ0ⰧCⰨ2Ⱘ8Ⱘ9ⰨDⰨEⰩ6Ⱙ7ⰩCⰪ4Ⱚ6Ⱛ5Ⱛ6ⰫAⰫEⰬ3ⰬDⰱDⰲ0ⰲEⰳ4ⰳ5ⰳ7ⰵ9ⰵBⰶ1ⰶ4ⰸ6ⰹ1ⰺ7ⰺCⰽCⰽFⰾ4'\ 234 | # 'ⰾ6ⰾBⰾEⰿ7ⱂ0ⱄ6ⱄ7ⱄDⱄFⱅ2ⱅ3ⱅ5ⱅ7ⱅ9ⱆ7ⱈ4ⱈ6ⱈ7ⱈ8ⱈAⱈDⱈEⱉ3ⱉ5ⱉ7ⱎ0ⱎBⱏ1ⱏ8ⱏCⱒFⱓ9ⱔ2ⱔ4ⱔAⱕBⱖ6ⱖC'\ 235 | # 'ⱘ3ⱙ1ⱙ6ⱙ8ⱙEⱙFⱚ0ⱚEⱛAⱡ3ⱡ4ⱡ5ⱡ6ⱡ7ⱡ8ⱡ9ⱡAⱡBⱡCⱡDⱡEⱡFⱢ0Ɫ1Ɫ2Ɫ3Ɫ4Ɫ5Ɫ6Ɫ7Ɫ8Ɫ9ⱢAⱢBⱢCⱢDⱢEⱢF'\ 236 | # 'Ᵽ0Ᵽ1Ᵽ2Ᵽ3Ᵽ4Ᵽ5Ᵽ6Ᵽ7Ᵽ8Ᵽ9ⱣAⱣBⱣCⱣDⱣEⱣFⱤ0Ɽ1Ɽ2Ɽ3Ɽ4Ɽ5Ɽ6Ɽ7Ɽ8Ɽ9ⱤAⱤBⱤEⱤFⱥDⱦAⱦBⱦDⱨ4Ɐ9ⱯCⱱ4'\ 237 | # 'Ⱳ5Ⱳ7Ⱳ8ⱲCⱲFⱳ8ⱳAⱳEⱳFⱴ1ⱴ3ⱴAⱴBⱵ6ⱶ0ⱶFⱷ4ⱸBⱹ5ⱹ8ⱹFⱺ3ⱺBⱼ1ⱾAⱿAⱿDⲀ3Ⲁ5Ⲁ8Ⲃ0ⲃ1ⲃ7Ⲅ7ⲄDⲄEⲅ2ⲅ3'\ 238 | # 'ⲅ4ⲅ5Ⲇ0Ⲇ6ⲇ1ⲇ7ⲇBⲈ7Ⲉ8Ⲉ9ⲈAⲈBⲈCⲈDⲈEⲈFⲉ0ⲉ1ⲉ2ⲉ3ⲉ4ⲉ5ⲊAⲊFⲋ3Ⲍ0ⲍ9ⲍAⲍBⲍCⲍDⲍEⲍFⲎ0Ⲏ1Ⲏ2Ⲏ3Ⲏ4'\ 239 | # 'Ⲏ5Ⲏ6Ⲏ7Ⲏ8Ⲏ9ⲎAⲎBⲎCⲎDⲎEⲎFⲏ0ⲏ1ⲏ2ⲏ3ⲏ4ⲏ5ⲏ6ⲏ7ⲏ8ⲏ9ⲏAⲏBⲏCⲏDⲏEⲏFⲐ0Ⲑ1Ⲑ2Ⲑ3Ⲑ4Ⲑ5Ⲑ6Ⲑ7Ⲑ8Ⲑ9ⲐA'\ 240 | # 'ⲐBⲐCⲐDⲐEⲐFⲑ0ⲑ1ⲑ2ⲑ3ⲑ4ⲑ5ⲑ6ⲑ7ⲑ8ⲑ9ⲑAⲑBⲑCⲑDⲑEⲑFⲒ0Ⲓ1Ⲓ2Ⲓ3Ⲓ4Ⲓ5Ⲓ6Ⲓ7Ⲓ8Ⲓ9ⲒAⲒBⲒCⲒDⲒEⲒFⲓ0'\ 241 | # 'ⲓ1ⲓ7Ⲕ4Ⲕ8ⲗ3ⲗ4ⲗ5ⲗ6ⲗ7ⲗ8ⲗ9ⲗAⲗBⲗCⲗDⲗEⲗFⲘ0Ⲙ5Ⲙ6Ⲛ3Ⲛ5Ⲛ7Ⲛ9ⲚBⲚFⲛ4ⲛ5ⲛ9ⲛBⲛEⲜ0Ⲝ3ⲝ1ⲝ4ⲝAⲝBⲞ2'\ 242 | # 'Ⲟ4Ⲟ9Ⲡ1Ⲡ2Ⲡ3Ⲡ4Ⲡ5Ⲡ6Ⲡ7Ⲡ8Ⲡ9ⲠBⲠCⲠDⲠEⲠFⲡ0ⲡ1ⲡ2ⲡ3ⲡ4ⲤEⲧDⲧEⲨDⲪ7Ⲫ8Ⲫ9ⲪBⲪFⲫAⲰ7Ⲳ7Ⲳ8Ⲳ9ⲲAⲲBⲲC'\ 243 | # 'ⲲDⲲEⲲFⲳ0ⲳ1ⲳ2ⲳ3ⲳ4ⲳ5ⲳ6ⲳ7ⲳ8ⲳ9ⲳAⲳBⲳCⲳDⲳEⲳFⲴ0Ⲵ1Ⲵ2Ⲵ3Ⲵ5Ⲵ6Ⲵ7Ⲵ8Ⲵ9ⲴAⲴBⲴCⲴDⲴEⲴFⲵ0ⲵ1ⲵ3ⲵ4'\ 244 | # 'ⲵ5ⲵ6ⲵ7ⲵ8ⲵ9ⲵAⲵBⲵCⲵDⲵEⲵFⲶ0Ⲷ1Ⲷ2Ⲷ3Ⲷ4Ⲷ5Ⲷ6Ⲷ8Ⲷ9ⲶAⲶBⲶCⲶDⲶFⲷ0ⲷ1ⲷ2ⲷ3ⲷ4ⲷ5ⲷ6ⲷ7ⲷ8ⲷ9ⲷAⲷBⲷC'\ 245 | # 'ⲷDⲷEⲷFⲸ0Ⲹ1Ⲹ2Ⲹ3Ⲹ4ⲹ8ⲹ9ⲹCⲹDⲹFⲺ0Ⲻ1Ⲻ2Ⲻ3Ⲻ4Ⲻ5Ⲻ7Ⲻ8Ⲻ9ⲺAⲺCⲺDⲺEⲺFⲻ0ⲻ1ⲻ2ⲻ3ⲻ4ⲻ5ⲻ8ⲻ9ⲻAⲻBⲻF'\ 246 | # 'Ⲽ0Ⲽ5ⲼAⲼEⳂ1Ⳃ3Ⳃ4Ⳃ5ⳃ1ⳃ2ⳃ3ⳃ4ⳃ5ⳃ6ⳃ7ⳃ8ⳃAⳅ3ⳅ4ⳅ5ⳅ6ⳅ7ⳅ8ⳅ9ⳅAⳅBⳅCⳅDⳅEⳅFⳆ0Ⳇ1Ⳇ2Ⳇ3Ⳇ4Ⳇ5Ⳇ6Ⳇ7'\ 247 | # 'Ⳇ8Ⳇ9ⳆAⳆBⳆCⳆDⳆEⳆFⳇ0ⳇ1ⳇ2ⳇ3ⳇ5ⳇ7ⳇ8ⳇAⳇCⳇDⳇEⳇFⳈ0Ⳉ5Ⳉ6ⳉ5Ⳋ5Ⳋ6Ⳋ7Ⳋ8Ⳋ9ⳊAⳊBⳊCⳊDⳊEⳊFⳋ0ⳋ1ⳋ2'\ 248 | # 'ⳋ3ⳋ4ⳋ5ⳋ6ⳋ7ⳋ8ⳋ9ⳋAⳋBⳋCⳋDⳋEⳋFⳌ0Ⳍ1Ⳍ2Ⳍ3Ⳍ4Ⳍ5Ⳍ6Ⳍ7Ⳍ8Ⳍ9ⳌAⳌBⳌCⳌDⳌEⳍ0ⳍ1ⳍ2ⳍ3ⳍ4ⳍ9ⳍFⳏ3ⳏ4ⳏ5'\ 249 | # 'ⳏ6ⳏ7ⳏ8ⳏ9ⳏAⳏBⳏCⳏDⳏEⳏFⳐ0Ⳑ1Ⳑ2Ⳑ3Ⳑ4Ⳑ5Ⳑ6Ⳑ7Ⳑ8Ⳑ9ⳐAⳐBⳐCⳐDⳐEⳐFⳑ0Ⳓ8Ⳓ9Ⳙ0Ⳙ1Ⳙ2Ⳙ3Ⳙ4Ⳙ5Ⳙ6Ⳙ7Ⳙ8'\ 250 | # 'Ⳙ9ⳘAⳘBⳘCⳘDⳘEⳘFⳙ0ⳙ1ⳙ2ⳙ3ⳙ4ⳙ5ⳙ6ⳙ7ⳙ9ⳙAⳙBⳙCⳙDⳙEⳙFⳚ0Ⳛ1Ⳛ2Ⳛ3Ⳛ4Ⳛ5Ⳛ6Ⳛ7Ⳛ8Ⳛ9ⳚAⳚBⳚCⳚDⳚEⳚF'\ 251 | # 'ⳛ0ⳛ1ⳛ2ⳛ3ⳛ4ⳛ5ⳛ6ⳛ7ⳛ8ⳛ9ⳛAⳛBⳝ5ⳟBⳟCⳟDⳟEⳟFⳠ0Ⳡ1Ⳡ2Ⳡ3Ⳡ4Ⳡ5Ⳡ6Ⳡ8Ⳡ9ⳠAⳠBⳠCⳠDⳠEⳠFⳡ0ⳡ1ⳡ2ⳡ3ⳡ4'\ 252 | # 'ⳡ5ⳡ6ⳡ7ⳡ8ⳡ9ⳡAⳡBⳡCⳡDⳡEⳡFⳢ0Ⳣ1Ⳣ2Ⳣ3Ⳣ4Ⳣ5Ⳣ6Ⳣ7Ⳣ8Ⳣ9ⳢAⳢBⳢCⳢDⳢEⳢFⳣ0ⳣ1ⳣ5ⳣ6ⳣ7ⳣ8ⳣ9ⳣEⳤ5ⳤ6ⳤ7'\ 253 | # 'ⳤ8ⳤ9ⳤAⳤBⳤCⳤDⳤE⳥5⳥6⳥7⳥8⳦3⳦4⳦D⳧A⳧B⳧C⳧D⳧E⳧F⳨0⳨1⳨2⳨3⳨4⳨5⳨6⳨7⳨8⳨9⳨A⳨B⳨C⳨D⳨E⳨F⳩0⳩1'\ 254 | # '⳩2⳩3⳩4⳩5⳩6⳩B⳩C⳩D⳩FⳮE⳹6⳺3ⴑBⴜ0ⴜ9ⴝ9ⴝCⴞ1ⴞFⴟ4ⴠ8ⴠ9ⴡCⴡFⴢEⴥ7⴦8ⴧC⴫8ⴸ2ⴹCⴾ6ⴿ8ⵇ8ⵇ9ⵌ0ⵔ6ⵡ3'\ 255 | # 'ⵡA⵪6⵴B⵶B⵸4ⶁ9ⶃDⶄ6ⶅCⶇ5ⶈBⶉ5ⶉDⶌ7ⶎ7ⶐEⶓ0ⶕ3⶜BⶥAⶥB⶧0ⶨ6ⶬ0ⶭ9ⶭDⶴ8ⷀEⷁ2ⷁ7ⷂ5ⷄ0ⷄAⷊBⷐAⷓ3ⷥCⷬD'\ 256 | # 'ⷭ4⸂1⸂4⸂A⸃2⸔E⸘F⸝4⸞4⸦0⸦1⸦2⸦3⸦4⸦5⸦7⸦8⸦9⸦A⸦B⸦C⸦D⸦E⸦F⸰C⸸D⸼0⸿A⹁A⹂8⹐2⹐5⹐A⹘1⹘3⹛1⹤A⹤B'\ 257 | # '⹭7⹳6⹷4⹷5⹷7⹷8⹷9⹷A⺁E⺃3⺏2⺏3⺏4⺏5⺏6⺒B⺒C⺒D⺒E⺒F⺓2⺓3⺓6⺓7⺓8⺘5⺙A⺟4⺟5⺣4⺣5⺥B⺥C⺥D⺥E⺪1⺪2⺪3'\ 258 | # '⺪4⺪5⺬2⺱B⺱C⺱D⺱E⺱F⺲0⺲1⺲2⺲3⺲4⺶1⺶2⺶4⺶5⺶6⺶8⺶A⺷0⺸5⺸7⺽9。1。2〄8々C〆7〇8〇E〈1〈3〈B〈E〈F〉7〉C'\ 259 | # '《6《D》B「6『E』3』6』7』B』F【1】E〒D〕4〖5〖6〗B〙5〙9〙A〜0〜A〜E〝5〝6〝8〞0〞1〞3〞5〟2〟C〠6〠7〠A〠D〡3〢E'\ 260 | # '〢F〣6〤1〤4〥8〥9〥A〦3〦5〦9〦A〧1〧D〨2〨5〨8〩1〩B〩F〪1〪2〭6〯8〯9〯D〯E〰0〰2〰6〰7〰9〱9〲6〳7〸C〸E〸F〹0'\ 261 | # '〹1〹4〹6〹B〹D〹E〺0〺2〺6〺B〻4〻7〻9〼1〽3〽5〽C〽F〿2〿6〿C〿DぁAぃEい1い4い5ぅ4ぅ5ぅ9ぅFう5う7うAうBうCぇ5ぇ8'\ 262 | # 'ぇFえ6ぉ2ぉ6が4が6き4き5き7き9きCきDきFぎ4ぎ7ぎCく1く7くBくCぐ7ぐBこ2こ6ご1ご5ご8さ0ざDじ8じFすAず0ず9ぜ5ぜ6そ3そ6'\ 263 | # 'そ8そ9そAそBそCぞ1ぞ2ぞ6ぞ8ぞCた5た9たAだ0だ5だ8ち3ぢ0ぢ3ぢ9っ3っ6っ8つBつEづ1づ5とDど4な6なAなCに1ぬ9ぬAぬFね2ねB'\ 264 | # 'の1の3の4の5の6の8の9のAのEは1は2は5はAはBはDぱ0ぱCぱDひ2ひ8び3ぴ5ぴBぴDふ7ふCふEふFぶ4ぷEへ7へ9へDぺ4ほ2ほ3ほ7ほB'\ 265 | # 'ぼ4ぽ8めBもBゃ2ゃ4ゃ9や4や9やAやBやEやFゅ0ゅ4ゅEゆ2ゆ9ょ0ょ5ょBょDよ4り2り4り6ゎ2ゎ6ゎ9ゎBゎCゎFわ6わCわDゑ3ゑ5を8'\ 266 | # 'をBをCんDゔAゕ2ゕBゕEゖ0ゖ2ゖ3ゖ8ゖAゖD゗9゙4゙C゚6゚8゚D゛0゛4゛7゛E゛F゜3゜7゜8゜9゜Eゝ4ゝ8ゟ0ゟBゟEァ6ァCア6ィ3'\ 267 | # 'イ5イFゥ3ウ7ウEェ2ェ8ェ9ェAェBエAエFオ3オAオBオDカ6カCカFガBキ6クCクDクFグ0グ1グ2グ3グ5グ6グ7グ8グ9グBグCグDグEグF'\ 268 | # 'ケ0ケ1ケ2ケ3ケ4ケ6ケ7ケ8ケ9ケAケBケCケDケEケFゲ0ゲ1ゲ2ゲ3ゲ4ゲ5ゲ6ゲ7ゲ8ゲ9ゲAゲBゲCゲDゲEゲFコ0コ1コ2コ3コ4コ5コ6'\ 269 | # 'コ7コ8コ9コAコBコCコDコEコFゴ0ゴ1ゴ4サ4サ7サAザ2ザ3シ9ジ5ジ7ス9スDズDセ2ゼ2ゼBゼEダ6ダBダCダFチ1ヂ0ヂ2ヂ4ヂ8ヂEッ1'\ 270 | # 'ッ3ッ4ッ5ッ6ッ7ッ9ッAッEッFツ0ツ7ツ8ツ9ツAツCツDヅ0ヅ1ヅBヅDヅFテ6テ9テEテFデ1デ2デEト1ト2ド2ド6ドFナ0ナBナCナEナF'\ 271 | # 'ニ0ニ2ニ3ニ4ニ5ニ6ニ8ニ9ニAニBヌ1ヌ2ヌ4ヌ6ヌAネ7ネAハ2ハ5ハ8ハ9ハAハBハCバ2パ5パ6パ7パ8パ9パAパBパCパDパEヒ2ヒ3ヒ4'\ 272 | # 'ヒ5ヒFピAピCピDピEピFフ0フ1フ2フ3フ4フ6フ7フ8フ9フAフBフCフDフEブ0ブ1ブ2ブ3ブ4ブ5ブ6ブ7ブ8ブ9ブAブBブCブDブEブFプ0'\ 273 | # 'プ1プ2プ3プ4プ5プ6プ7プ8プ9プAプBプCプDプEプFヘ0ヘ1ヘ2ヘ3ヘ4ヘ5ヘ6ヘ7ヘ8ヘ9ヘAヘBヘCヘDヘEヘFベ1ベ4ペ8ペCポEマ0マ1'\ 274 | # 'マ2マ4マ5マ6マ7マ8マ9マAマBマCマDマEミ4ミ5ミ6ミ8ム7ム8ムAムEメ0メ4メAメBメEモ6ヤ0ユFョ1ョ2ョ3ョ4ョ5ョ6ョ7ョ8ョAョB'\ 275 | # 'ョCョDョEョFヨ0ヨ1ヨ2ヨ3ヨ4ヨ5ヨ6ヨ7ヨ8ヨ9ヨAヨBヨCヨDヨEヨFラ0ラ1ラ2ラ3ラ4ラ5ラ6ラ7ラ8ラ9ラAラBラCラDラEラFリ0リ1'\ 276 | # 'リ2リ3リ4リ8リDル2ル7レ6ロDヮ1ヮ6ヮ8ヮEワ3ヰ5ヰBヰFヱ1ンBヵ5ヵ6ヵ7ヵ8ヵAヵBヵCヵDヵEヶ0ヶ1ヶ2ヶ3ヶ5ヶ6ヶ7ヶ8ヶ9ヶB'\ 277 | # 'ヶCヶDヶEヶFヷ0ヷ1ヷ2ヷ3ヷ4ヷ5ヷ6ヷ7ヷ8ヷ9ヷAヷBヷCヷDヷEヷFヸ0ヸ1ヸ3ヸ4ヸ5ヸ6ヸ7ヸ8ヸ9ヸAヸBヸCヸDヸEヸFヹ0ヹ1ヹ2'\ 278 | # 'ヹ3ヹ5ヹ6ヹ7ヹ8ヹ9ヹAヹBヹCヹDヹEヹFヺ1ヺ2ヺ3ヺ4ヺ5ヺ6ヺ7ヺ8ヺ9ヺAヺBヺCヺDヺEヺF・0・1・2・3・4・5・6・7・8・9・A'\ 279 | # '・B・C・D・E・Fー0ー1ー2ー3ー4ー5ー6ー7ー8ー9ーAヽ6ヾ5ヾ6ヾ7ヾ8ヾ9ヾAヾBヾCヾDヾFヿ0ヿ3ヿ4ヿ5ヿ8ヿ9ヿAヿBヿE㄁1㄂1'\ 280 | # 'ㄅ2ㄅEㄇ1ㄇ3ㄇ4ㄇ6ㄇ7ㄇ9ㄇAㄇDㄇEㄈ3ㄈ4ㄈ5ㄈ6ㄈ7ㄈ8ㄈ9ㄈAㄈBㄈCㄈDㄈEㄉ0ㄊ0ㄊ1ㄊ2ㄊ3ㄊ4ㄊ5ㄊ6ㄊ7ㄊ8ㄊ9ㄊBㄊCㄊDㄊE'\ 281 | # 'ㄊFㄋ0ㄋ1ㄋ2ㄋ3ㄋ4ㄋ5ㄋ6ㄋ7ㄋ8ㄋAㄋBㄍ4ㄍ5ㄍ6ㄍ7ㄍ8ㄍ9ㄍAㄍBㄍCㄍDㄍEㄍFㄎ0ㄏ1ㄏ2ㄏ3ㄏ4ㄏ5ㄏ7ㄏ8ㄏ9ㄏAㄏCㄏDㄏEㄏF'\ 282 | # 'ㄐ0ㄐ1ㄐ2ㄐ3ㄐ4ㄐ5ㄐ6ㄐ7ㄐ8ㄐ9ㄐAㄓCㄓDㄓEㄓFㄔ0ㄔ1ㄔ2ㄔ3ㄔ4ㄔ5ㄔ7ㄔ8ㄔ9ㄔAㄔBㄔDㄔEㄔFㄕ0ㄕ2ㄕ3ㄕ4ㄕ5ㄕ6ㄕ7ㄕ8ㄕ9'\ 283 | # 'ㄕAㄕBㄕCㄕDㄕEㄕFㄖ0ㄖ1ㄖ2ㄖ3ㄖ4ㄖ5ㄖ6ㄖ7ㄖ8ㄖ9ㄖAㄖBㄖCㄖEㄘ0ㄘ1ㄘ3ㄘ4ㄘ5ㄘ6ㄘ8ㄘCㄘDㄙ6ㄙ9ㄙAㄙBㄜDㄜEㄜFㄝ0ㄝ1'\ 284 | # 'ㄝ2ㄝ3ㄝ4ㄝ5ㄝ6ㄝ7ㄝ8ㄝ9ㄝAㄝBㄝCㄝDㄝEㄝFㄞ0ㄞ1ㄞ2ㄞ3ㄞ4ㄞ5ㄞ6ㄞ7ㄞ8ㄞ9ㄞAㄞBㄞCㄞDㄞEㄞFㄟ0ㄟ1ㄟ2ㄟ3ㄟ4ㄟ5ㄟ6ㄟ7'\ 285 | # 'ㄟ8ㄟ9ㄟAㄟBㄟCㄟDㄟEㄟFㄠ0ㄠ1ㄠ2ㄠ3ㄠ4ㄠ5ㄠ6ㄠ7ㄠ8ㄠ9ㄠAㄠBㄠCㄠDㄠEㄠFㄡ0ㄡ1ㄡ2ㄡ3ㄡ4ㄡ5ㄡ6ㄡ7ㄡ8ㄡ9ㄡAㄡBㄡCㄤ7'\ 286 | # 'ㄤ8ㄤ9ㄤAㄤBㄤCㄤDㄤEㄤFㄥ0ㄥ1ㄥ2ㄥ3ㄥ4ㄥ5ㄥ6ㄥ7ㄥ8ㄥ9ㄥAㄥBㄥCㄥDㄥEㄥFㄦ0ㄦ1ㄦ2ㄦ3ㄦ4ㄦ5ㄦ6ㄦ7ㄦ8ㄦ9ㄦAㄦBㄦCㄦD'\ 287 | # 'ㄦEㄦFㄧ0ㄧ1ㄧ2ㄧ3ㄧ4ㄧ5ㄧ6ㄧ7ㄧ8ㄧ9ㄧAㄧBㄧCㄧDㄧEㄧFㄨ0ㄨ1ㄨ2ㄨ3ㄨ4ㄨ5ㄨ6ㄨ7ㄨ8ㄨ9ㄨAㄨBㄨCㄨDㄨEㄨFㄩ0ㄩ1ㄩ2ㄩ3'\ 288 | # 'ㄩ4ㄩ5ㄩ6ㄩ7ㄩ8ㄩ9ㄩAㄩBㄩCㄩDㄩEㄩFㄪ0ㄪ1ㄪ2ㄪ3ㄪ4ㄪ5ㄪ6ㄪ7ㄪ8ㄪ9ㄪAㄪBㄪCㄪDㄪEㄪFㄫ0ㄫ1ㄫ2ㄫ3ㄫ4ㄫ5ㄫAㄫBㄫCㄫD'\ 289 | # 'ㄬ2ㄬ4ㄬ5ㄬ6ㄬ7ㄬ8ㄬ9ㄬAㄬBㄬCㄬDㄬEㄭ0ㄭ1ㄭ3ㄭ4ㄭ5ㄭ6ㄭ7ㄭ8ㄭ9ㄭAㄭCㄭDㄭFㄮ0ㄮ1ㄮ2ㄮ3ㄮ4ㄮ5ㄮ6ㄮ8ㄮAㄮBㄮCㄮDㄮE'\ 290 | # 'ㄯ1ㄯ4ㄯ6ㄯEㄯF㄰0㄰1㄰3㄰4㄰5㄰6㄰7㄰8㄰9㄰A㄰Fㄱ5ㄱ6ㄱ7ㄱ8ㄱ9ㄲBㄲCㄲDㄲEㄲFㄳ0ㄳ1ㄳ2ㄳ3ㄳ4ㄳ5ㄳ6ㄳ7ㄳ8ㄳ9ㄳAㄳC'\ 291 | # 'ㄳDㄴ1ㄴ2ㄴ4ㄴ5ㄴ6ㄴ7ㄴ8ㄴ9' 292 | __p_zh_s: ClassVar[regex.Pattern[str]] = regex.compile( 293 | rf"(?![{__JAPANESE_KANJIS_SEQUENCE_1ST}" 294 | + rf"{__JAPANESE_KANJIS_SEQUENCE_2ND_JOYO}" 295 | + rf"{__JAPANESE_KANJIS_SEQUENCE_3RD_JOYO}])" 296 | + rf"(?=[{__CHINESE_KANJIS_SEQUENCE_SIMPLIFIED}])" 297 | ) 298 | # 299 | __CHINESE_KANJIS_SEQUENCE_TRADITIONAL: ClassVar[str] = ( 300 | "㑮㑯㑳㑶㒓㒜㒣㒿㓄㓖㓨㔃㔅㔋㔝㔢㕒㕢㖦㖮㗙㗢㗣㗰㗲㗶㗻㗼㗿㘓㘔㘖㘙㘚㘤㙔㙡㙢" 301 | "㙬㙺㙾㛝㜄㜏㜐㜗㜞㜢㜥㜭㜮㜷㜺㝞㝟㞞㟺㠁㠏㠠㠣㡓㡞㢗㢝㤲㥮㥷㦊㦎㦖㦛㦞㦦㦬㦭" 302 | "㨛㨟㨥㨻㩇㩋㩌㩜㩣㩭㩳㩵㩷㩹㪎㪹㬣㬮㮓㮝㮲㯂㯆㯤㯸㯼㰂㰅㰍㰰㰳㲯㲰㲲㴸㴿㵍㵑" 303 | "㵒㵗㵤㵾㶆㶌㶍㶏㶒㶕㷃㷍㷲㷶㷻㷿㸅㸊㸐㹓㹽㺏㺑㺜㻶㻽㼆㼈㼻㾵㾺㿉㿎㿖㿗㿧㿹䀉" 304 | "䀍䀴䀹䁝䁪䁱䁻䂎䂓䃁䃕䃘䃢䃣䃤䃮䃴䅐䅘䅳䆅䆉䇓䉍䉐䉑䉙䉬䉱䉲䉶䊜䊟䊭䊲䊵䊷䊺" 305 | "䋃䋆䋍䋎䋏䋐䋑䋔䋙䋚䋦䋫䋹䋺䋻䋼䋽䋾䋿䌁䌇䌈䌋䌌䌐䌖䌝䌞䌟䌥䌪䌰䍤䍷䍽䎘䎙䎱" 306 | "䏊䐢䐣䐷䐹䐽䑗䑼䓣䔇䔈䔡䕡䕤䕳䕹䕼䖀䖅䖚䗃䗅䗥䗻䗽䗿䙔䙡䙱䙼䚆䚉䚕䚞䚩䚳䚵䚽" 307 | "䛀䛄䛌䛍䛘䛛䛞䛠䛤䛬䛭䛳䛽䛿䜀䜄䜉䜋䜍䜎䜏䜒䜖䜚䜝䝏䝕䝭䝯䝻䝼䞀䞁䞂䞈䞉䞋䞓" 308 | "䞶䟃䟆䟏䟐䟺䠆䠟䠠䠩䠮䠱䡁䡅䡇䡊䡐䡗䡘䡝䡟䡦䡩䡰䡴䡵䡶䡷䡻䡾䢈䢨䤌䤍䤠䤤䤥䤨" 309 | "䤩䤪䤬䤵䤸䤻䤼䥄䥇䥑䥕䥖䥗䥛䥝䥞䥩䥯䥱䥴䥶䥷䥸䦌䦎䦘䦛䦝䦟䦪䦯䦱䦳䧞䧢䨴䩫䪊" 310 | "䪍䪏䪐䪓䪗䪘䪜䪝䪥䪴䪼䪾䫀䫂䫈䫉䫌䫏䫐䫜䫟䫠䫥䫩䫴䫶䫻䫼䫾䬀䬂䬅䬍䬎䬐䬓䬔䬘" 311 | "䬝䬞䬟䬣䬧䬪䬫䬬䬯䬲䬳䬶䬹䬾䭀䭃䭅䭇䭈䭉䭑䭒䭓䭔䭕䭘䭞䭡䭢䭣䭭䭿䮂䮄䮈䮗䮝䮞" 312 | "䮠䮧䮫䮰䮲䮳䮸䮽䮾䮿䯀䯤䰎䰐䰖䰫䰲䰷䰻䰽䰾䱀䱁䱂䱅䱇䱌䱍䱎䱐䱒䱓䱗䱙䱚䱛䱜䱟" 313 | "䱡䱤䱥䱧䱬䱭䱰䱱䱴䱵䱷䱸䱹䱻䱽䱾䲁䲅䲉䲏䲕䲖䲗䲘䲙䲚䲛䲨䲰䲸䲹䲼䳅䳇䳍䳏䳒䳓" 314 | "䳕䳚䳜䳟䳢䳤䳧䳨䳫䳭䳮䳲䳺䴇䴈䴉䴋䴚䴝䴬䴭䴮䴱䴲䴳䴴䴵䴷䴸䴹䴺䴽䵂䵃䵆䵐䵘䵳" 315 | "䵴䵶䵷䶕䶗䶢䶣䶦䶧䶨䶪䶱䶲丟並乾亂亞佇併來侖侶俁係俓俔俠俥倀倆倈倉個們倫倲偉" 316 | "偑偩側偵偽傌傑傖傘備傢傪傭傯傱傳傴債傷傾僀僂僅僆僉働僑僓僕僗僞僤僥僨僩僴價僾" 317 | "儀儁儂億儅儈儉儐儔儕儖儘償儢儣儥儩優儰儱儲儷儸儹儺儻儼兌兒兗內兩冊冪凈凍凔凙" 318 | "凜凟凱別刪剄則剋剎剗剛剝剮剴創剸剾劃劇劉劊劌劍劏劑劗劚勁勑動務勛勝勞勢勣勩勱" 319 | "勴勵勸勻匭匯匰匱匵區協卨卻厙厭厱厲厴參叄叢吒吳吶呂咼員哯唄唊唓唚唻問啞啟啢喎" 320 | "喚喪喬單喲嗆嗇嗊嗎嗚嗧嗩嗶嗹嗿嘄嘆嘇嘍嘓嘔嘖嘗嘜嘩嘪嘮嘯嘰嘳嘵嘸嘺嘽噁噅噓噚" 321 | "噝噞噠噥噦噯噲噴噸噹嚀嚂嚇嚈嚌嚍嚐嚕嚙嚛嚝嚠嚦嚧嚨嚩嚪嚫嚬嚱嚲嚳嚴嚶嚸嚽嚿囀" 322 | "囁囂囃囅囇囈囉囋囐囑囒囕囪圇國圍園圓圖團圞垵垷埉埡埨埬埰執堅堈堊堖堚堝堯報場" 323 | "塊塋塏塒塗塢塤塵塸塹塼塿墆墊墋墏墜墝墠墢墧墮墳墶墷墾墿壇壈壋壍壏壐壒壓壔壗壘" 324 | "壙壚壛壝壞壟壠壢壣壧壩壪壯壺壼壽夠夢夥夾奐奧奩奪奫奬奮奯奲奼妝姍姦娙娛婁婡婦" 325 | "婭婸媁媈媜媧媯媰媼媽嫈嫗嫢嫥嫧嫵嫻嫿嬃嬅嬇嬈嬋嬌嬐嬒嬙嬡嬣嬤嬦嬪嬮嬰嬸嬻嬾孄" 326 | "孆孇孋孌孎孫孲學孻孾孿宮寠寢實寧審寪寫寬寯寵寶寷將專尋對導尵尷屆屍屓屜屢層屨" 327 | "屩屬岡峴島峽崍崗崙崠崢崬崱崵嵐嵷嵸嵼嵽嵾嶁嶄嶇嶈嶔嶗嶠嶢嶤嶧嶨嶩嶪嶮嶴嶸嶹嶺" 328 | "嶼嶽巃巆巊巋巑巒巔巖巗巘巚巠巰帥師帳帴帶幀幃幓幗幘幟幠幣幩幫幬幰幱幹幺幾庫庲" 329 | "廁廂廄廈廎廔廕廗廚廝廞廟廠廡廢廣廥廧廩廬廮廳弒弳張強彄彈彌彍彎彙彞彠彥彲後徑" 330 | "從徠復徵徹徿恆恥悅悏悞悵悶惀惡惱惲惻愇愛愜愨愩愴愷愾慄態慍慐慘慙慚慟慣慪慫慮" 331 | "慯慱慲慳慶慸慹慺憂憊憍憐憑憒憖憚憢憤憦憪憫憮憲憴憶憸憹懀懇應懌懍懓懕懘懙懜懞" 332 | "懟懠懣懤懧懨懩懫懭懰懲懶懷懸懺懼懾戀戁戃戇戔戠戧戩戰戱戲戶拋挩挾捨捫捲掁掃掄" 333 | "掆掗掙掚掛採揀揚換揮搊損搎搖搗搵搶摀摃摋摐摑摕摙摜摟摪摫摯摲摳摶摺摻摼撈撊撋" 334 | "撌撏撐撓撝撟撣撥撧撫撲撳撶撻撾撿擁擃擄擇擈擊擋擓擔據擟擠擣擥擧擪擫擬擯擰擱擲" 335 | "擳擴擷擺擻擼擽擾攄攆攋攎攏攑攔攖攙攛攜攝攞攢攣攤攦攧攩攪攬攳敗敘敵數敺敿斁斂" 336 | "斃斄斅斆斕斬斷斸於旝旟昜時晉晛晝暈暉暐暘暟暢暫曄曆曇曉曊曏曖曠曥曨曬曭曮書會" 337 | "朥朧東柵桱桿梔梖梘梜條梟梲棄棆棖棗棟棡棧棲棶椏椚椲楇楊楎楓楨業極榝榪榮榯榲榿" 338 | "構槍槤槧槨槫槮槳槶槻槼樁樂樅樓標樞樠樢樣樫樲樳樸樹樺樻樿橃橅橈橋橚機橢橨橫橯" 339 | "檁檂檉檋檒檔檛檜檟檡檢檣檥檭檮檯檰檲檳檵檸檻檾檿櫃櫅櫍櫎櫏櫓櫚櫛櫝櫞櫟櫠櫢櫥" 340 | "櫧櫨櫩櫪櫫櫬櫯櫱櫳櫴櫶櫸櫹櫻櫽欄欇權欍欏欐欑欒欓欖欘欞欽歄歍歐歕歗歛歞歟歡歲" 341 | "歷歸歿殘殞殢殤殨殫殮殯殰殲殺殼毀毄毆毊毿氀氂氈氌氣氫氬氭氳決沒沖況洶浹浿涇涷" 342 | "涼淚淥淪淵淶淺渙減渢渦測渾湊湋湞湯溈準溝溡溤溫溮溰溳滄滅滌滎滬滭滯滲滷滸滻滾" 343 | "滿漁漊漍漎漐漙漚漢漣漬漲漵漸漿潁潑潔潕潚潛潣潤潬潯潰潷潿澀澅澆澇澐澒澖澗澠澢" 344 | "澤澦澩澫澬澮澰澱澾濁濃濄濆濇濊濕濘濚濛濜濟濤濧濫濰濱濺濼濾濿瀁瀂瀃瀄瀅瀆瀇瀈" 345 | "瀉瀋瀏瀕瀘瀙瀝瀟瀠瀢瀦瀧瀨瀯瀰瀲瀳瀴瀵瀾灃灄灍灑灒灓灕灘灙灝灟灠灡灣灤灦灧災" 346 | "為烏烴焛無煇煉煒煙煢煥煩煬煱煼熂熅熉熌熒熓熕熗熞熡熰熱熲熾燀燁燈燌燒燖燘燙燜" 347 | "營燡燦燭燰燴燵燶燼燽燾爁爃爄爍爐爓爖爛爣爥爧爭爺爾牆牋牘牼牽犅犓犖犞犢犤犧狀" 348 | "狹狽猌猍猙猧猶猻獁獄獅獊獎獑獖獟獢獨獩獪獫獮獰獱獲獵獷獸獹獺獻獼玀玁玂珼現琖" 349 | "琺琿瑋瑒瑙瑣瑤瑩瑪瑲瑻瑽璉璊璕璗璛璝璡璣璦璫璯環璵璸璹璼璽璾瓄瓅瓊瓏瓐瓓瓔瓕" 350 | "瓚瓛甊甌甒甖產畝畢畫異當疇疊痙痮痾瘂瘋瘍瘑瘒瘓瘞瘡瘧瘮瘱瘲瘺療癆癇癈癉癎癐癘" 351 | "癟癠癢癤癥癧癩癪癬癭癮癰癱癲癴發皚皟皪皰皸皺皾盜盞盡監盤盧盨盪眥眾睍睏睔睜睞" 352 | "睪睴瞓瞘瞛瞜瞞瞡瞤瞭瞯瞱瞶瞷瞼矇矉矊矑矓矕矖矘矚矯矲硃硜硤硨硯碙碢碩碭碸確碼" 353 | "碽磑磒磚磠磣磧磯磱磵磽磾礄礆礋礎礏礐礒礙礚礛礥礦礩礪礫礬礮礰礱礲礹祿禍禎禓禕" 354 | "禜禡禦禪禬禮禯禰禱禵禿秈稅稈稏稟種稱穀穇穌積穎穖穠穡穢穧穨穩穫穬穭窩窪窮窯窱" 355 | "窵窶窺竀竄竅竇竈竉竊竱競筆筍筧筴箋箏箹節範築篋篔篘篢篤篩篳篵篸篿簀簂簍簜簞簡" 356 | "簢簣簥簫簵簹簻簽簾籃籋籌籔籙籚籛籜籟籠籣籦籩籪籫籬籭籮籯籲粯粵粻糝糞糧糮糰糲" 357 | "糴糶糷糹糺糽糾紀紂紃約紅紆紇紈紉紋紌納紐紑紒紓純紕紖紗紘紙級紛紜紝紞紟紡紨紩" 358 | "紬紭細紱紲紳紵紶紸紹紺紼紽紾紿絀絁終絃組絅絆絇絍絎結絑絓絕絖絘絙絚絛絝絞絟絠" 359 | "絡絢絣絤絥給絧絨絪絯絰統絲絳絸絹絺絻絼絽絾絿綀綁綃綄綅綆綇綈綊綋綌綍綎綏綐經" 360 | "綕綖綜綝綞綟綠綡綢綣綧綪綬維綯綰綱網綴綵綷綸綹綺綻綼綽綾綿緀緁緂緄緅緆緇緉緊" 361 | "緋緌緍緎総緒緓緔緗緘緙線緛緝緞緟締緡緢緣緤緦緧編緩緪緫緬緮緯緰緱緲練緵緶緷緸" 362 | "緹緺緻縈縉縊縋縌縍縎縐縑縒縓縕縖縗縚縛縜縝縞縟縡縣縧縩縪縫縬縭縮縯縰縱縲縳縴" 363 | "縵縶縷縸縹縺縼總績縿繀繂繃繅繆繈繎繏繐繑繒繓織繕繖繗繘繙繚繜繞繟繡繢繣繨繩繪" 364 | "繫繬繭繮繯繰繲繳繵繶繷繸繹繻繼繽繾繿纀纁纃纆纇纈纊纋續纍纏纑纓纔纕纖纗纘纚纜" 365 | "缽罃罆罈罌罏罰罵罷罼羂羅羆羈羋羥義羵習翜翬翹翽翿耬耮聖聞聯聰聲聳聵聶職聹聻聽" 366 | "聾肅脅脈脛脥脫脹腎腖腡腦腪腫腳腸膃膒膕膚膞膠膢膩膮膴膶膷膹膽膾膿臇臉臍臏臗臘" 367 | "臚臟臠臡臢臤臨臺與興舉舊艙艛艜艤艦艫艭艱艷芻苧茲荊莊莖莢莧菕華萇萊萬萯萴萵葉" 368 | "葒著葝葤葦葷葻蒍蒒蒔蒞蒭蒳蒶蒼蓀蓋蓮蓯蓲蓴蓽蔄蔎蔔蔞蔠蔣蔥蔦蔪蔭蔮蔯蔱蕁蕄蕆" 369 | "蕎蕑蕒蕓蕕蕘蕝蕟蕡蕢蕧蕩蕪蕭蕳蕷蕽薀薆薈薉薊薋薌薑薔薖薘薟薠薦薩薱薲薳薴薵薺" 370 | "藇藉藍藎藖藘藚藝藣藥藪藬藭藰藶藷藹藺藾蘀蘄蘆蘇蘈蘊蘋蘚蘞蘟蘡蘢蘫蘬蘭蘱蘵蘹蘺" 371 | "蘿虅虆虉處虛虜號虦虧虯蛵蛺蛻蛼蜆蜦蜸蜽蝀蝁蝕蝜蝟蝦蝸螄螘螞螢螮螴螹螻螿蟂蟄蟈" 372 | "蟎蟘蟜蟡蟣蟦蟬蟯蟱蟲蟳蟶蟷蟻蟽蠀蠁蠅蠆蠈蠌蠐蠑蠒蠙蠞蠟蠣蠦蠨蠪蠱蠳蠶蠻蠾衊術" 373 | "衕衚衛衝袞裊裌補裝裡裲製複褌褘褭褲褳褸褺褻襀襂襇襌襏襓襖襗襘襛襝襠襤襨襪襬襭" 374 | "襯襰襱襲襴襵襸襹襼覆見覎規覒覓覕視覗覘覛覜覟覠覡覢覤覥覦覩親覬覭覯覰覲覴覶覷" 375 | "覸覹覺覻覼覽覿觀觴觶觷觸觹觻觽訁訂訃訆計訊訌討訏訐訑訒訓訕訖託記訛訜訝訞訟訢" 376 | "訣訥訦訧訨訩訪訬設訰許訴訶訸訹診註訽詀詁詃詄詅詆詇詉詊詌詍詎詏詐詑詒詓詔評詖" 377 | "詗詘詛詜詝詞詠詡詢詣詥試詨詩詪詫詬詭詮詯詰話該詳詴詵詶詷詺詻詼詿誂誃誄誅誆誇" 378 | "誋誌認誎誏誐誑誒誔誕誗誘誙誚誜語誠誡誣誤誥誦誧誨說誫誰課誳誴誶誷誹誺誻誼誽誾" 379 | "調諁諂諃諄諆談諈諉請諍諎諏諑諒諓諔諕論諗諛諜諝諞諟諠諢諣諤諥諦諧諩諫諭諮諯諰" 380 | "諱諲諳諴諶諷諸諹諺諻諼諾謀謁謂謄謅謆謉謊謋謌謍謎謏謐謑謔謖謗謙謚講謜謝謞謟謠" 381 | "謣謥謨謫謬謭謯謰謱謲謳謴謵謸謹謻謼謾譀譂譄譅譆譇譈證譊譌譎譏譐譑譓譔譖識譙譚" 382 | "譜譞譟譠譡譨譩譫譯議譳譴護譸譹譺譻譼譽譾譿讀讂讅讆讇讉變讋讌讎讑讒讓讔讕讖讘" 383 | "讙讚讛讜讝讞讟豄豅豈豎豐豬豵豶貓貗貙貝貞貟負財貢貣貤貦貧貨販貪貫責貯貰貱貲貳" 384 | "貴貶買貸貺費貼貽貾貿賀賁賂賃賄賅資賈賊賑賒賓賕賗賙賚賜賝賞賟賠賡賢賣賤賥賦賧" 385 | "賨質賬賭賮賰賴賵賶賸賹賺賻購賽賾贃贄贅贆贇贈贉贊贋贍贏贐贑贓贔贕贖贗贙贚贛赬" 386 | "趕趙趨趫趬趲跡踐踚踴蹌蹔蹕蹛蹡蹣蹤蹥蹪蹳蹺蹻躀躂躉躊躋躍躎躑躒躓躕躘躚躝躡躥" 387 | "躦躧躪軀軂軃軇軉車軋軌軍軎軏軑軒軓軔軕軖軗軘軛軜軝軞軟軤軥軧軨軫軬軮軯軱軲軳" 388 | "軵軷軸軹軺軻軼軾軿輀輁輂較輄輅輆輇輈載輊輋輐輑輒輓輔輕輖輗輘輙輚輛輜輝輞輟輠" 389 | "輡輢輣輤輥輦輨輩輪輫輬輮輯輲輳輴輵輶輷輸輹輻輾輿轀轂轃轄轅轆轇轈轉轊轍轎轏轐" 390 | "轑轒轓轔轕轖轗轘轙轚轛轝轞轟轠轡轢轣轤轥辦辭辮辯農迴逕這連進逿運過達違遙遜遞" 391 | "遠適遰遱遲遶遷選遺遼邁還邇邊邏邐郟郲郵鄆鄉鄒鄔鄖鄟鄡鄦鄧鄩鄪鄬鄭鄮鄰鄲鄳鄴鄶" 392 | "鄺酇酈醆醜醞醦醧醫醬醱醲醳醶釀釁釃釅釋釐釒釓釔釕釗釘釙釚釛針釟釣釤釥釦釧釨釩" 393 | "釪釫釬釭釱釲釳釴釵釷釹釺釽釾釿鈀鈁鈂鈃鈄鈆鈇鈈鈉鈋鈍鈎鈏鈐鈑鈒鈓鈔鈕鈖鈗鈚鈛" 394 | "鈜鈞鈠鈣鈤鈥鈦鈧鈪鈮鈯鈰鈲鈳鈴鈵鈶鈷鈸鈹鈺鈼鈽鈾鈿鉀鉁鉅鉈鉉鉊鉋鉌鉍鉎鉏鉐鉑" 395 | "鉒鉔鉕鉗鉘鉙鉚鉛鉜鉝鉞鉟鉠鉡鉤鉥鉦鉧鉨鉬鉭鉮鉲鉵鉶鉷鉸鉹鉺鉻鉼鉽鉾鉿銀銁銂銃" 396 | "銅銈銊銋銍銏銑銓銔銖銗銘銙銚銛銜銠銡銣銥銦銧銨銩銪銫銬銱銲銳銶銷銸銹銻銼銾鋁" 397 | "鋂鋃鋅鋇鋉鋊鋋鋌鋍鋏鋐鋒鋗鋘鋙鋜鋝鋟鋠鋡鋣鋤鋥鋦鋧鋨鋩鋪鋮鋯鋰鋱鋶鋸鋹鋼鋾錀" 398 | "錁錂錄錆錇錈錋錍錏錐錑錒錔錕錗錘錙錚錛錜錝錞錟錠錡錢錣錤錥錦錧錨錩錪錫錭錮錯" 399 | "錳錶錸錽鍀鍁鍂鍃鍄鍆鍇鍈鍉鍊鍋鍍鍏鍐鍑鍒鍔鍖鍘鍚鍛鍜鍝鍟鍠鍡鍣鍤鍥鍦鍧鍨鍩鍬" 400 | "鍭鍮鍯鍰鍱鍴鍵鍶鍺鍼鍾鎂鎄鎅鎇鎈鎉鎊鎋鎌鎍鎑鎒鎓鎔鎕鎖鎗鎘鎙鎚鎛鎝鎞鎡鎢鎣鎦" 401 | "鎧鎩鎪鎬鎮鎯鎰鎲鎳鎵鎶鎷鎿鏁鏂鏃鏆鏇鏈鏉鏌鏍鏏鏐鏑鏒鏓鏔鏕鏗鏘鏙鏚鏜鏝鏞鏟鏡" 402 | "鏢鏤鏥鏦鏨鏩鏰鏵鏷鏸鏹鏺鏻鏽鏾鐀鐁鐃鐄鐇鐈鐉鐊鐋鐍鐎鐏鐐鐒鐓鐔鐕鐖鐘鐙鐚鐝鐠" 403 | "鐤鐥鐦鐧鐨鐩鐪鐫鐬鐮鐯鐲鐳鐴鐵鐶鐸鐹鐺鐼鐽鐿鑀鑄鑇鑈鑉鑊鑋鑌鑏鑐鑑鑒鑔鑕鑖鑘" 404 | "鑙鑛鑞鑠鑡鑢鑣鑥鑨鑪鑭鑮鑯鑰鑱鑲鑴鑷鑸鑹鑼鑽鑾鑿钀钁钃長門閂閃閄閅閆閈閉開閌" 405 | "閍閎閏閐閑閒間閔閕閗閘閛閜閝閞閟閡閣閤閥閦閧閨閩閫閬閭閯閱閵閶閷閹閻閼閽閾閿" 406 | "闃闄闆闇闈闉闊闋闌闍闐闑闒闓闔闕闖闚闛關闞闟闠闡闢闤闥阪陘陝陣陰陳陸陽陿隉隊" 407 | "階隑隕隖際隤隨險隫隮隯隱隲隴隸隻雋雖雙雛雜雞離難雲電霢霣霧霼霽靂靄靅靆靈靉靚" 408 | "靜靦靧靨鞀鞏鞝鞦鞸鞻鞼鞽鞾韁韃韆韇韉韊韋韌韍韏韐韒韓韔韗韘韙韚韛韜韝韞韠韡韢" 409 | "韣韻響頁頂頃頄項順頇須頊頌頍頎頏預頑頒頓頔頕頖頗領頛頜頞頟頠頡頢頤頦頩頪頫頭" 410 | "頮頯頰頲頴頵頷頸頹頻顀顁顃顄顅顆顇顉顊顋題額顎顏顐顑顒顓顖顗願顙顛顜顝類顠顢" 411 | "顣顤顥顦顧顩顪顫顬顮顯顰顱顳顴風颩颬颭颮颯颰颱颲颳颴颶颷颸颹颺颻颼颽颾颿飀飁" 412 | "飂飄飆飇飈飉飋飍飛飠飢飣飤飥飦飩飪飫飭飯飰飲飴飵飶飷飼飽飾飿餀餂餃餄餅餉養餌" 413 | "餎餏餑餒餓餔餕餖餗餘餚餛餜餞餟餡餢餣餤餦餧館餩餪餫餬餭餯餰餱餲餳餴餵餶餷餸餹" 414 | "餺餼餾餿饀饁饃饅饆饇饈饉饊饋饌饎饐饒饗饘饙饛饜饞饟饠饡饢馩馬馭馮馯馱馲馳馴馵" 415 | "馹馺馼馽駁駂駃駉駊駍駎駏駐駑駒駓駔駕駗駘駙駚駛駜駝駞駟駢駣駤駥駧駩駪駫駬駭駮" 416 | "駰駱駴駶駷駸駹駺駻駼駽駾駿騀騁騂騃騄騅騇騉騊騋騌騍騎騏騑騔騕騖騗騙騚騜騝騞騟" 417 | "騠騢騣騤騥騧騩騪騫騬騭騮騯騰騱騲騳騴騵騶騷騸騹騺騻騼騽騾驀驁驂驃驄驅驈驉驊驋" 418 | "驌驍驎驏驐驒驓驔驕驖驗驙驚驛驞驟驠驡驢驤驥驦驨驩驪驫骯髏髐髒體髕髖髮鬆鬍鬖鬗" 419 | "鬚鬜鬝鬞鬠鬡鬢鬥鬧鬩鬮鬱鬹鬺魎魗魘魚魛魜魝魟魠魡魢魣魥魦魧魨魪魫魬魭魮魯魱魴" 420 | "魵魶魷魺魻魼魽魾鮀鮁鮂鮃鮄鮅鮆鮇鮈鮊鮋鮌鮍鮎鮏鮐鮑鮒鮓鮗鮘鮚鮛鮜鮞鮟鮠鮡鮣鮤" 421 | "鮥鮦鮧鮨鮪鮫鮬鮭鮮鮯鮰鮳鮵鮶鮷鮸鮹鮺鮻鮿鯀鯁鯄鯅鯆鯇鯈鯉鯊鯌鯒鯔鯕鯖鯗鯚鯛鯝" 422 | "鯞鯠鯡鯢鯤鯥鯦鯧鯨鯩鯪鯫鯬鯮鯰鯱鯴鯶鯷鯸鯹鯻鯼鯽鯾鯿鰁鰂鰃鰅鰆鰇鰈鰉鰊鰋鰌鰍" 423 | "鰏鰐鰑鰒鰓鰕鰗鰛鰜鰝鰟鰠鰡鰣鰤鰥鰦鰧鰨鰩鰫鰬鰭鰮鰯鰱鰲鰳鰴鰵鰶鰷鰹鰺鰻鰼鰽鰾" 424 | "鰿鱀鱁鱂鱃鱄鱅鱆鱇鱈鱉鱊鱋鱌鱍鱎鱏鱐鱑鱒鱓鱔鱕鱖鱗鱘鱚鱝鱞鱟鱠鱢鱣鱤鱥鱦鱧鱨" 425 | "鱬鱭鱮鱯鱲鱴鱵鱷鱸鱹鱺鱻鳥鳦鳧鳩鳭鳱鳲鳳鳴鳶鳷鳸鳺鳻鳼鳽鳾鳿鴀鴁鴂鴃鴅鴆鴇鴉" 426 | "鴍鴐鴒鴓鴔鴕鴗鴘鴙鴚鴛鴜鴝鴞鴟鴠鴡鴢鴣鴥鴦鴨鴩鴮鴯鴰鴱鴲鴳鴴鴶鴷鴸鴹鴺鴻鴽鴾" 427 | "鴿鵀鵁鵂鵃鵄鵅鵊鵋鵌鵎鵏鵐鵑鵒鵓鵔鵕鵖鵗鵙鵚鵛鵜鵝鵟鵠鵡鵧鵩鵪鵫鵬鵮鵯鵰鵱鵲" 428 | "鵳鵴鵵鵶鵷鵸鵹鵻鵼鵽鵾鶀鶂鶃鶄鶅鶆鶇鶉鶊鶋鶌鶒鶓鶔鶕鶖鶗鶘鶙鶚鶛鶝鶞鶟鶠鶡鶢" 429 | "鶣鶤鶥鶦鶨鶩鶪鶬鶭鶯鶰鶱鶲鶴鶵鶶鶷鶹鶺鶻鶼鶽鷀鷁鷂鷃鷅鷇鷈鷉鷊鷋鷎鷏鷐鷑鷒鷓" 430 | "鷔鷕鷖鷗鷙鷚鷛鷜鷞鷟鷢鷣鷤鷥鷦鷧鷨鷩鷫鷭鷮鷯鷰鷲鷳鷵鷶鷷鷸鷹鷺鷽鷾鷿鸀鸁鸂鸃" 431 | "鸄鸅鸆鸇鸉鸊鸋鸌鸎鸏鸐鸑鸒鸓鸕鸖鸗鸘鸙鸚鸛鸜鸝鸞鹵鹹鹺鹼鹽麗麡麥麧麨麩麬麮麯" 432 | "麰麱麲麳麴麵麷麼麽黂黃黌點黨黲黴黶黷黸黽黿鼀鼁鼄鼅鼆鼈鼉鼊鼕鼚鼲鼴齈齊齋齌齍" 433 | "齎齏齒齔齕齖齗齘齙齚齜齝齞齟齠齡齣齤齥齦齧齩齪齬齭齮齯齰齱齲齳齴齵齶齷齸齹齺" 434 | "齻齼齽齾龍龎龏龐龑龓龔龕龖龜龝龞龥龭龯龲龻龽鿁鿐鿓" 435 | ) 436 | # '齻齼齽齾龍龎龏龐龑龓龔龕龖龜龝龞龥龭龯龲龻龽鿁鿐鿓鿠鿳 4 E′5‸5‹2‾2‾E⁀7⁀A⁀D⁂E'\ 437 | # '⁃D⁄7⁅9⁇2⁚B F⁢5⁳2⁷F⁸6⁺D⁾A₀E₀F₁D₂B₥8₦C₱9⃕4⃕8⃗9⃛8⃛9⃜C⃜F⃥B⃩6⃪E⃱7⃲4⃲E⃴8⃷8⃺C⃽5⃽8⃿F'\ 438 | # 'ℂ0℃F℅A℆F℉2ℊ1ℋFℌ4ℌ8ℎ4ℑ4ℑ6ℒ3ℒ4ℒ9℔Fℕ8№5№7ℶB⅄D⅄EⅆDⅆF⅋6⅌1⅍7ⅎ6⅏E⅜6ⅻ5ⅾBↁAↃ9ↈ3↉8↋F↎8'\ 439 | # '→0→1→B↸9↺3↺4⇏3⇞8⇡7⇦C⇪0⇪8⇳1⇳E⇵7⇷3⇷5⇸6⇻1⇽6∑3∓C∖1∖3∧F∨3∷0≁7≖9≙5≭4≲D⊃0⊃C⊈0⊌F⊍0⊍A'\ 440 | # '⊎D⊐C⊑C⊒7⊒9⊓1⊓F⊖0⊾6⊾9⊿7⋆1⋉0⋊9⋊B⋋8⋋E⋌2⋍A⋒6⋒9⋖3⋙1⋙2⋚B⋜3⋜F⋝E⋞E⋠1⋡4⋡9⋣3⋣4⋣8⋤F⋦5⋧C'\ 441 | # '⋧F⋨E⋫3⋽3⋾1⌁8⌃7⌃B⌓8⌣6〉F⌬B⌭E⌰2⌵0⌸4⌹C⍓F⍤E⍧F⍩9⍮3⍵5⍸1⍹0⍻B⎁5⎂9⎃2⎄C⎇6⎐B⎓F⎥5⎭2⎾9⎿4⎿6'\ 442 | # '⏁B⏂8⏐7⏚F⏬F⏭1⏰A⏲9⏴F⏻7⏼9␂A␆3␆A␑9␓7␕9␖9␗7␟E␵6␵C␺4␻1␽0⑇3⑇9⑈E⑊6⑋B⑌C⑌E⑍3①0⑮E⑯1⑰6⑾4'\ 443 | # '⒁4⒂E⒇2⒉F⒌E⒎4⒖D⒤2⒪4⒫A⒮9⒰5Ⓔ6Ⓣ3Ⓤ2Ⓩ7Ⓩ8ⓜ3ⓟDⓢBⓨ9ⓩ4⓭C⓭D⓯2⓰8⓸9│C┃2┊B┋8┝4┧8┭D┰3┱A┽D═2'\ 444 | # '╖5╘5╘F╚9╛2╜7╟4╟9╟A╟D╠3╱0╳0╻5█A▊2▋6▋7□0▨2▾4◇8◌A◒8◓C◔3◔A◕B◕C◕D◢0◫C◮4◮6◯5◳6◳D◵6'\ 445 | # '◶D◷D◸2◹D◺F◼9◼A◾F☀E☁6☄4★5☆7☈5☈B☌4☍2☍8☎9☐B☐D☒7☓C☔7☔8☔B☕8☗7☘6☘8☛2☜E☝B☳E☴6☻9☽1♈0'\ 446 | # '♑6♢7♱6♹B♽0♿C⚀5⚄F⚅6⚅D⚆7⚇6⚈8⚌7⚌E⚖F⚟4⚟A⚪D⚫D⛄C⛍D⛕5⛘6⛣7⛪3⛵2⛸F⛻5⛻6⛼D✇F✈5✏D✵5✿B❁0❃1'\ 447 | # '❉6❊F❒5❕F❖6❚6❯8❰1❰2❱7❲3❳5❳6❵E❸5❹4❺3❺B❻6❼C➀8➂5➃5➄D➆A➇4➇8➈3➈4➈D➊2➏4➖3➗A➙D➚6➚7➚D'\ 448 | # '➝D➞D➟5➟8➠A➡D➣3➣E➥5➥9➦6➦7➦A➧C➩E➪1➪6➪A➪E➭A➭D➰1➰5➰7➰C➲4➲8➲A➲E➲F➳B➴8➷9➸6➸7➸8➹3⟀6'\ 449 | # '⟀E⟇B⟍F⟒A⟔A⟗3⟘4⟙4⟙F⟚7⟛2⟜E⟝B⟡6⟡8⟢6⟢A⟢B⟤8⟶2⟶F⟷5⟺5⠄2⠉0⠍8⠍C⠐9⠒3⠓0⠔D⠘5⠘9⠚A⠛1⠜1⠜D⠝7'\ 450 | # '⠝E⠞4⠞F⠟0⠟D⠠0⠠6⠠7⠠A⠠C⠥6⠧9⠪0⠫0⠫8⠫9⠫B⠬1⠭A⠮2⠮E⠰4⠰8⠴8⠴F⠵0⠵2⠷0⠷9⠸C⠺9⠺A⠺E⠽2⠽4⠾0⠾5⡃6'\ 451 | # '⡄A⡠C⡺8⡺A⡻A⡼A⢋F⢌3⢌8⢌9⢍E⢎7⢎8⢐B⢒1⢓B⢕B⢕C⢕F⢖6⢗A⢚1⢚B⢛1⢜0⢝0⢝A⢝C⢞B⢟0⢟1⢠F⢡B⢡D⢢2⢢F⢣9⢦8'\ 452 | # '⢧0⢨5⢨B⢩5⢬0⢭2⢯C⢰2⢱2⢱6⢱E⢱F⢴3⢴6⢴C⢴E⢵0⢵6⢵7⢵A⢵B⢶5⢷8⢸1⢸2⢸5⢻0⢻3⢼5⢽F⢿5⣀3⣀B⣂0⣂5⣂D⣃2⣃5'\ 453 | # '⣃7⣃9⣆5⣊D⣋3⣌C⣍0⣍1⣍2⣍5⣍9⣍A⣎8⣏8⣏F⣑1⣑7⣒4⣓9⣔6⣔C⣕7⣖4⣖6⣖9⣖C⣗8⣘0⣘F⣙1⣚E⣚F⣛0⣛2⣛B⣛F⣜8⣟2'\ 454 | # '⣟B⣳3⣴8⣴F⤂8⤕9⤖6⤗E⤜9⤤D⤥9⤬C⤯0⤵C⤹2⤹5⤹6⤹F⤺0⤺2⤼2⤼C⤾0⤾A⤿4⤿7⥀C⥄3⥅2⥅4⥆1⥆3⥆6⥈E⥉C⥉D⥋2⥋A'\ 455 | # '⥋C⥎3⥎5⥏8⥏9⥐7⥐8⥐A⥑1⥒3⥓3⥔A⥗0⥘1⥛0⥛F⥜0⥝3⥝B⥞1⥟4⥠0⥡A⥡D⥣9⥣A⥣B⥤8⥨5⥩A⥩B⥪5⥪9⥫5⥬6⥬C⥬E⥭E'\ 456 | # '⥮1⥮9⥯2⥰7⥲0⥲6⥲F⥳0⥳5⥳6⥵1⥵4⥶0⥶1⥶3⥶7⥷D⥸3⥸4⥸6⥸9⥺1⥺6⥺7⥺C⥺F⥼0⥼2⥽0⥽7⥾0⦃4⦆3⦆4⦇A⦈D⦊1⦋0'\ 457 | # '⦋2⦋4⦋8⦋C⦋E⦌A⦌B⦌F⦍1⦍4⦎1⦎B⦏5⦏A⦐A⦑9⦓2⦓5⦓8⦔3⦔4⦔5⦔7⦔9⦔E⦕1⦗2⦗C⦘3⦙A⦚0⦛A⦜6⦜9⦝0⦞2⦵9⦶F'\ 458 | # '⦼1⦼3⦼6⦿3⧀0⧃9⧄8⧎4⧐6⧓5⧕A⧖6⧖9⧗1⧗9⧗A⧘0⧘1⧙8⧚F⧛0⧛1⧝2⧝3⧝5⧟0⧟6⧠3⧠4⧠6⧢1⧢3⧢4⧢6⧢9⧢C⧤2⧤A'\ 459 | # '⧤E⧥D⧧D⧧E⧩D⧩E⧭7⧭B⧮7⧮C⧮E⧯0⧯1⧱4⧳6⧴5⧴7⧴8⧵4⧷7⧹0⧹2⧹D⧼5⧼A⧾4⧾7⧾A⧿1⧿A⨀9⨁6⨁7⨁A⨁B⨂6⨃B⨃E'\ 460 | # '⨄8⨄F⨅0⨅1⨅6⨅B⨅C⨇1⨇F⨈6⨈8⨊9⨊B⨌3⨌D⨌F⨍2⨎6⨎7⨎E⨏F⨐5⨐6⨑5⨒0⨓2⨓3⨔2⨔3⨕6⨕C⨗E⨘3⨚B⨛0⨛4⨛7⨜4'\ 461 | # '⨝6⨝8⨟0⨟3⨠F⨡4⨡7⨣C⨥6⨥C⨦3⨦8⨦E⨧1⨧8⨧F⨨9⨬8⨯C⨯D⨯F⨱0⨱2⨱7⨱8⨱C⨲3⨲8⨲C⨲D⨲E⨲F⨳0⨳D⨳E⨳F⨴0⨴7'\ 462 | # '⨴D⨵1⨵2⨵3⨵8⨵A⨵E⨶0⨶3⨶4⨶C⨷4⨷6⨷7⨷F⨸2⩅A⩇3⩊C⩋F⩍B⩎C⩏0⩏9⩏D⩓5⩖3⩚8⩜B⩝C⩝D⩞A⩞D⩟3⩟B⩟D⩠0⩠5'\ 463 | # '⩡3⩡E⩢5⩢7⩢8⩢9⩢C⩢F⩣2⩤9⩤D⩤F⩥1⩥5⩥E⩦4⩨5⩩4⩪3⩪D⩪E⩫0⩭5⩵6⩷5⩽6⪈D⪊5⪻0⪼2⫏7⫒5⫖2⫝̸8⬍0⬍1⬍E⬎5'\ 464 | # '⬏7⬐7⬞0⬣9⬤D⬭0⬮7⬱9⬵8⭉E⭊1⭊2⭋7⭑8⭒1⭙E⭝1⭝5⭟B⭲6⮏4⮕D⮙4⮙9⮛8⮝D⮡1⮩B⮰6⮳1⮽3⯋4⯚6⯭1⯺1ⰑDⰘ9Ⱖ4'\ 465 | # 'Ⱖ7ⰲ6ⰴ1ⰿ2ⱆ1ⱉ2ⱎ1ⱜFⱟAⱥ4Ɑ5ⲁ0ⲌDⲍ8ⲗ2ⲝ9Ⲹ7ⲸDⲽ8Ⳅ2Ⳅ8ⳉAⳉBⳔ2Ⳕ3Ⳗ8ⳖEⳛCⳤ2ⴉ6ⴧEⵅ9ⵞ1ⶉ2⶝2⶝6ⶢ1ⷅ8'\ 466 | # 'ⷙ9⹱7⹿D⺄8⺐F⺑2⺙7⺢D⺣B《0》4』4〡D〤0〬6〻Cげ0げBこAせBぢFと2な3ぶ2まBゅ3ガ6ガFキBク3タ2バFヒ6ビDヤ8ロEヾ2ㄎ1'\ 467 | # 'ㄎ2ㄎAㄚ5ㄜB' 468 | __p_zh_t: ClassVar[regex.Pattern[str]] = regex.compile( 469 | rf"(?![{__JAPANESE_KANJIS_SEQUENCE_1ST}" 470 | + rf"{__JAPANESE_KANJIS_SEQUENCE_2ND_JOYO}" 471 | + rf"{__JAPANESE_KANJIS_SEQUENCE_3RD_JOYO}])" 472 | + rf"(?=[{__CHINESE_KANJIS_SEQUENCE_TRADITIONAL}])" 473 | ) 474 | # 475 | __p_zh_all: ClassVar[regex.Pattern[str]] = regex.compile( 476 | rf"(?![{__JAPANESE_KANJIS_SEQUENCE_1ST}" 477 | + rf"{__JAPANESE_KANJIS_SEQUENCE_2ND_JOYO}" 478 | + rf"{__JAPANESE_KANJIS_SEQUENCE_3RD_JOYO}])" 479 | + r"(?=[\p{Script=Han}])" 480 | ) 481 | # 482 | __lang_from_converters: ClassVar[dict[TERTransService, dict[str, str]]] = { 483 | TERTransService.DEEPLKEY: { 484 | str(googletrans.LANGCODES["norwegian"]) 485 | .casefold() 486 | .strip(): deepl.Language.NORWEGIAN.casefold().strip(), 487 | str(googletrans.LANGCODES["chinese (simplified)"]) 488 | .casefold() 489 | .strip(): deepl.Language.CHINESE.casefold().strip(), 490 | str(googletrans.LANGCODES["chinese (traditional)"]) 491 | .casefold() 492 | .strip(): deepl.Language.CHINESE.casefold().strip(), 493 | }, 494 | TERTransService.DEEPLTRANSLATE: { 495 | str(googletrans.LANGCODES["chinese (simplified)"]) 496 | .casefold() 497 | .strip(): deepl.Language.CHINESE.casefold().strip(), 498 | str(googletrans.LANGCODES["chinese (traditional)"]) 499 | .casefold() 500 | .strip(): deepl.Language.CHINESE.casefold().strip(), 501 | }, 502 | } 503 | 504 | @classmethod 505 | def detect_cj(cls, _text: str, _service: TERTransService) -> str | None: 506 | if _service in [ 507 | TERTransService.DEEPLKEY, 508 | TERTransService.DEEPLTRANSLATE, 509 | ]: 510 | if cls.__p_ja_kana.search(_text) is not None: 511 | return deepl.Language.JAPANESE.casefold().strip() 512 | elif cls.__p_zh_all.search(_text) is not None: 513 | return deepl.Language.CHINESE.casefold().strip() 514 | elif cls.__p_ja_kanji.search(_text) is not None: 515 | return deepl.Language.JAPANESE.casefold().strip() 516 | else: 517 | return None 518 | elif _service in [ 519 | TERTransService.GOOGLETRANS, 520 | TERTransService.GOOGLEGAS, 521 | ]: 522 | if cls.__p_ja_kana.search(_text) is not None: 523 | return ( 524 | str(googletrans.LANGCODES["japanese"]).casefold().strip() 525 | ) 526 | elif cls.__p_zh_s.search(_text) is not None: 527 | return ( 528 | str(googletrans.LANGCODES["chinese (simplified)"]) 529 | .casefold() 530 | .strip() 531 | ) 532 | elif cls.__p_zh_t.search(_text) is not None: 533 | return ( 534 | str(googletrans.LANGCODES["chinese (traditional)"]) 535 | .casefold() 536 | .strip() 537 | ) 538 | elif cls.__p_zh_all.search(_text) is not None: 539 | return ( 540 | str(googletrans.LANGCODES["chinese (simplified)"]) 541 | .casefold() 542 | .strip() 543 | ) 544 | elif cls.__p_ja_kanji.search(_text) is not None: 545 | return ( 546 | str(googletrans.LANGCODES["japanese"]).casefold().strip() 547 | ) 548 | else: 549 | return None 550 | else: 551 | assert False, f"Translation service is {_service}." 552 | 553 | @classmethod 554 | def convert_from( 555 | cls, _lang_from_g_detection: str, _service: TERTransService 556 | ) -> str: 557 | return cls.__lang_from_converters.get(_service, {}).get( 558 | _lang_from_g_detection, _lang_from_g_detection 559 | ) 560 | -------------------------------------------------------------------------------- /Codes/ter/cogs/base_cog.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | import queue 6 | from typing import Any 7 | 8 | from twitchio import Channel, HTTPException, PartialUser, User 9 | from twitchio.ext import commands 10 | 11 | 12 | # Classes 13 | class TERBaseCog(commands.Cog): 14 | def __init__( 15 | self, 16 | _token: str, 17 | _pu: PartialUser, 18 | _settings_base: dict[str, Any], 19 | _response_cms_base: list[list[Any]], 20 | ) -> None: 21 | print(f" {self.name}") 22 | self.__token: str = _token 23 | self.__pu: PartialUser = _pu 24 | self.__settings_base: dict[str, Any] = _settings_base 25 | self.__response_cms_base: list[list[Any]] = _response_cms_base 26 | if len(self.__response_cms_base) > 0: 27 | for rcb in self.__response_cms_base: 28 | print(f" {rcb}") 29 | 30 | def get_settings_replaced( 31 | self, _replacements: dict[str, str] 32 | ) -> dict[str, Any]: 33 | return _replace_recursively_d(self.__settings_base, _replacements) 34 | 35 | def get_cm_units_replaced( 36 | self, _replacements: dict[str, str] 37 | ) -> list[TERCommandMessageUnit]: 38 | return [ 39 | TERCommandMessageUnit(cm_base, _replacements) 40 | for cm_base in self.__response_cms_base 41 | ] 42 | 43 | async def execute_cms( 44 | self, _channel: Channel, _cm_units: list[TERCommandMessageUnit] 45 | ) -> None: 46 | index: int = 0 47 | q: queue.Queue[TERCommandMessageUnit] = queue.Queue() 48 | for _cm_unit in _cm_units: 49 | q.put(_cm_unit) 50 | # 51 | # 52 | print(" Commands or messages") 53 | while q.empty() is False: 54 | # 送信前の待機(秒) 55 | index += 1 56 | cm_unit: TERCommandMessageUnit = q.get() 57 | print( 58 | f" {index} (after {cm_unit.sleep_sec:>3} s.) = " 59 | + f"{cm_unit.cm_replaced} (+ {cm_unit.args_replaced}) ... ", 60 | end="", 61 | flush=True, 62 | ) 63 | # 64 | await asyncio.sleep(cm_unit.sleep_sec) 65 | # 66 | # 67 | # (コマンド) 68 | # /shoutout メッセージ送信先チャンネルにレイドをしたユーザー 69 | if cm_unit.cm_replaced == "/shoutout": 70 | channel_broadcaster_user: User = await _channel.user() 71 | try: 72 | await self.__pu._http.post_shoutout( 73 | self.__token, 74 | str(channel_broadcaster_user.id), 75 | str(self.__pu.id), 76 | str(cm_unit.args_replaced[0]), 77 | ) 78 | except HTTPException as e: 79 | print("failed.") 80 | print(f" {e}") 81 | num_retrials: int = ( 82 | 1 83 | if len(cm_unit.args_replaced) < 3 84 | else int(cm_unit.args_replaced[2]) + 1 85 | ) 86 | cm_base: list[Any] = [ 87 | 125, 88 | cm_unit.cm_replaced, 89 | cm_unit.args_replaced[0], 90 | "(Retrial)", 91 | num_retrials, 92 | ] 93 | q.put(TERCommandMessageUnit(cm_base, {})) 94 | continue 95 | # 96 | # ToDo: ★ (コマンド) 別のコマンドにも対応する場合は、ここに実装する 97 | # elif cm_unit.cm_replaced == '/????': 98 | # pass 99 | # 100 | # (メッセージ) 101 | else: 102 | await _channel.send(cm_unit.cm_replaced) 103 | # 104 | print("done.") 105 | 106 | 107 | class TERCommandMessageUnit: 108 | def __init__( 109 | self, _cm_base: list[Any], _replacements: dict[str, str] 110 | ) -> None: 111 | self.__sleep_sec: int = int(_cm_base[0]) 112 | # 113 | self.__cm_replaced: str = str(_cm_base[1]) 114 | for rk, rv in _replacements.items(): 115 | self.__cm_replaced = self.__cm_replaced.replace(rk, rv) 116 | # 117 | self.__args_replaced: list[Any] = _replace_recursively_l( 118 | [a for a in _cm_base[2:]], _replacements 119 | ) 120 | 121 | @property 122 | def sleep_sec(self) -> int: 123 | return self.__sleep_sec 124 | 125 | @property 126 | def cm_replaced(self) -> str: 127 | return self.__cm_replaced 128 | 129 | def extend_args_replaced(self, _args_to_add: list[Any]) -> None: 130 | self.__args_replaced.extend(_args_to_add) 131 | 132 | @property 133 | def args_replaced(self) -> list[Any]: 134 | return self.__args_replaced 135 | 136 | 137 | # Functions 138 | def _replace_recursively_d( 139 | d_base: dict[str, Any], _replacements: dict[str, str] 140 | ) -> dict[str, Any]: 141 | d_replaced: dict[str, Any] = {} 142 | for k_vase, v_base in d_base.items(): 143 | v_replaced: Any 144 | if isinstance(v_base, str) is True: 145 | v_replaced = v_base 146 | for rk, rv in _replacements.items(): 147 | v_replaced = v_replaced.replace(rk, rv) 148 | elif isinstance(v_base, list) is True: 149 | v_replaced = _replace_recursively_l(v_base, _replacements) 150 | elif isinstance(v_base, dict) is True: 151 | v_replaced = _replace_recursively_d(v_base, _replacements) 152 | else: 153 | v_replaced = v_base 154 | d_replaced[k_vase.strip()] = v_replaced 155 | return d_replaced 156 | 157 | 158 | def _replace_recursively_l( 159 | l_base: list[Any], _replacements: dict[str, str] 160 | ) -> list[Any]: 161 | l_replaced: list[Any] = [] 162 | for e_base in l_base: 163 | e_replaced: Any 164 | if isinstance(e_base, str): 165 | e_replaced = e_base 166 | for rk, rv in _replacements.items(): 167 | e_replaced = e_replaced.replace(rk, rv) 168 | elif isinstance(e_base, list) is True: 169 | e_replaced = _replace_recursively_l(e_base, _replacements) 170 | elif isinstance(e_base, dict) is True: 171 | e_replaced = _replace_recursively_d(e_base, _replacements) 172 | else: 173 | e_replaced = e_base 174 | l_replaced.append(e_replaced) 175 | return l_replaced 176 | -------------------------------------------------------------------------------- /Codes/ter/cogs/bouyoumi_cog.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import pathlib 5 | import subprocess 6 | from typing import Any 7 | 8 | import aiohttp 9 | from twitchio import Chatter, Message, PartialUser 10 | from twitchio.ext import commands 11 | 12 | from .base_cog import TERBaseCog 13 | 14 | 15 | # Classes 16 | class TERBouyomiCog(TERBaseCog): 17 | def __init__( 18 | self, 19 | _token: str, 20 | _pu: PartialUser, 21 | _settings_base: dict[str, Any], 22 | _response_cms_base: list[list[Any]], 23 | _prefix: str, 24 | ) -> None: 25 | super().__init__(_token, _pu, _settings_base, _response_cms_base) 26 | # 27 | # 28 | # セッション 29 | self.__session: aiohttp.ClientSession | None = _pu._http.session 30 | # 棒読みちゃん プロセス 31 | self.__p: subprocess.Popen | None = None 32 | # 33 | # 34 | # 設定値 35 | self.__settings_replaced: dict[str, Any] = self.get_settings_replaced( 36 | {} 37 | ) 38 | # 39 | # 40 | # メッセージたちを受け渡すか否か 41 | self.__sends_messages = bool(self.__settings_replaced["sendsMessages"]) 42 | if self.__sends_messages is False: 43 | return 44 | # 45 | # 46 | # 自動起動・停止とパス 47 | arkp: pathlib.Path = pathlib.Path( 48 | rf"{self.__settings_replaced['autoRunKillPath']}" 49 | ) 50 | self.__auto_run_kill_path: pathlib.Path | None = ( 51 | arkp if arkp.exists() is True and arkp.is_file() is True else None 52 | ) 53 | # ポート番号 54 | self.__port_no: int = int(self.__settings_replaced["portNo"]) 55 | # 56 | if self.__auto_run_kill_path is not None: 57 | try: 58 | print( 59 | f' Running "{str(self.__auto_run_kill_path)}" ... ', 60 | end="", 61 | ) 62 | self.__p = subprocess.Popen(self.__auto_run_kill_path) 63 | print("done.") 64 | except (OSError, subprocess.CalledProcessError) as e: 65 | print("failed") 66 | print(f" {e}") 67 | # 68 | # 69 | # 受け渡すメッセージたちに対する制限 70 | # 送信ユーザーのユーザー名ないし表示名の、 71 | # 末尾の数字部分を省略するか否か 72 | self.__ignores_sender_name_suffix_num: bool = bool( 73 | self.__settings_replaced["limitsWhenPassing"][ 74 | "ignoresSenderNameSuffixNum" 75 | ] 76 | ) 77 | # 送信ユーザーのユーザー名ないし表示名の、先頭からの文字数の上限 78 | self.__num_sender_name_characters: int = int( 79 | self.__settings_replaced["limitsWhenPassing"][ 80 | "numSenderNameCharacters" 81 | ] 82 | ) 83 | # 先頭からのエモート(スタンプ)数の上限 84 | self.__num_emotes: int = int( 85 | self.__settings_replaced["limitsWhenPassing"]["numEmotes"] 86 | ) 87 | # 88 | # 89 | # 受け渡さないメッセージたち 90 | # 送信したユーザーたち 91 | self.__sender_user_names_to_ignore: list[str] = [ 92 | str(s).casefold().strip() 93 | for s in self.__settings_replaced["messagesToIgnore"][ 94 | "senderUserNames" 95 | ] 96 | if str(s).casefold().strip() != "" 97 | ] 98 | # ユーザーコマンドの接頭辞たち ( _ も含む) 99 | self.__user_command_prefixes_to_ignore: list[str] = [ 100 | str(s).strip() 101 | for s in self.__settings_replaced["messagesToIgnore"][ 102 | "userCommandPrefixes" 103 | ] 104 | if str(s).strip() != "" 105 | ] 106 | self.__user_command_prefixes_to_ignore.append(_prefix) 107 | # メッセージ内の文字列たち 108 | self.__strings_in_message_to_ignore: list[str] = [ 109 | str(s).strip() 110 | for s in self.__settings_replaced["messagesToIgnore"][ 111 | "stringsInMessage" 112 | ] 113 | if str(s).strip() != "" 114 | ] 115 | # 116 | # 117 | # 翻訳先メッセージの構成 118 | self.__messages_format: str = str( 119 | self.__settings_replaced["messagesFormat"] 120 | ).strip() 121 | 122 | @commands.Cog.event(event="event_message") 123 | async def message_response(self, message: Message) -> None: 124 | # (受け渡さない 1/3) 125 | # セッションがない 126 | if self.__session is None: 127 | return 128 | # メッセージたちを受け渡さないように設定されている 129 | if self.__sends_messages is False: 130 | return 131 | # メッセージがボットによる反応によるものである 132 | if bool(message.echo) is True: 133 | return 134 | # 135 | # 136 | # メッセージから文字列を除去 137 | # 左右空白 138 | text: str = ( 139 | "" if message.content is None else str(message.content).strip() 140 | ) 141 | # /me 公式コマンド実行時に付随してくるもの 142 | text = text.removeprefix("\x01ACTION").removesuffix("\x01") 143 | # エモート文字列たち 144 | if ( 145 | message.tags is not None 146 | and "emotes" in message.tags.keys() 147 | and str(message.tags["emotes"]) != "" 148 | ): 149 | emote_names: list[str] = [] 150 | # 151 | emote_id_positions_col: list[str] = str( 152 | message.tags["emotes"] 153 | ).split("/") 154 | for emote_id_positions in emote_id_positions_col: 155 | id_and_positions: list[str] = emote_id_positions.split(":") 156 | assert len(id_and_positions) == 2, ( 157 | f"Emote ID & positions are {id_and_positions}." 158 | ) 159 | first_position: str = id_and_positions[1].split(",")[0] 160 | from_and_to: list[str] = first_position.split("-") 161 | assert len(from_and_to) == 2, ( 162 | f"1st position of {id_and_positions[0]} is {from_and_to}." 163 | ) 164 | # (* メッセージ(各種削除前のもの)からエモート名を特定) 165 | if message.content is not None: 166 | emote_names.append( 167 | str(message.content)[ 168 | int(from_and_to[0]) : int(from_and_to[1]) + 1 169 | ] 170 | ) 171 | # 172 | # エモートの削除 173 | words: list[str] = text.split() 174 | emote_order: int = 0 175 | for i, word in enumerate(words): 176 | if word in emote_names: 177 | if emote_order >= self.__num_emotes: 178 | words[i] = "" 179 | emote_order += 1 180 | text = " ".join(words) 181 | # 182 | # 単語間を半角空白1つで統一 183 | text = " ".join(text.split()) 184 | # 185 | # (受け渡さない 2/3) メッセージが空になった 186 | if text == "": 187 | return 188 | # 189 | # 190 | # 送信者のユーザー名(小文字化済み, 左右空白除去済み) 191 | sender_user_name: str = ( 192 | "" 193 | if message.author.name is None 194 | else str(message.author.name).casefold().strip() 195 | ) 196 | # 送信者の表示名(左右空白除去済み) 197 | sender_display_name: str = ( 198 | "" 199 | if type(message.author) is not Chatter 200 | else ( 201 | "" 202 | if message.author.display_name is None 203 | else str(message.author.display_name).strip() 204 | ) 205 | ) 206 | # 207 | # 208 | # (受け渡さない 3/3) 209 | # メッセージ送信者が翻訳しないユーザー名たちの中に含まれている 210 | if sender_user_name in self.__sender_user_names_to_ignore: 211 | return 212 | # メッセージの接頭辞が 213 | # 翻訳しないユーザーコマンドの接頭辞たちの中に含まれている 214 | for ( 215 | user_command_prefix_to_ignore 216 | ) in self.__user_command_prefixes_to_ignore: 217 | if text.startswith(user_command_prefix_to_ignore): 218 | return 219 | # メッセージに翻訳しない文字列たちのいずれかが含まれている 220 | for string_in_message_to_ignore in self.__strings_in_message_to_ignore: 221 | if string_in_message_to_ignore in text: 222 | return 223 | # 224 | # 225 | # 送信ユーザーのユーザー名ないし表示名に対する制限を適用 226 | if self.__ignores_sender_name_suffix_num is True: 227 | sender_user_name = sender_user_name.rstrip( 228 | "01234567890123456789" 229 | ) 230 | sender_display_name = sender_display_name.rstrip( 231 | "01234567890123456789" 232 | ) 233 | sender_user_name = sender_user_name[ 234 | : self.__num_sender_name_characters 235 | ] 236 | sender_display_name = sender_display_name[ 237 | : self.__num_sender_name_characters 238 | ] 239 | # 240 | # 241 | # 置換される文字列たちを定義 242 | replacements: dict[str, str] = { 243 | "{{senderUserName}}": sender_user_name, 244 | "{{senderDisplayName}}": sender_display_name, 245 | "{{senderMessage}}": text, 246 | # 247 | # ToDo: ★ (置換) 別の文字列置換にも対応する場合は、ここに実装する 248 | # '{{????}}': '????', 249 | } 250 | # 置換後の受け渡しメッセージ 251 | m: str = self.__messages_format 252 | for k, v in replacements.items(): 253 | m = m.replace(k, v) 254 | # 空白類を %20 に置換 255 | m = "%20".join(m.split()) 256 | # 257 | # 258 | try: 259 | async with self.__session.get( 260 | f"http://localhost:{self.__port_no}/talk?text={m}" 261 | ) as res: 262 | res.raise_for_status() 263 | if res.status != 200: 264 | raise ValueError(f"{res.status=}, {await res.text()=}") 265 | except Exception as e: 266 | print(f' Passing "{m}" to BouyomiChan failed.') 267 | print(f" {e}") 268 | print("") 269 | 270 | def kill_process(self) -> None: 271 | if self.__p is not None and self.__p.poll() is None: 272 | print(" Killing BouyomiChan ... ", end="") 273 | self.__p.kill() 274 | print("done.") 275 | -------------------------------------------------------------------------------- /Codes/ter/cogs/raid_cog.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | from typing import Any 5 | 6 | from twitchio import Channel 7 | from twitchio.ext import commands 8 | 9 | from .base_cog import TERBaseCog, TERCommandMessageUnit 10 | 11 | 12 | # Classes 13 | class TERRaidCog(TERBaseCog): 14 | @commands.Cog.event(event="event_raw_usernotice") 15 | async def raid_response( 16 | self, channel: Channel, tags: dict[Any, Any] 17 | ) -> None: 18 | msg_id: str = tags.get("msg-id", "") 19 | if msg_id != "raid": 20 | return 21 | # 22 | # 23 | print(" Responding to raid ...") 24 | raid_broadcaster_user_name: str = tags.get("msg-param-login", "") 25 | print(f" Raid broadcaster user name = {raid_broadcaster_user_name}") 26 | # 27 | # 28 | # コマンドやメッセージたち 29 | # 置換される文字列たちを定義 30 | replacements: dict[str, str] = { 31 | "{{raidBroadcasterUserName}}": raid_broadcaster_user_name, 32 | # 33 | # ToDo: ★ (置換) 別の文字列置換にも対応する場合は、ここに実装する 34 | # '{{????}}': '????', 35 | } 36 | # 置換後のコマンドやメッセージたち 37 | cm_units_replaced: list[TERCommandMessageUnit] = ( 38 | self.get_cm_units_replaced(replacements) 39 | ) 40 | # 41 | # 42 | cm_units_valid: list[TERCommandMessageUnit] = [] 43 | for cm_unit in cm_units_replaced: 44 | # (コマンド) /shoutout の場合 45 | if cm_unit.cm_replaced == "/shoutout": 46 | # レイド元のユーザー(チャンネル)IDを取得して利用 47 | shoutout_broadcaster_user_id: str = str( 48 | tags.get("user-id", -1) 49 | ) 50 | print( 51 | " Shoutout broadcaster user ID = " 52 | + f"{shoutout_broadcaster_user_id}" 53 | ) 54 | if shoutout_broadcaster_user_id == "-1": 55 | print(" * Invalid ID") 56 | continue 57 | else: 58 | cm_unit.extend_args_replaced( 59 | [shoutout_broadcaster_user_id] 60 | ) 61 | # 62 | # ToDo: ★ (コマンド) 63 | # 別のコマンドにも対応する場合は、追加の引数取得をここに実装する 64 | # elif cm_unit.cm_replaced == '/????': 65 | # pass 66 | else: 67 | pass 68 | cm_units_valid.append(cm_unit) 69 | # 70 | await self.execute_cms(channel, cm_units_valid) 71 | print(" done.") 72 | print("") 73 | -------------------------------------------------------------------------------- /Codes/ter/cogs/trans_cog.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import json 5 | from typing import Any 6 | 7 | import aiohttp 8 | import deepl 9 | import deepltranslate 10 | import deepltranslate.settings 11 | import googletrans 12 | import googletrans.models 13 | from twitchio import Chatter, Message, PartialUser 14 | from twitchio.ext import commands 15 | 16 | from .__lang import TERLang, TERTransService 17 | from .base_cog import TERBaseCog 18 | 19 | 20 | # Classes 21 | class TERTransCog(TERBaseCog): 22 | def __init__( 23 | self, 24 | _token: str, 25 | _pu: PartialUser, 26 | _settings_base: dict[str, Any], 27 | _response_cms_base: list[list[Any]], 28 | _prefix: str, 29 | ) -> None: 30 | super().__init__(_token, _pu, _settings_base, _response_cms_base) 31 | # 32 | # 33 | # 設定値 34 | self.__settings_replaced: dict[str, Any] = self.get_settings_replaced( 35 | {} 36 | ) 37 | # 38 | # 39 | # 使用する翻訳サービスたち 40 | self.__services: list[TERTransService] = [] 41 | # 対応言語たち 42 | self.__from_langs_all: dict[TERTransService, list[str]] = {} 43 | self.__to_langs_all: dict[TERTransService, list[str]] = {} 44 | # 翻訳サービスたち 45 | self.__translator_d_trans: deepl.Translator | None = None 46 | self.__translator_g_trans: googletrans.Translator | None = None 47 | self.__translator_g_session_url: ( 48 | tuple[aiohttp.ClientSession, str] | None 49 | ) = None 50 | print(" Services") 51 | for service_with_key_or_url in self.__settings_replaced[ 52 | "servicesWithKeyOrURL" 53 | ]: 54 | if isinstance(service_with_key_or_url, list) is False: 55 | continue 56 | tts: TERTransService | None = TERTransService.get( 57 | str(service_with_key_or_url[0]) 58 | ) 59 | if tts is None or len(service_with_key_or_url) <= 1: 60 | continue 61 | key_or_url: str = str(service_with_key_or_url[1]).strip() 62 | # DeepL Python Library 63 | if tts is TERTransService.DEEPLKEY: 64 | try: 65 | print(f" {tts.name}") 66 | print(" Getting instance ... ", end="") 67 | self.__translator_d_trans = deepl.Translator(key_or_url) 68 | # 対応言語文字列たちは小文字で持つことにする(以下、同様) 69 | self.__from_langs_all[tts] = [ 70 | (g.code).casefold().strip() 71 | for g in self.__translator_d_trans.get_source_languages() 72 | ] 73 | self.__to_langs_all[tts] = [ 74 | (g.code).casefold().strip() 75 | for g in self.__translator_d_trans.get_target_languages() 76 | ] 77 | print("done.") 78 | except deepl.exceptions.AuthorizationException as e: 79 | print("failed.") 80 | print(f" {e}") 81 | continue 82 | # DeepL Translate 83 | elif tts is TERTransService.DEEPLTRANSLATE: 84 | print(f" {tts.name}") 85 | self.__from_langs_all[tts] = [ 86 | d["code"].casefold().strip() 87 | for d in deepltranslate.settings.SUPPORTED_LANGUAGES 88 | ] 89 | self.__to_langs_all[tts] = [ 90 | d["code"].casefold().strip() 91 | for d in deepltranslate.settings.SUPPORTED_LANGUAGES 92 | ] 93 | # Googletrans 94 | elif tts is TERTransService.GOOGLETRANS: 95 | try: 96 | print(f" {tts.name}") 97 | print(" Getting instance ... ", end="") 98 | self.__translator_g_trans = googletrans.Translator( 99 | service_urls=[ 100 | key_or_url, 101 | ], 102 | raise_exception=True, 103 | ) 104 | self.__from_langs_all[tts] = [ 105 | k.casefold().strip() 106 | for k in googletrans.LANGUAGES.keys() 107 | ] 108 | self.__to_langs_all[tts] = [ 109 | k.casefold().strip() 110 | for k in googletrans.LANGUAGES.keys() 111 | ] 112 | print("done.") 113 | except Exception as e: 114 | print("failed.") 115 | print(f" {e}") 116 | continue 117 | # Google Apps Script (GAS) 118 | elif tts is TERTransService.GOOGLEGAS: 119 | if _pu._http.session is None: 120 | continue 121 | print(f" {tts.name}") 122 | self.__translator_g_session_url = ( 123 | _pu._http.session, 124 | key_or_url, 125 | ) 126 | self.__from_langs_all[tts] = [ 127 | k.casefold().strip() for k in googletrans.LANGUAGES.keys() 128 | ] 129 | self.__to_langs_all[tts] = [ 130 | k.casefold().strip() for k in googletrans.LANGUAGES.keys() 131 | ] 132 | else: 133 | assert False, f"Translation service is {tts}." 134 | # 135 | self.__services.append(tts) 136 | # 137 | # 138 | # 翻訳元言語の推定に使うための googleTrans 139 | self.__translator_g_detection: googletrans.Translator | None = None 140 | try: 141 | print(" Getting language detection function ... ", end="") 142 | self.__translator_g_detection = googletrans.Translator( 143 | service_urls=[ 144 | self.__settings_replaced["fromLanguageDetection"], 145 | ], 146 | raise_exception=True, 147 | ) 148 | print("done.") 149 | except Exception as e: 150 | print("failed.") 151 | print(f" {e}") 152 | # 153 | # 154 | # 翻訳しないメッセージたち 155 | # 送信したユーザーたち 156 | self.__sender_user_names_to_ignore: list[str] = [ 157 | str(s).casefold().strip() 158 | for s in self.__settings_replaced["messagesToIgnore"][ 159 | "senderUserNames" 160 | ] 161 | if str(s).casefold().strip() != "" 162 | ] 163 | # 翻訳元言語たち 164 | self.__from_langs_to_ignore: list[str] = [ 165 | str(s).casefold().strip() 166 | for s in self.__settings_replaced["messagesToIgnore"][ 167 | "fromLanguages" 168 | ] 169 | for tts in self.__from_langs_all.keys() 170 | if str(s).casefold().strip() in self.__from_langs_all[tts] 171 | ] 172 | # ユーザーコマンドの接頭辞たち ( _ も含む) 173 | self.__user_command_prefixes_to_ignore: list[str] = [ 174 | str(s).strip() 175 | for s in self.__settings_replaced["messagesToIgnore"][ 176 | "userCommandPrefixes" 177 | ] 178 | if str(s).strip() != "" 179 | ] 180 | self.__user_command_prefixes_to_ignore.append(_prefix) 181 | # メッセージ内の文字列たち 182 | self.__strings_in_message_to_ignore: list[str] = [ 183 | str(s).strip() 184 | for s in self.__settings_replaced["messagesToIgnore"][ 185 | "stringsInMessage" 186 | ] 187 | if str(s).strip() != "" 188 | ] 189 | # 190 | # 191 | # 翻訳先言語(たち) 192 | # 既定(たち) 193 | self.__to_langs_default: list[str] = [ 194 | str(s).casefold().strip() 195 | for s in self.__settings_replaced["toLanguages"]["defaults"] 196 | for tts in self.__to_langs_all.keys() 197 | if str(s).casefold().strip() in self.__to_langs_all[tts] 198 | ] 199 | if len(self.__to_langs_default) == 0: 200 | self.__services = [] 201 | return 202 | # 翻訳元言語が既定の翻訳先言語であった場合(たち) 203 | self.__to_langs_if_from_lang_is_in_defaults: list[str] = [ 204 | str(s).casefold().strip() 205 | for s in self.__settings_replaced["toLanguages"][ 206 | "onesIfFromLanguageIsInDefaults" 207 | ] 208 | for tts in self.__to_langs_all.keys() 209 | if str(s).casefold().strip() in self.__to_langs_all[tts] 210 | ] 211 | # 212 | # 213 | # 翻訳先メッセージの構成 214 | self.__messages_format: str = str( 215 | self.__settings_replaced["messagesFormat"] 216 | ).strip() 217 | 218 | @commands.Cog.event(event="event_message") 219 | async def message_response(self, message: Message) -> None: 220 | # (翻訳しない 1/6) 221 | # 翻訳サービスが全く設定されていない 222 | if len(self.__services) == 0: 223 | return 224 | # メッセージがボットによる反応によるものである 225 | if bool(message.echo) is True: 226 | return 227 | # 228 | # 229 | # メッセージから文字列を除去 230 | # 左右空白 231 | text_from_w_force_service_langs: str = ( 232 | "" if message.content is None else str(message.content).strip() 233 | ) 234 | # /me 公式コマンド実行時に付随してくるもの 235 | text_from_w_force_service_langs = ( 236 | text_from_w_force_service_langs.removeprefix( 237 | "\x01ACTION" 238 | ).removesuffix("\x01") 239 | ) 240 | # エモート文字列たち 241 | if ( 242 | message.tags is not None 243 | and "emotes" in message.tags.keys() 244 | and str(message.tags["emotes"]) != "" 245 | ): 246 | emote_names: list[str] = [] 247 | # 248 | emote_id_positions_col: list[str] = str( 249 | message.tags["emotes"] 250 | ).split("/") 251 | for emote_id_positions in emote_id_positions_col: 252 | id_and_positions: list[str] = emote_id_positions.split(":") 253 | assert len(id_and_positions) == 2, ( 254 | f"Emote ID & positions are {id_and_positions}." 255 | ) 256 | first_position: str = id_and_positions[1].split(",")[0] 257 | from_and_to: list[str] = first_position.split("-") 258 | assert len(from_and_to) == 2, ( 259 | f"1st position of {id_and_positions[0]} is {from_and_to}." 260 | ) 261 | # (* メッセージ(各種削除前のもの)からエモート名を特定) 262 | if message.content is not None: 263 | emote_names.append( 264 | str(message.content)[ 265 | int(from_and_to[0]) : int(from_and_to[1]) + 1 266 | ] 267 | ) 268 | # 269 | # エモートの削除 270 | for emote_name in emote_names: 271 | text_from_w_force_service_langs = ( 272 | text_from_w_force_service_langs.replace(emote_name, "") 273 | ) 274 | # 単語間を半角空白1つで統一 275 | text_from_w_force_service_langs = " ".join( 276 | text_from_w_force_service_langs.split() 277 | ) 278 | # 279 | # (翻訳しない 2/6) メッセージが空になった 280 | if text_from_w_force_service_langs == "": 281 | return 282 | # 283 | # 284 | # 送信者のユーザー名(小文字化済み, 左右空白除去済み) 285 | sender_user_name: str = ( 286 | "" 287 | if message.author.name is None 288 | else str(message.author.name).casefold().strip() 289 | ) 290 | # 送信者の表示名(左右空白除去済み) 291 | sender_display_name: str = ( 292 | "" 293 | if type(message.author) is not Chatter 294 | else ( 295 | "" 296 | if message.author.display_name is None 297 | else str(message.author.display_name).strip() 298 | ) 299 | ) 300 | # 必ず翻訳するか否か 301 | forces_translation: bool = False 302 | text_from_w_service_langs: str = text_from_w_force_service_langs 303 | force_and_text_froms: list[str] = text_from_w_service_langs.split( 304 | " ^ " 305 | ) 306 | if ( 307 | len(force_and_text_froms) >= 2 308 | and force_and_text_froms[0] == "trnslt" 309 | ): 310 | forces_translation = True 311 | text_from_w_service_langs = " ^ ".join(force_and_text_froms[1:]) 312 | # 313 | # (翻訳しない 3/6) 314 | # メッセージ送信者が翻訳しないユーザー名たちの中に含まれている 315 | if ( 316 | sender_user_name in self.__sender_user_names_to_ignore 317 | and forces_translation is False 318 | ): 319 | return 320 | # メッセージの接頭辞が翻訳しないユーザーコマンドの接頭辞たちの中に 321 | # 含まれている 322 | for ( 323 | user_command_prefix_to_ignore 324 | ) in self.__user_command_prefixes_to_ignore: 325 | if ( 326 | text_from_w_service_langs.startswith( 327 | user_command_prefix_to_ignore 328 | ) 329 | and forces_translation is False 330 | ): 331 | return 332 | # メッセージに翻訳しない文字列たちのいずれかが含まれている 333 | for string_in_message_to_ignore in self.__strings_in_message_to_ignore: 334 | if ( 335 | string_in_message_to_ignore in text_from_w_service_langs 336 | and forces_translation is False 337 | ): 338 | return 339 | # 340 | # 341 | # 翻訳処理 342 | text_to: str = "" 343 | lang_from: str | None = None 344 | lang_to: str | None = None 345 | # サービスのリスト 346 | services: list[TERTransService] = self.__services 347 | index_service: int = 0 348 | services_to_be_removed: list[TERTransService] = [] 349 | # メッセージ内に指定があれば従う 350 | text_from_w_langs: str = text_from_w_service_langs 351 | service_and_text_froms: list[str] = text_from_w_langs.split(" ~ ") 352 | if len(service_and_text_froms) >= 2: 353 | tts: TERTransService | None = TERTransService.get( 354 | service_and_text_froms[0].casefold().strip() 355 | ) 356 | if tts in self.__services: 357 | services = [tts] 358 | text_from_w_langs = " ~ ".join(service_and_text_froms[1:]) 359 | # 360 | # 現在のサービス~最後のサービスまでの間でサービスの選択と翻訳 361 | while index_service < len(services): 362 | # 翻訳元言語 363 | lang_from = None 364 | text_from_wo_langs: str = text_from_w_langs 365 | # メッセージ内に指定があれば従う 366 | lang_from_and_text_froms: list[str] = text_from_wo_langs.split( 367 | " > " 368 | ) 369 | if len(lang_from_and_text_froms) >= 2: 370 | # 対応言語を、現在のサービス~最後のサービスから探す 371 | for i in range(index_service, len(services)): 372 | if ( 373 | lang_from_and_text_froms[0].casefold().strip() 374 | in self.__from_langs_all[services[i]] 375 | ): 376 | lang_from = ( 377 | lang_from_and_text_froms[0].casefold().strip() 378 | ) 379 | text_from_wo_langs = " > ".join( 380 | lang_from_and_text_froms[1:] 381 | ) 382 | index_service = i 383 | break 384 | # ない場合は、現在のサービスを維持する 385 | # 独自で日本語, 中国語(簡体字), 中国語(繁体字)であるか 386 | # それ以外の言語であるかを推定 387 | if lang_from is None: 388 | lang_from = TERLang.detect_cj( 389 | text_from_wo_langs, services[index_service] 390 | ) 391 | # googleTrans を利用して推定 392 | if lang_from is None and self.__translator_g_detection is not None: 393 | try: 394 | lang_from_g_detection: str = ( 395 | str( 396 | self.__translator_g_detection.detect( 397 | text_from_wo_langs 398 | ).lang 399 | ) 400 | .casefold() 401 | .strip() 402 | ) 403 | # 対応言語を、現在のサービス~最後のサービスから探す 404 | for i in range(index_service, len(services)): 405 | # googleTrans と現在のサービスとで 406 | # 言語名が違う場合は変換 407 | lang_from_converted: str = TERLang.convert_from( 408 | lang_from_g_detection, services[i] 409 | ) 410 | if ( 411 | lang_from_converted 412 | in self.__from_langs_all[services[i]] 413 | ): 414 | lang_from = lang_from_converted 415 | index_service = i 416 | break 417 | # ない場合は、現在のサービスを維持する 418 | except Exception as e: 419 | print( 420 | " Language detection of " 421 | + f'"{text_from_w_force_service_langs}" failed.' 422 | ) 423 | print(f" {e}") 424 | print("") 425 | # 426 | # (翻訳しない 4/6) メッセージが翻訳しない言語たちのいずれかである 427 | if ( 428 | lang_from in self.__from_langs_to_ignore 429 | and forces_translation is False 430 | ): 431 | return 432 | # 433 | # 翻訳先言語 434 | lang_to = None 435 | # メッセージ内に指定があれば従う 436 | text_from_and_lang_tos: list[str] = text_from_wo_langs.split(" > ") 437 | if len(text_from_and_lang_tos) >= 2: 438 | # 対応言語を、現在のサービス~最後のサービスから探す 439 | for i in range(index_service, len(services)): 440 | if ( 441 | text_from_and_lang_tos[-1].casefold().strip() 442 | in self.__to_langs_all[services[i]] 443 | ): 444 | lang_to = text_from_and_lang_tos[-1].casefold().strip() 445 | text_from_wo_langs = " > ".join( 446 | text_from_and_lang_tos[0:-1] 447 | ) 448 | index_service = i 449 | break 450 | # ない場合は、現在のサービスを維持する 451 | # 既定 452 | if lang_to is None: 453 | is_service_found_when_none: bool = False 454 | # 対応言語を、現在のサービス~最後のサービスから探す 455 | for i in range(index_service, len(services)): 456 | for to_lang_default in self.__to_langs_default: 457 | if to_lang_default in self.__to_langs_all[services[i]]: 458 | lang_to = to_lang_default 459 | index_service = i 460 | is_service_found_when_none = True 461 | break 462 | if is_service_found_when_none is True: 463 | break 464 | # ない場合は、現在のサービスを維持する 465 | # ( lang_to is None なので、以下で return ) 466 | # 推定した翻訳元と翻訳先が同じであった場合 467 | if lang_to is not None and lang_from == lang_to: 468 | lang_to = None 469 | is_service_found_when_same: bool = False 470 | # 対応言語を、現在のサービス~最後のサービスから探す 471 | for i in range(index_service, len(services)): 472 | for ( 473 | to_lang_if 474 | ) in self.__to_langs_if_from_lang_is_in_defaults: 475 | if to_lang_if in self.__to_langs_all[services[i]]: 476 | lang_to = to_lang_if 477 | index_service = i 478 | is_service_found_when_same = True 479 | break 480 | if is_service_found_when_same is True: 481 | break 482 | # ない場合は、現在のサービスを維持する 483 | # ( lang_to is None なので、以下で return ) 484 | # 485 | # (翻訳しない 5/6) 翻訳先の言語が決まっていない 486 | if lang_to is None: 487 | return 488 | # 489 | # 現在のサービスで非対応の言語ならば次のサービスへ 490 | is_translatable: bool = True 491 | if lang_from not in self.__from_langs_all[services[index_service]]: 492 | # DeepL Translate は lang_from が None の場合に非対応 493 | if ( 494 | services[index_service] is TERTransService.DEEPLTRANSLATE 495 | or lang_from is not None 496 | ): 497 | is_translatable = False 498 | if lang_to not in self.__to_langs_all[services[index_service]]: 499 | is_translatable = False 500 | if is_translatable is False: 501 | print( 502 | f" {services[index_service]} cannot translate " 503 | + f'"{text_from_w_force_service_langs}" ' 504 | + f"from {lang_from} to {lang_to}." 505 | ) 506 | print("") 507 | index_service += 1 508 | continue 509 | # 510 | # 翻訳処理 511 | try: 512 | # DeepL Python Library 513 | if services[index_service] is TERTransService.DEEPLKEY: 514 | assert self.__translator_d_trans is not None, ( 515 | f"{services[index_service]} is not available." 516 | ) 517 | if lang_from is not None: 518 | lang_from = lang_from.upper() 519 | lang_to = lang_to.upper() 520 | # 521 | r_or_rs: deepl.TextResult | list[deepl.TextResult] = ( 522 | self.__translator_d_trans.translate_text( 523 | text_from_wo_langs, 524 | source_lang=lang_from, 525 | target_lang=lang_to, 526 | split_sentences=deepl.SplitSentences.NO_NEWLINES, 527 | outline_detection=False, 528 | ) 529 | ) 530 | if type(r_or_rs) is deepl.TextResult: 531 | text_to = r_or_rs.text 532 | lang_from = ( 533 | r_or_rs.detected_source_lang.upper().strip() 534 | ) 535 | elif ( 536 | type(r_or_rs) is list 537 | and r_or_rs[0] is deepl.TextResult 538 | ): 539 | text_to = " ".join([r.text for r in r_or_rs]) 540 | lang_from = ( 541 | str(r_or_rs[0].detected_source_lang) 542 | .upper() 543 | .strip() 544 | ) 545 | else: 546 | assert False, ( 547 | f"{r_or_rs}([0]) is not deepl.TextResult." 548 | ) 549 | break 550 | # DeepL Translate 551 | elif services[index_service] is TERTransService.DEEPLTRANSLATE: 552 | assert lang_from is not None, ( 553 | f"{services[index_service]} is not available." 554 | ) 555 | lang_from = lang_from.upper() 556 | lang_to = lang_to.upper() 557 | text_to = str( 558 | deepltranslate.translate( 559 | lang_from, lang_to, text_from_wo_langs 560 | ) 561 | ) 562 | break 563 | # Googletrans 564 | elif services[index_service] is TERTransService.GOOGLETRANS: 565 | assert self.__translator_g_trans is not None, ( 566 | f"{services[index_service]} is not available." 567 | ) 568 | r: googletrans.models.Translated = ( 569 | self.__translator_g_trans.translate( 570 | text_from_wo_langs, 571 | dest=lang_to, 572 | src=lang_from if lang_from is not None else "auto", 573 | ) 574 | ) 575 | text_to = str(r.text) 576 | lang_from = str(r.src).casefold().strip() 577 | break 578 | # Google Apps Script (GAS) 579 | elif services[index_service] is TERTransService.GOOGLEGAS: 580 | assert self.__translator_g_session_url is not None, ( 581 | f"{services[index_service]} is not available." 582 | ) 583 | p: str = f"text={text_from_wo_langs}" 584 | if lang_from is not None: 585 | p += f"&source={lang_from}" 586 | p += f"&target={lang_to}" 587 | # 588 | async with self.__translator_g_session_url[0].get( 589 | f"{self.__translator_g_session_url[1]}?{p}" 590 | ) as res: 591 | res.raise_for_status() 592 | d: dict[str, Any] = json.loads(await res.text()) 593 | if int(d["code"]) == 200: 594 | text_to = str(d["text"]) 595 | if lang_from is None: 596 | lang_from = "??" 597 | else: 598 | raise ValueError( 599 | f"{res.status=}, {await res.text()=}" 600 | ) 601 | break 602 | else: 603 | assert False, ( 604 | f"Translation service is {services[index_service]}." 605 | ) 606 | except Exception as e: 607 | services_to_be_removed.append(services[index_service]) 608 | print( 609 | f' Translation of "{text_from_w_force_service_langs}" ' 610 | + f"by {services[index_service]} failed." 611 | ) 612 | print(f" {e}") 613 | print("") 614 | # 615 | index_service += 1 616 | # 617 | for service_to_be_removed in services_to_be_removed: 618 | self.__services.remove(service_to_be_removed) 619 | # 620 | # 621 | # (翻訳しない 6/6) 翻訳できなかった 622 | if text_to == "": 623 | return 624 | # 625 | # 626 | # 置換される文字列たちを定義 627 | replacements: dict[str, str] = { 628 | "{{senderUserName}}": sender_user_name, 629 | "{{senderDisplayName}}": sender_display_name, 630 | "{{toMessage}}": text_to, 631 | "{{fromLanguage}}": str(lang_from), 632 | "{{toLanguage}}": str(lang_to), 633 | # 634 | # ToDo: ★ (置換) 別の文字列置換にも対応する場合は、ここに実装する 635 | # '{{????}}': '????', 636 | } 637 | # 置換後の翻訳先メッセージ 638 | m: str = self.__messages_format 639 | for k, v in replacements.items(): 640 | m = m.replace(k, v) 641 | # 642 | # 643 | await message.channel.send(f"/me {m}") 644 | -------------------------------------------------------------------------------- /Codes/util.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import pathlib 5 | from typing import Any 6 | 7 | import chardet 8 | import json5 9 | 10 | 11 | # Classes 12 | class JSON5Reader: 13 | """JSON5ファイルを読み込むクラス(クラスメソッドのみ)""" 14 | 15 | @classmethod 16 | def open_and_load(cls, _file: pathlib.Path | str) -> dict[str, Any]: 17 | """ファイルをパースして内容を辞書形式で返す 18 | 19 | Parameters 20 | ---------- 21 | _file : pathlib.Path | str 22 | ファイルのパス 23 | 24 | Returns 25 | ------- 26 | dict[str, Any] 27 | ファイルの内容を辞書形式にしたもの 28 | """ 29 | d: dict[str, Any] = {} 30 | with open( 31 | _file, mode="r", encoding=TextFileEncodingEstimator.do(_file) 32 | ) as fp: 33 | d = json5.load(fp) 34 | return d 35 | 36 | 37 | class TextFileEncodingEstimator: 38 | """テキストファイルのエンコーディングを推定するクラス(クラスメソッドのみ)""" 39 | 40 | @classmethod 41 | def do(cls, _file: pathlib.Path | str) -> str | None: 42 | """テキストファイルのエンコーディングを推定する 43 | 44 | Parameters 45 | ---------- 46 | _file : pathlib.Path | str 47 | ファイルのパス 48 | 49 | Returns 50 | ------- 51 | str | None 52 | 推定したエンコーディングの名称 53 | """ 54 | enc: str | None = None 55 | with open(_file, "rb") as f_b: 56 | cd: dict[str, Any] | None = chardet.detect(f_b.read()) 57 | if cd is not None: 58 | enc = str(cd["encoding"]) 59 | return enc 60 | -------------------------------------------------------------------------------- /Codes/window.py: -------------------------------------------------------------------------------- 1 | # Import 2 | from __future__ import annotations 3 | 4 | import sys 5 | import tkinter as tk 6 | import tkinter.scrolledtext as tks 7 | from typing import AnyStr, Callable, ClassVar, Union 8 | 9 | 10 | # Classes 11 | class TkinterConsoleWindow: 12 | __root: ClassVar[Union[tk.Tk, None]] = None 13 | __text: ClassVar[Union[tks.ScrolledText, None]] = None 14 | # 15 | lets_thread_close_window: ClassVar[bool] = False 16 | __thread_close_window_intervale_ms: ClassVar[int] = 4000 17 | # 18 | __func_kill_bot: Callable[[], None] | None = None 19 | # 20 | __window_width: ClassVar[int] = 48 * 16 - 13 21 | __window_height: ClassVar[int] = 48 * 9 - 15 22 | __text_padding: ClassVar[int] = 8 23 | __color_background: ClassVar[str] = "#0c0c0c" 24 | __color_foreground: ClassVar[str] = "#e5e5e5" 25 | __color_selectbackground: ClassVar[str] = "#858585" 26 | __font: ClassVar[tuple[str, int]] = ("Cascadia Mono", 12) 27 | 28 | @classmethod 29 | def open(cls, _ver_no: str) -> None: 30 | root: tk.Tk = tk.Tk() 31 | root.title(f"Twitch EventSub Response Bot (v{_ver_no})") 32 | root.geometry(f"{cls.__window_width}x{cls.__window_height}") 33 | # 34 | text: tks.ScrolledText = tks.ScrolledText( 35 | root, 36 | width=(cls.__window_width - cls.__text_padding * 2), 37 | padx=cls.__text_padding, 38 | pady=cls.__text_padding, 39 | background=cls.__color_background, 40 | foreground=cls.__color_foreground, 41 | insertbackground=cls.__color_foreground, 42 | selectbackground=cls.__color_selectbackground, 43 | font=cls.__font, 44 | ) 45 | # 46 | menu_right_click: tk.Menu = tk.Menu( 47 | root, 48 | tearoff=0, 49 | ) 50 | menu_right_click.add_command( 51 | label="Copy Ctrl+C", 52 | command=lambda: [ 53 | text.clipboard_clear(), 54 | text.clipboard_append(text.get(tk.SEL_FIRST, tk.SEL_LAST)), 55 | ] 56 | if text.tag_ranges(tk.SEL) != () 57 | else [], 58 | ) 59 | menu_right_click.add_separator() 60 | menu_right_click.add_command( 61 | label="Select all Ctrl+A", 62 | command=lambda: [ 63 | text.tag_add(tk.SEL, "1.0", tk.END), 64 | ], 65 | ) 66 | text.bind( 67 | "", lambda e: menu_right_click.post(e.x_root, e.y_root) 68 | ) 69 | # 70 | text.configure(state=tk.DISABLED) 71 | text.pack() 72 | cls.__text = text 73 | # 74 | root.resizable(False, False) 75 | root.protocol("WM_DELETE_WINDOW", cls.__close_delete_window) 76 | root.after( 77 | cls.__thread_close_window_intervale_ms, cls.__close_or_not_after 78 | ) 79 | cls.__root = root 80 | cls.__root.mainloop() 81 | # 82 | del menu_right_click 83 | del cls.__text 84 | del text 85 | del cls.__root 86 | del root 87 | 88 | @classmethod 89 | def is_aleady_opened(cls) -> bool: 90 | return cls.__root is not None and cls.__text is not None 91 | 92 | @classmethod 93 | def add_func_kill_bot(cls, f: Callable[[], None]) -> None: 94 | cls.__func_kill_bot = f 95 | 96 | @classmethod 97 | def write(cls, s: AnyStr) -> int: 98 | if cls.__text is not None: 99 | cls.__text.see(tk.END) 100 | cls.__text.configure(state=tk.NORMAL) 101 | cls.__text.insert(tk.END, str(s)) 102 | cls.__text.configure(state=tk.DISABLED) 103 | cls.__text.see(tk.END) 104 | return len(s) 105 | return 0 106 | 107 | @classmethod 108 | def flush(cls) -> None: 109 | pass 110 | 111 | @classmethod 112 | def __close_delete_window(cls) -> None: 113 | if cls.__root is not None: 114 | sys.stdout = None 115 | sys.stderr = None 116 | cls.__root.destroy() 117 | if cls.__func_kill_bot is not None: 118 | cls.__func_kill_bot() 119 | 120 | @classmethod 121 | def __close_or_not_after(cls) -> None: 122 | if cls.__root is not None: 123 | if cls.lets_thread_close_window is True: 124 | cls.__root.destroy() 125 | else: 126 | cls.__root.after( 127 | cls.__thread_close_window_intervale_ms, 128 | cls.__close_or_not_after, 129 | ) 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | [Twitch EventSub Response Bot] 2 | ------------------------------------------------------------------------------- 3 | MIT License 4 | 5 | Copyright (c) 2023-present samuelladoco 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | ------------------------------------------------------------------------------- 25 | 26 | 27 | [Chardet] 28 | ------------------------------------------------------------------------------- 29 | GNU LESSER GENERAL PUBLIC LICENSE 30 | Version 2.1, February 1999 31 | 32 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 33 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 34 | Everyone is permitted to copy and distribute verbatim copies 35 | of this license document, but changing it is not allowed. 36 | 37 | [This is the first released version of the Lesser GPL. It also counts 38 | as the successor of the GNU Library Public License, version 2, hence 39 | the version number 2.1.] 40 | 41 | Preamble 42 | 43 | The licenses for most software are designed to take away your 44 | freedom to share and change it. By contrast, the GNU General Public 45 | Licenses are intended to guarantee your freedom to share and change 46 | free software--to make sure the software is free for all its users. 47 | 48 | This license, the Lesser General Public License, applies to some 49 | specially designated software packages--typically libraries--of the 50 | Free Software Foundation and other authors who decide to use it. You 51 | can use it too, but we suggest you first think carefully about whether 52 | this license or the ordinary General Public License is the better 53 | strategy to use in any particular case, based on the explanations below. 54 | 55 | When we speak of free software, we are referring to freedom of use, 56 | not price. Our General Public Licenses are designed to make sure that 57 | you have the freedom to distribute copies of free software (and charge 58 | for this service if you wish); that you receive source code or can get 59 | it if you want it; that you can change the software and use pieces of 60 | it in new free programs; and that you are informed that you can do 61 | these things. 62 | 63 | To protect your rights, we need to make restrictions that forbid 64 | distributors to deny you these rights or to ask you to surrender these 65 | rights. These restrictions translate to certain responsibilities for 66 | you if you distribute copies of the library or if you modify it. 67 | 68 | For example, if you distribute copies of the library, whether gratis 69 | or for a fee, you must give the recipients all the rights that we gave 70 | you. You must make sure that they, too, receive or can get the source 71 | code. If you link other code with the library, you must provide 72 | complete object files to the recipients, so that they can relink them 73 | with the library after making changes to the library and recompiling 74 | it. And you must show them these terms so they know their rights. 75 | 76 | We protect your rights with a two-step method: (1) we copyright the 77 | library, and (2) we offer you this license, which gives you legal 78 | permission to copy, distribute and/or modify the library. 79 | 80 | To protect each distributor, we want to make it very clear that 81 | there is no warranty for the free library. Also, if the library is 82 | modified by someone else and passed on, the recipients should know 83 | that what they have is not the original version, so that the original 84 | author's reputation will not be affected by problems that might be 85 | introduced by others. 86 | 87 | Finally, software patents pose a constant threat to the existence of 88 | any free program. We wish to make sure that a company cannot 89 | effectively restrict the users of a free program by obtaining a 90 | restrictive license from a patent holder. Therefore, we insist that 91 | any patent license obtained for a version of the library must be 92 | consistent with the full freedom of use specified in this license. 93 | 94 | Most GNU software, including some libraries, is covered by the 95 | ordinary GNU General Public License. This license, the GNU Lesser 96 | General Public License, applies to certain designated libraries, and 97 | is quite different from the ordinary General Public License. We use 98 | this license for certain libraries in order to permit linking those 99 | libraries into non-free programs. 100 | 101 | When a program is linked with a library, whether statically or using 102 | a shared library, the combination of the two is legally speaking a 103 | combined work, a derivative of the original library. The ordinary 104 | General Public License therefore permits such linking only if the 105 | entire combination fits its criteria of freedom. The Lesser General 106 | Public License permits more lax criteria for linking other code with 107 | the library. 108 | 109 | We call this license the "Lesser" General Public License because it 110 | does Less to protect the user's freedom than the ordinary General 111 | Public License. It also provides other free software developers Less 112 | of an advantage over competing non-free programs. These disadvantages 113 | are the reason we use the ordinary General Public License for many 114 | libraries. However, the Lesser license provides advantages in certain 115 | special circumstances. 116 | 117 | For example, on rare occasions, there may be a special need to 118 | encourage the widest possible use of a certain library, so that it becomes 119 | a de-facto standard. To achieve this, non-free programs must be 120 | allowed to use the library. A more frequent case is that a free 121 | library does the same job as widely used non-free libraries. In this 122 | case, there is little to gain by limiting the free library to free 123 | software only, so we use the Lesser General Public License. 124 | 125 | In other cases, permission to use a particular library in non-free 126 | programs enables a greater number of people to use a large body of 127 | free software. For example, permission to use the GNU C Library in 128 | non-free programs enables many more people to use the whole GNU 129 | operating system, as well as its variant, the GNU/Linux operating 130 | system. 131 | 132 | Although the Lesser General Public License is Less protective of the 133 | users' freedom, it does ensure that the user of a program that is 134 | linked with the Library has the freedom and the wherewithal to run 135 | that program using a modified version of the Library. 136 | 137 | The precise terms and conditions for copying, distribution and 138 | modification follow. Pay close attention to the difference between a 139 | "work based on the library" and a "work that uses the library". The 140 | former contains code derived from the library, whereas the latter must 141 | be combined with the library in order to run. 142 | 143 | GNU LESSER GENERAL PUBLIC LICENSE 144 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 145 | 146 | 0. This License Agreement applies to any software library or other 147 | program which contains a notice placed by the copyright holder or 148 | other authorized party saying it may be distributed under the terms of 149 | this Lesser General Public License (also called "this License"). 150 | Each licensee is addressed as "you". 151 | 152 | A "library" means a collection of software functions and/or data 153 | prepared so as to be conveniently linked with application programs 154 | (which use some of those functions and data) to form executables. 155 | 156 | The "Library", below, refers to any such software library or work 157 | which has been distributed under these terms. A "work based on the 158 | Library" means either the Library or any derivative work under 159 | copyright law: that is to say, a work containing the Library or a 160 | portion of it, either verbatim or with modifications and/or translated 161 | straightforwardly into another language. (Hereinafter, translation is 162 | included without limitation in the term "modification".) 163 | 164 | "Source code" for a work means the preferred form of the work for 165 | making modifications to it. For a library, complete source code means 166 | all the source code for all modules it contains, plus any associated 167 | interface definition files, plus the scripts used to control compilation 168 | and installation of the library. 169 | 170 | Activities other than copying, distribution and modification are not 171 | covered by this License; they are outside its scope. The act of 172 | running a program using the Library is not restricted, and output from 173 | such a program is covered only if its contents constitute a work based 174 | on the Library (independent of the use of the Library in a tool for 175 | writing it). Whether that is true depends on what the Library does 176 | and what the program that uses the Library does. 177 | 178 | 1. You may copy and distribute verbatim copies of the Library's 179 | complete source code as you receive it, in any medium, provided that 180 | you conspicuously and appropriately publish on each copy an 181 | appropriate copyright notice and disclaimer of warranty; keep intact 182 | all the notices that refer to this License and to the absence of any 183 | warranty; and distribute a copy of this License along with the 184 | Library. 185 | 186 | You may charge a fee for the physical act of transferring a copy, 187 | and you may at your option offer warranty protection in exchange for a 188 | fee. 189 | 190 | 2. You may modify your copy or copies of the Library or any portion 191 | of it, thus forming a work based on the Library, and copy and 192 | distribute such modifications or work under the terms of Section 1 193 | above, provided that you also meet all of these conditions: 194 | 195 | a) The modified work must itself be a software library. 196 | 197 | b) You must cause the files modified to carry prominent notices 198 | stating that you changed the files and the date of any change. 199 | 200 | c) You must cause the whole of the work to be licensed at no 201 | charge to all third parties under the terms of this License. 202 | 203 | d) If a facility in the modified Library refers to a function or a 204 | table of data to be supplied by an application program that uses 205 | the facility, other than as an argument passed when the facility 206 | is invoked, then you must make a good faith effort to ensure that, 207 | in the event an application does not supply such function or 208 | table, the facility still operates, and performs whatever part of 209 | its purpose remains meaningful. 210 | 211 | (For example, a function in a library to compute square roots has 212 | a purpose that is entirely well-defined independent of the 213 | application. Therefore, Subsection 2d requires that any 214 | application-supplied function or table used by this function must 215 | be optional: if the application does not supply it, the square 216 | root function must still compute square roots.) 217 | 218 | These requirements apply to the modified work as a whole. If 219 | identifiable sections of that work are not derived from the Library, 220 | and can be reasonably considered independent and separate works in 221 | themselves, then this License, and its terms, do not apply to those 222 | sections when you distribute them as separate works. But when you 223 | distribute the same sections as part of a whole which is a work based 224 | on the Library, the distribution of the whole must be on the terms of 225 | this License, whose permissions for other licensees extend to the 226 | entire whole, and thus to each and every part regardless of who wrote 227 | it. 228 | 229 | Thus, it is not the intent of this section to claim rights or contest 230 | your rights to work written entirely by you; rather, the intent is to 231 | exercise the right to control the distribution of derivative or 232 | collective works based on the Library. 233 | 234 | In addition, mere aggregation of another work not based on the Library 235 | with the Library (or with a work based on the Library) on a volume of 236 | a storage or distribution medium does not bring the other work under 237 | the scope of this License. 238 | 239 | 3. You may opt to apply the terms of the ordinary GNU General Public 240 | License instead of this License to a given copy of the Library. To do 241 | this, you must alter all the notices that refer to this License, so 242 | that they refer to the ordinary GNU General Public License, version 2, 243 | instead of to this License. (If a newer version than version 2 of the 244 | ordinary GNU General Public License has appeared, then you can specify 245 | that version instead if you wish.) Do not make any other change in 246 | these notices. 247 | 248 | Once this change is made in a given copy, it is irreversible for 249 | that copy, so the ordinary GNU General Public License applies to all 250 | subsequent copies and derivative works made from that copy. 251 | 252 | This option is useful when you wish to copy part of the code of 253 | the Library into a program that is not a library. 254 | 255 | 4. You may copy and distribute the Library (or a portion or 256 | derivative of it, under Section 2) in object code or executable form 257 | under the terms of Sections 1 and 2 above provided that you accompany 258 | it with the complete corresponding machine-readable source code, which 259 | must be distributed under the terms of Sections 1 and 2 above on a 260 | medium customarily used for software interchange. 261 | 262 | If distribution of object code is made by offering access to copy 263 | from a designated place, then offering equivalent access to copy the 264 | source code from the same place satisfies the requirement to 265 | distribute the source code, even though third parties are not 266 | compelled to copy the source along with the object code. 267 | 268 | 5. A program that contains no derivative of any portion of the 269 | Library, but is designed to work with the Library by being compiled or 270 | linked with it, is called a "work that uses the Library". Such a 271 | work, in isolation, is not a derivative work of the Library, and 272 | therefore falls outside the scope of this License. 273 | 274 | However, linking a "work that uses the Library" with the Library 275 | creates an executable that is a derivative of the Library (because it 276 | contains portions of the Library), rather than a "work that uses the 277 | library". The executable is therefore covered by this License. 278 | Section 6 states terms for distribution of such executables. 279 | 280 | When a "work that uses the Library" uses material from a header file 281 | that is part of the Library, the object code for the work may be a 282 | derivative work of the Library even though the source code is not. 283 | Whether this is true is especially significant if the work can be 284 | linked without the Library, or if the work is itself a library. The 285 | threshold for this to be true is not precisely defined by law. 286 | 287 | If such an object file uses only numerical parameters, data 288 | structure layouts and accessors, and small macros and small inline 289 | functions (ten lines or less in length), then the use of the object 290 | file is unrestricted, regardless of whether it is legally a derivative 291 | work. (Executables containing this object code plus portions of the 292 | Library will still fall under Section 6.) 293 | 294 | Otherwise, if the work is a derivative of the Library, you may 295 | distribute the object code for the work under the terms of Section 6. 296 | Any executables containing that work also fall under Section 6, 297 | whether or not they are linked directly with the Library itself. 298 | 299 | 6. As an exception to the Sections above, you may also combine or 300 | link a "work that uses the Library" with the Library to produce a 301 | work containing portions of the Library, and distribute that work 302 | under terms of your choice, provided that the terms permit 303 | modification of the work for the customer's own use and reverse 304 | engineering for debugging such modifications. 305 | 306 | You must give prominent notice with each copy of the work that the 307 | Library is used in it and that the Library and its use are covered by 308 | this License. You must supply a copy of this License. If the work 309 | during execution displays copyright notices, you must include the 310 | copyright notice for the Library among them, as well as a reference 311 | directing the user to the copy of this License. Also, you must do one 312 | of these things: 313 | 314 | a) Accompany the work with the complete corresponding 315 | machine-readable source code for the Library including whatever 316 | changes were used in the work (which must be distributed under 317 | Sections 1 and 2 above); and, if the work is an executable linked 318 | with the Library, with the complete machine-readable "work that 319 | uses the Library", as object code and/or source code, so that the 320 | user can modify the Library and then relink to produce a modified 321 | executable containing the modified Library. (It is understood 322 | that the user who changes the contents of definitions files in the 323 | Library will not necessarily be able to recompile the application 324 | to use the modified definitions.) 325 | 326 | b) Use a suitable shared library mechanism for linking with the 327 | Library. A suitable mechanism is one that (1) uses at run time a 328 | copy of the library already present on the user's computer system, 329 | rather than copying library functions into the executable, and (2) 330 | will operate properly with a modified version of the library, if 331 | the user installs one, as long as the modified version is 332 | interface-compatible with the version that the work was made with. 333 | 334 | c) Accompany the work with a written offer, valid for at 335 | least three years, to give the same user the materials 336 | specified in Subsection 6a, above, for a charge no more 337 | than the cost of performing this distribution. 338 | 339 | d) If distribution of the work is made by offering access to copy 340 | from a designated place, offer equivalent access to copy the above 341 | specified materials from the same place. 342 | 343 | e) Verify that the user has already received a copy of these 344 | materials or that you have already sent this user a copy. 345 | 346 | For an executable, the required form of the "work that uses the 347 | Library" must include any data and utility programs needed for 348 | reproducing the executable from it. However, as a special exception, 349 | the materials to be distributed need not include anything that is 350 | normally distributed (in either source or binary form) with the major 351 | components (compiler, kernel, and so on) of the operating system on 352 | which the executable runs, unless that component itself accompanies 353 | the executable. 354 | 355 | It may happen that this requirement contradicts the license 356 | restrictions of other proprietary libraries that do not normally 357 | accompany the operating system. Such a contradiction means you cannot 358 | use both them and the Library together in an executable that you 359 | distribute. 360 | 361 | 7. You may place library facilities that are a work based on the 362 | Library side-by-side in a single library together with other library 363 | facilities not covered by this License, and distribute such a combined 364 | library, provided that the separate distribution of the work based on 365 | the Library and of the other library facilities is otherwise 366 | permitted, and provided that you do these two things: 367 | 368 | a) Accompany the combined library with a copy of the same work 369 | based on the Library, uncombined with any other library 370 | facilities. This must be distributed under the terms of the 371 | Sections above. 372 | 373 | b) Give prominent notice with the combined library of the fact 374 | that part of it is a work based on the Library, and explaining 375 | where to find the accompanying uncombined form of the same work. 376 | 377 | 8. You may not copy, modify, sublicense, link with, or distribute 378 | the Library except as expressly provided under this License. Any 379 | attempt otherwise to copy, modify, sublicense, link with, or 380 | distribute the Library is void, and will automatically terminate your 381 | rights under this License. However, parties who have received copies, 382 | or rights, from you under this License will not have their licenses 383 | terminated so long as such parties remain in full compliance. 384 | 385 | 9. You are not required to accept this License, since you have not 386 | signed it. However, nothing else grants you permission to modify or 387 | distribute the Library or its derivative works. These actions are 388 | prohibited by law if you do not accept this License. Therefore, by 389 | modifying or distributing the Library (or any work based on the 390 | Library), you indicate your acceptance of this License to do so, and 391 | all its terms and conditions for copying, distributing or modifying 392 | the Library or works based on it. 393 | 394 | 10. Each time you redistribute the Library (or any work based on the 395 | Library), the recipient automatically receives a license from the 396 | original licensor to copy, distribute, link with or modify the Library 397 | subject to these terms and conditions. You may not impose any further 398 | restrictions on the recipients' exercise of the rights granted herein. 399 | You are not responsible for enforcing compliance by third parties with 400 | this License. 401 | 402 | 11. If, as a consequence of a court judgment or allegation of patent 403 | infringement or for any other reason (not limited to patent issues), 404 | conditions are imposed on you (whether by court order, agreement or 405 | otherwise) that contradict the conditions of this License, they do not 406 | excuse you from the conditions of this License. If you cannot 407 | distribute so as to satisfy simultaneously your obligations under this 408 | License and any other pertinent obligations, then as a consequence you 409 | may not distribute the Library at all. For example, if a patent 410 | license would not permit royalty-free redistribution of the Library by 411 | all those who receive copies directly or indirectly through you, then 412 | the only way you could satisfy both it and this License would be to 413 | refrain entirely from distribution of the Library. 414 | 415 | If any portion of this section is held invalid or unenforceable under any 416 | particular circumstance, the balance of the section is intended to apply, 417 | and the section as a whole is intended to apply in other circumstances. 418 | 419 | It is not the purpose of this section to induce you to infringe any 420 | patents or other property right claims or to contest validity of any 421 | such claims; this section has the sole purpose of protecting the 422 | integrity of the free software distribution system which is 423 | implemented by public license practices. Many people have made 424 | generous contributions to the wide range of software distributed 425 | through that system in reliance on consistent application of that 426 | system; it is up to the author/donor to decide if he or she is willing 427 | to distribute software through any other system and a licensee cannot 428 | impose that choice. 429 | 430 | This section is intended to make thoroughly clear what is believed to 431 | be a consequence of the rest of this License. 432 | 433 | 12. If the distribution and/or use of the Library is restricted in 434 | certain countries either by patents or by copyrighted interfaces, the 435 | original copyright holder who places the Library under this License may add 436 | an explicit geographical distribution limitation excluding those countries, 437 | so that distribution is permitted only in or among countries not thus 438 | excluded. In such case, this License incorporates the limitation as if 439 | written in the body of this License. 440 | 441 | 13. The Free Software Foundation may publish revised and/or new 442 | versions of the Lesser General Public License from time to time. 443 | Such new versions will be similar in spirit to the present version, 444 | but may differ in detail to address new problems or concerns. 445 | 446 | Each version is given a distinguishing version number. If the Library 447 | specifies a version number of this License which applies to it and 448 | "any later version", you have the option of following the terms and 449 | conditions either of that version or of any later version published by 450 | the Free Software Foundation. If the Library does not specify a 451 | license version number, you may choose any version ever published by 452 | the Free Software Foundation. 453 | 454 | 14. If you wish to incorporate parts of the Library into other free 455 | programs whose distribution conditions are incompatible with these, 456 | write to the author to ask for permission. For software which is 457 | copyrighted by the Free Software Foundation, write to the Free 458 | Software Foundation; we sometimes make exceptions for this. Our 459 | decision will be guided by the two goals of preserving the free status 460 | of all derivatives of our free software and of promoting the sharing 461 | and reuse of software generally. 462 | 463 | NO WARRANTY 464 | 465 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 466 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 467 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 468 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 469 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 470 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 471 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 472 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 473 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 474 | 475 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 476 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 477 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 478 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 479 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 480 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 481 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 482 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 483 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 484 | DAMAGES. 485 | 486 | END OF TERMS AND CONDITIONS 487 | 488 | How to Apply These Terms to Your New Libraries 489 | 490 | If you develop a new library, and you want it to be of the greatest 491 | possible use to the public, we recommend making it free software that 492 | everyone can redistribute and change. You can do so by permitting 493 | redistribution under these terms (or, alternatively, under the terms of the 494 | ordinary General Public License). 495 | 496 | To apply these terms, attach the following notices to the library. It is 497 | safest to attach them to the start of each source file to most effectively 498 | convey the exclusion of warranty; and each file should have at least the 499 | "copyright" line and a pointer to where the full notice is found. 500 | 501 | 502 | Copyright (C) 503 | 504 | This library is free software; you can redistribute it and/or 505 | modify it under the terms of the GNU Lesser General Public 506 | License as published by the Free Software Foundation; either 507 | version 2.1 of the License, or (at your option) any later version. 508 | 509 | This library is distributed in the hope that it will be useful, 510 | but WITHOUT ANY WARRANTY; without even the implied warranty of 511 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 512 | Lesser General Public License for more details. 513 | 514 | You should have received a copy of the GNU Lesser General Public 515 | License along with this library; if not, write to the Free Software 516 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 517 | 518 | Also add information on how to contact you by electronic and paper mail. 519 | 520 | You should also get your employer (if you work as a programmer) or your 521 | school, if any, to sign a "copyright disclaimer" for the library, if 522 | necessary. Here is a sample; alter the names: 523 | 524 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 525 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 526 | 527 | , 1 April 1990 528 | Ty Coon, President of Vice 529 | 530 | That's all there is to it! 531 | ------------------------------------------------------------------------------- 532 | 533 | 534 | [DeepL Python Library] 535 | ------------------------------------------------------------------------------- 536 | MIT License 537 | 538 | Copyright 2022 DeepL SE (https://www.deepl.com) 539 | 540 | Permission is hereby granted, free of charge, to any person obtaining a copy 541 | of this software and associated documentation files (the "Software"), to deal 542 | in the Software without restriction, including without limitation the rights 543 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 544 | copies of the Software, and to permit persons to whom the Software is 545 | furnished to do so, subject to the following conditions: 546 | 547 | The above copyright notice and this permission notice shall be included in all 548 | copies or substantial portions of the Software. 549 | 550 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 551 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 552 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 553 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 554 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 555 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 556 | SOFTWARE. 557 | ------------------------------------------------------------------------------- 558 | 559 | 560 | [DeepL Translate] 561 | ------------------------------------------------------------------------------- 562 | MIT License 563 | 564 | Copyright (c) 2020 Peter Stein 565 | 566 | Permission is hereby granted, free of charge, to any person obtaining a copy 567 | of this software and associated documentation files (the "Software"), to deal 568 | in the Software without restriction, including without limitation the rights 569 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 570 | copies of the Software, and to permit persons to whom the Software is 571 | furnished to do so, subject to the following conditions: 572 | 573 | The above copyright notice and this permission notice shall be included in all 574 | copies or substantial portions of the Software. 575 | 576 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 577 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 578 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 579 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 580 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 581 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 582 | SOFTWARE. 583 | ------------------------------------------------------------------------------- 584 | 585 | 586 | [Googletrans] 587 | ------------------------------------------------------------------------------- 588 | The MIT License (MIT) 589 | 590 | Copyright (c) 2015 SuHun Han 591 | 592 | Permission is hereby granted, free of charge, to any person obtaining a copy 593 | of this software and associated documentation files (the "Software"), to deal 594 | in the Software without restriction, including without limitation the rights 595 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 596 | copies of the Software, and to permit persons to whom the Software is 597 | furnished to do so, subject to the following conditions: 598 | 599 | The above copyright notice and this permission notice shall be included in all 600 | copies or substantial portions of the Software. 601 | 602 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 603 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 604 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 605 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 606 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 607 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 608 | SOFTWARE. 609 | ------------------------------------------------------------------------------- 610 | 611 | 612 | [pip] 613 | ------------------------------------------------------------------------------- 614 | Copyright (c) 2008-present The pip developers (see AUTHORS.txt file) 615 | 616 | Permission is hereby granted, free of charge, to any person obtaining 617 | a copy of this software and associated documentation files (the 618 | "Software"), to deal in the Software without restriction, including 619 | without limitation the rights to use, copy, modify, merge, publish, 620 | distribute, sublicense, and/or sell copies of the Software, and to 621 | permit persons to whom the Software is furnished to do so, subject to 622 | the following conditions: 623 | 624 | The above copyright notice and this permission notice shall be 625 | included in all copies or substantial portions of the Software. 626 | 627 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 628 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 629 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 630 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 631 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 632 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 633 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 634 | ------------------------------------------------------------------------------- 635 | 636 | 637 | [Python 3.11.2] 638 | ------------------------------------------------------------------------------- 639 | Copyright © 2001-2023 Python Software Foundation; All Rights Reserved 640 | ------------------------------------------------------------------------------- 641 | 642 | 643 | [pyjson5] 644 | ------------------------------------------------------------------------------- 645 | Apache License 646 | Version 2.0, January 2004 647 | http://www.apache.org/licenses/ 648 | 649 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 650 | 651 | 1. Definitions. 652 | 653 | "License" shall mean the terms and conditions for use, reproduction, 654 | and distribution as defined by Sections 1 through 9 of this document. 655 | 656 | "Licensor" shall mean the copyright owner or entity authorized by 657 | the copyright owner that is granting the License. 658 | 659 | "Legal Entity" shall mean the union of the acting entity and all 660 | other entities that control, are controlled by, or are under common 661 | control with that entity. For the purposes of this definition, 662 | "control" means (i) the power, direct or indirect, to cause the 663 | direction or management of such entity, whether by contract or 664 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 665 | outstanding shares, or (iii) beneficial ownership of such entity. 666 | 667 | "You" (or "Your") shall mean an individual or Legal Entity 668 | exercising permissions granted by this License. 669 | 670 | "Source" form shall mean the preferred form for making modifications, 671 | including but not limited to software source code, documentation 672 | source, and configuration files. 673 | 674 | "Object" form shall mean any form resulting from mechanical 675 | transformation or translation of a Source form, including but 676 | not limited to compiled object code, generated documentation, 677 | and conversions to other media types. 678 | 679 | "Work" shall mean the work of authorship, whether in Source or 680 | Object form, made available under the License, as indicated by a 681 | copyright notice that is included in or attached to the work 682 | (an example is provided in the Appendix below). 683 | 684 | "Derivative Works" shall mean any work, whether in Source or Object 685 | form, that is based on (or derived from) the Work and for which the 686 | editorial revisions, annotations, elaborations, or other modifications 687 | represent, as a whole, an original work of authorship. For the purposes 688 | of this License, Derivative Works shall not include works that remain 689 | separable from, or merely link (or bind by name) to the interfaces of, 690 | the Work and Derivative Works thereof. 691 | 692 | "Contribution" shall mean any work of authorship, including 693 | the original version of the Work and any modifications or additions 694 | to that Work or Derivative Works thereof, that is intentionally 695 | submitted to Licensor for inclusion in the Work by the copyright owner 696 | or by an individual or Legal Entity authorized to submit on behalf of 697 | the copyright owner. For the purposes of this definition, "submitted" 698 | means any form of electronic, verbal, or written communication sent 699 | to the Licensor or its representatives, including but not limited to 700 | communication on electronic mailing lists, source code control systems, 701 | and issue tracking systems that are managed by, or on behalf of, the 702 | Licensor for the purpose of discussing and improving the Work, but 703 | excluding communication that is conspicuously marked or otherwise 704 | designated in writing by the copyright owner as "Not a Contribution." 705 | 706 | "Contributor" shall mean Licensor and any individual or Legal Entity 707 | on behalf of whom a Contribution has been received by Licensor and 708 | subsequently incorporated within the Work. 709 | 710 | 2. Grant of Copyright License. Subject to the terms and conditions of 711 | this License, each Contributor hereby grants to You a perpetual, 712 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 713 | copyright license to reproduce, prepare Derivative Works of, 714 | publicly display, publicly perform, sublicense, and distribute the 715 | Work and such Derivative Works in Source or Object form. 716 | 717 | 3. Grant of Patent License. Subject to the terms and conditions of 718 | this License, each Contributor hereby grants to You a perpetual, 719 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 720 | (except as stated in this section) patent license to make, have made, 721 | use, offer to sell, sell, import, and otherwise transfer the Work, 722 | where such license applies only to those patent claims licensable 723 | by such Contributor that are necessarily infringed by their 724 | Contribution(s) alone or by combination of their Contribution(s) 725 | with the Work to which such Contribution(s) was submitted. If You 726 | institute patent litigation against any entity (including a 727 | cross-claim or counterclaim in a lawsuit) alleging that the Work 728 | or a Contribution incorporated within the Work constitutes direct 729 | or contributory patent infringement, then any patent licenses 730 | granted to You under this License for that Work shall terminate 731 | as of the date such litigation is filed. 732 | 733 | 4. Redistribution. You may reproduce and distribute copies of the 734 | Work or Derivative Works thereof in any medium, with or without 735 | modifications, and in Source or Object form, provided that You 736 | meet the following conditions: 737 | 738 | (a) You must give any other recipients of the Work or 739 | Derivative Works a copy of this License; and 740 | 741 | (b) You must cause any modified files to carry prominent notices 742 | stating that You changed the files; and 743 | 744 | (c) You must retain, in the Source form of any Derivative Works 745 | that You distribute, all copyright, patent, trademark, and 746 | attribution notices from the Source form of the Work, 747 | excluding those notices that do not pertain to any part of 748 | the Derivative Works; and 749 | 750 | (d) If the Work includes a "NOTICE" text file as part of its 751 | distribution, then any Derivative Works that You distribute must 752 | include a readable copy of the attribution notices contained 753 | within such NOTICE file, excluding those notices that do not 754 | pertain to any part of the Derivative Works, in at least one 755 | of the following places: within a NOTICE text file distributed 756 | as part of the Derivative Works; within the Source form or 757 | documentation, if provided along with the Derivative Works; or, 758 | within a display generated by the Derivative Works, if and 759 | wherever such third-party notices normally appear. The contents 760 | of the NOTICE file are for informational purposes only and 761 | do not modify the License. You may add Your own attribution 762 | notices within Derivative Works that You distribute, alongside 763 | or as an addendum to the NOTICE text from the Work, provided 764 | that such additional attribution notices cannot be construed 765 | as modifying the License. 766 | 767 | You may add Your own copyright statement to Your modifications and 768 | may provide additional or different license terms and conditions 769 | for use, reproduction, or distribution of Your modifications, or 770 | for any such Derivative Works as a whole, provided Your use, 771 | reproduction, and distribution of the Work otherwise complies with 772 | the conditions stated in this License. 773 | 774 | 5. Submission of Contributions. Unless You explicitly state otherwise, 775 | any Contribution intentionally submitted for inclusion in the Work 776 | by You to the Licensor shall be under the terms and conditions of 777 | this License, without any additional terms or conditions. 778 | Notwithstanding the above, nothing herein shall supersede or modify 779 | the terms of any separate license agreement you may have executed 780 | with Licensor regarding such Contributions. 781 | 782 | 6. Trademarks. This License does not grant permission to use the trade 783 | names, trademarks, service marks, or product names of the Licensor, 784 | except as required for reasonable and customary use in describing the 785 | origin of the Work and reproducing the content of the NOTICE file. 786 | 787 | 7. Disclaimer of Warranty. Unless required by applicable law or 788 | agreed to in writing, Licensor provides the Work (and each 789 | Contributor provides its Contributions) on an "AS IS" BASIS, 790 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 791 | implied, including, without limitation, any warranties or conditions 792 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 793 | PARTICULAR PURPOSE. You are solely responsible for determining the 794 | appropriateness of using or redistributing the Work and assume any 795 | risks associated with Your exercise of permissions under this License. 796 | 797 | 8. Limitation of Liability. In no event and under no legal theory, 798 | whether in tort (including negligence), contract, or otherwise, 799 | unless required by applicable law (such as deliberate and grossly 800 | negligent acts) or agreed to in writing, shall any Contributor be 801 | liable to You for damages, including any direct, indirect, special, 802 | incidental, or consequential damages of any character arising as a 803 | result of this License or out of the use or inability to use the 804 | Work (including but not limited to damages for loss of goodwill, 805 | work stoppage, computer failure or malfunction, or any and all 806 | other commercial damages or losses), even if such Contributor 807 | has been advised of the possibility of such damages. 808 | 809 | 9. Accepting Warranty or Additional Liability. While redistributing 810 | the Work or Derivative Works thereof, You may choose to offer, 811 | and charge a fee for, acceptance of support, warranty, indemnity, 812 | or other liability obligations and/or rights consistent with this 813 | License. However, in accepting such obligations, You may act only 814 | on Your own behalf and on Your sole responsibility, not on behalf 815 | of any other Contributor, and only if You agree to indemnify, 816 | defend, and hold each Contributor harmless for any liability 817 | incurred by, or claims asserted against, such Contributor by reason 818 | of your accepting any such warranty or additional liability. 819 | 820 | END OF TERMS AND CONDITIONS 821 | 822 | APPENDIX: How to apply the Apache License to your work. 823 | 824 | To apply the Apache License to your work, attach the following 825 | boilerplate notice, with the fields enclosed by brackets "{}" 826 | replaced with your own identifying information. (Don't include 827 | the brackets!) The text should be enclosed in the appropriate 828 | comment syntax for the file format. We also recommend that a 829 | file or class name and description of purpose be included on the 830 | same "printed page" as the copyright notice for easier 831 | identification within third-party archives. 832 | 833 | Copyright {yyyy} {name of copyright owner} 834 | 835 | Licensed under the Apache License, Version 2.0 (the "License"); 836 | you may not use this file except in compliance with the License. 837 | You may obtain a copy of the License at 838 | 839 | http://www.apache.org/licenses/LICENSE-2.0 840 | 841 | Unless required by applicable law or agreed to in writing, software 842 | distributed under the License is distributed on an "AS IS" BASIS, 843 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 844 | See the License for the specific language governing permissions and 845 | limitations under the License. 846 | ------------------------------------------------------------------------------- 847 | 848 | 849 | [setuptools] 850 | ------------------------------------------------------------------------------- 851 | Copyright Jason R. Coombs 852 | 853 | Permission is hereby granted, free of charge, to any person obtaining a copy 854 | of this software and associated documentation files (the "Software"), to 855 | deal in the Software without restriction, including without limitation the 856 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 857 | sell copies of the Software, and to permit persons to whom the Software is 858 | furnished to do so, subject to the following conditions: 859 | 860 | The above copyright notice and this permission notice shall be included in 861 | all copies or substantial portions of the Software. 862 | 863 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 864 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 865 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 866 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 867 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 868 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 869 | IN THE SOFTWARE. 870 | ------------------------------------------------------------------------------- 871 | 872 | 873 | [TwitchIO] 874 | ------------------------------------------------------------------------------- 875 | MIT License 876 | 877 | Copyright (c) 2019 Myst(MysterialPy) 878 | 879 | Permission is hereby granted, free of charge, to any person obtaining a copy 880 | of this software and associated documentation files (the "Software"), to deal 881 | in the Software without restriction, including without limitation the rights 882 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 883 | copies of the Software, and to permit persons to whom the Software is 884 | furnished to do so, subject to the following conditions: 885 | 886 | The above copyright notice and this permission notice shall be included in all 887 | copies or substantial portions of the Software. 888 | 889 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 890 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 891 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 892 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 893 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 894 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 895 | SOFTWARE. 896 | ------------------------------------------------------------------------------- 897 | 898 | 899 | [twitchTransFreeNext] 900 | ------------------------------------------------------------------------------- 901 | MIT License 902 | 903 | Copyright (c) 2021 Ryota NISHIMURA 904 | 905 | Permission is hereby granted, free of charge, to any person obtaining a copy 906 | of this software and associated documentation files (the "Software"), to deal 907 | in the Software without restriction, including without limitation the rights 908 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 909 | copies of the Software, and to permit persons to whom the Software is 910 | furnished to do so, subject to the following conditions: 911 | 912 | The above copyright notice and this permission notice shall be included in all 913 | copies or substantial portions of the Software. 914 | 915 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 916 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 917 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 918 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 919 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 920 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 921 | SOFTWARE. 922 | ------------------------------------------------------------------------------- 923 | -------------------------------------------------------------------------------- /PyInstaller/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !build-release.sh 4 | -------------------------------------------------------------------------------- /PyInstaller/build-release.sh: -------------------------------------------------------------------------------- 1 | echo Script starts. 2 | echo 3 | 4 | init=`cat ../Codes/ter/__init__.py` 5 | ver=${init:15:5} 6 | echo ---- ver=${ver} 7 | echo 8 | 9 | echo ---- pyinstaller 10 | source ../Venvs/myvenv/Scripts/activate 11 | pyinstaller --clean --onefile --noconsole --runtime-tmpdir=. --name twitch-eventsub-response-py ../Codes/main.py 12 | echo 13 | 14 | echo ---- copy 15 | mkdir ./twitch-eventsub-response-py-v${ver}/ 16 | cp --force ./dist/twitch-eventsub-response-py.exe ../Codes/config.json5 ../LICENSE ../README.pdf ./twitch-eventsub-response-py-v${ver} 17 | echo 18 | 19 | echo ---- zip 20 | powershell -c Compress-Archive -Path "./twitch-eventsub-response-py-v${ver}/*" -DestinationPath twitch-eventsub-response-py-v${ver}.zip 21 | echo 22 | 23 | echo ---- remove 24 | rm --recursive ./build ./dist/ twitch-eventsub-response-py.spec ./twitch-eventsub-response-py-v${ver}/ 25 | echo 26 | 27 | read -p "Press enter to finish." 28 | echo 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 最終更新日:2025-06-07 (v3.3.2) 2 | 3 | # Twitch EventSub Response Bot (twitch-eventsub-response-py) 4 | [Twitch](https://www.twitch.tv/) で配信中にレイドを受けたときに、それに応答して自動で「 `/shoutout レイド元のユーザー名` 」Twitch公式チャットコマンドの実行や、チャット欄に指定したメッセージを表示してくれる、ボットアプリです。 5 | 6 | 百聞は一見に如かず、 **[本ボットの動作例](https://clips.twitch.tv/TriangularSoftSheepUncleNox-lFYplIHDARc_DMZC) をご覧ください** 。 7 | 8 | v3.0.0から **チャットメッセージの [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) への受け渡し機能** も搭載しています。 **Twitch以外のサイトに同時配信していなければ** 、 [わんコメ](https://onecomme.com/) ・ [マルチコメントビューア](https://ryu-s.github.io/app/multicommentviewer) ・ [Tubeyomi](https://sites.google.com/site/suzuniwa/tools/tubeyomi) などを併用せずに本ボットと [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) だけで、チャットに送信されたメッセージを読み上げできます。設定方法は下記の [棒読みちゃん連携機能の設定](#棒読みちゃん連携機能の設定) を参照ください。 9 | 10 | v2.0から **チャットメッセージの翻訳機能** も搭載しています。 [チャット翻訳ちゃん](http://www.sayonari.com/trans_asr/trans.html) を併用せずに本ボットだけで、チャットに送信されたメッセージを翻訳できます。設定方法は下記の [チャット翻訳機能の設定](#チャット翻訳機能の設定) を参照ください。 11 | 12 | ※ これらの機能は、個別にオン・オフができます。 13 | 14 | 15 | 16 | 17 | ## 目次 18 | - [背景説明](#背景説明) 19 | - [本ボットのメイン機能](#本ボットのメイン機能) 20 | - [動作環境](#動作環境) 21 | - [ダウンロードとインストールの方法](#ダウンロードとインストールの方法) 22 | - [事前設定](#事前設定) 23 | - [ボットとして運用するユーザーにモデレーター権限を付与](#ボットとして運用するユーザーにモデレーター権限を付与) 24 | - [ボットとして運用するユーザーのユーザーアクセストークン文字列の取得](#ボットとして運用するユーザーのユーザーアクセストークン文字列の取得) 25 | - [トークン取得ウェブサービスが **トークン文字列を悪用しないと信じる** 場合](#トークン取得ウェブサービスが-トークン文字列を悪用しないと信じる-場合) 26 | - [トークン取得ウェブサービスがトークン文字列を悪用しないと信じない場合](#トークン取得ウェブサービスがトークン文字列を悪用しないと信じない場合) 27 | - [`config.json5` ファイルへの設定の記述](#configjson5-ファイルへの設定の記述) 28 | - [必須の設定](#必須の設定) 29 | - [イベントに自動で応答する機能の設定](#イベントに自動で応答する機能の設定) 30 | - [チャット翻訳機能の設定](#チャット翻訳機能の設定) 31 | - [棒読みちゃん連携機能の設定](#棒読みちゃん連携機能の設定) 32 | - [実行](#実行) 33 | - [起動](#起動) 34 | - [動作中であるかの確認](#動作中であるかの確認) 35 | - [停止](#停止) 36 | - [再起動](#再起動) 37 | - [異常中断](#異常中断) 38 | - [アンインストール方法](#アンインストール方法) 39 | - [今後の展開](#今後の展開) 40 | - [バージョン履歴](#バージョン履歴) 41 | - [参考資料](#参考資料) 42 | 43 | 44 | 45 | 46 | ## 背景説明 47 | Twitch配信のチャット欄に指定したメッセージを自動で表示してくれるボットサービスには [Nightbot](https://nightbot.tv/) などがあり、例えば「 `!` 」で始まる『 **ユーザーチャットコマンド(以下:ユーザーコマンド)** 』を定義し、ある程度条件を指定して実行させることができます。 [Streamlabs](https://streamlabs.com/) ないし [StreamElements](https://streamelements.com/) といったサービスと組み合わせれば、他配信者からのレイドや視聴者によるフォローなどのイベントが発生すると自動で応答してユーザーコマンドを実行させることもできます。 48 | 49 | しかし、少なくとも **[Nightbot](https://nightbot.tv/) に関しては、「 `/` 」 で始まる『Twitch公式チャットコマンド(以下:公式コマンド)』のうち、以下のものしか実行させることができないようです** 。 50 | 51 | | 公式コマンド | 実行内容 | 52 | | :--- | :-- | 53 | | `/me メッセージ` | 「 `メッセージ` 」をイタリックで表示させる(日本語は非対応) | 54 | | `/announce メッセージ` | 「 **お知らせ** (改行) `メッセージ` 」を表示させる | 55 | 56 | 詳しくは未調査ですが、 [Streamlabs](https://streamlabs.com/) や [StreamElements](https://streamelements.com/) も公式コマンドを実行させることができないと推測しています。 57 | 58 | 59 | 60 | 61 | ## 本ボットのメイン機能 62 | このような背景を踏まえて、 **イベントに自動で応答し、かつ、上記以外の公式コマンドを実行させることのできるボット** を開発しました。本稿更新時点でサポートしている機能は以下です。 63 | 64 | | 応答タイミング | コマンド | 実行内容 | 65 | | :--- | :-- | :-- | 66 | | レイドを受けたとき | `/shoutout レイド元のユーザー名` | レイド元のユーザーのチャンネルを応援し、フォローボタン付きでチャット内で紹介する | 67 | | レイドを受けたとき | `(任意のメッセージ)` | 「 `(任意のメッセージ)` 」 を表示させる( これを利用して、 **ユーザーコマンドも実行可能** ) | 68 | 69 | そのほか、以下の機能も備えています。 70 | - チャットメッセージの翻訳機能(v.2.0以降) 71 | - チャットメッセージの [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) への受け渡し機能(v3.0.0以降) 72 | 73 | なお、各機能は、メイン機能も含めて、個別にオン・オフが設定できます。 74 | 75 | 76 | 77 | 78 | ## 動作環境 79 | - .exeファイル版:たぶん、本稿更新時点でサポートされている Windows(64bit版) で動作 80 | - スクリプト版:たぶん、 Python 3.10 以降のPythonインタプリタで動作 81 | 82 | 83 | 84 | 85 | ## ダウンロードとインストールの方法 86 | - .exeファイル版:右上にある [Releases](https://github.com/samuelladoco/twitch-eventsub-response-py/releases) → 最新版の `twitch-eventsub-response-py-vX.Y.Z.zip` ファイルをダウンロードして展開 87 | - `X.Y.Z` の部分は数字 88 | - スクリプト版:右のReleasesからソースコードをダウンロードするなり本リポジトリーをクローンするなりし、必要な外部パッケージをインストールしたうえで、Pythonインタプリタを使って実行 89 | - 必要な外部パッケージは `./Venvs/requirements.txt` に記載 90 | - ただし [DeepL Translate](https://github.com/ptrstn/deepl-translate) は、ソースコードをダウンロードし、 `deepl` となっている全ての箇所を `deepltranslate` に変更したうえでインストールすることが必要 91 | - [DeepL Python Library](https://github.com/DeepLcom/deepl-python) とパッケージ名が競合するのを回避するため 92 | 93 | 94 | .exeファイル版は、ダウンロードするのに使用するブラウザーによってはウイルスの疑いありと判定され、ダウンロードが妨げられる可能性があります。その場合は、(もちろん本ボットはウイルスではないので)疑いを解除してダウンロードできるようにしてください。 95 | 96 | 97 | 98 | 99 | ## 事前設定 100 | 本ボットを起動する前にやるべきことは最大で3つあります。 101 | 102 | 103 | 104 | ### ボットとして運用するユーザーにモデレーター権限を付与 105 | ボットとして運用するユーザーを決めてください。 106 | 107 | - 配信で使っているユーザーをボットとしても運用する場合:権限を付与する必要はなし 108 | - すでにモデレーター以上の権限を持っているため 109 | 110 | - ボットとして運用するユーザーを別に用意する場合:そのユーザーにモデレーターの権限を与えること 111 | - **セキュリティーの観点から、こちらをお勧め** 112 | 113 | すでに [チャット翻訳ちゃん](http://www.sayonari.com/trans_asr/trans.html) などでユーザーをボットとして使用している場合は、同じユーザーを本ボットに使用しても、お互い正常に動作するようです。 114 | 115 | 116 | 117 | ### ボットとして運用するユーザーのユーザーアクセストークン文字列の取得 118 | 119 | 本ボットが正常に動作するには、 [チャット翻訳ちゃん](http://www.sayonari.com/trans_asr/trans.html) などと同様に、「ユーザーアクセストークン」文字列というものをTwitchから取得して使用しなければなりません。 **トークン文字列は、ユーザーによって、そして、本ボットを含むTwitch関係の外部アプリやサービスが何を行う権限を要求するかによって、異なるものになります** 。本ボットが要求する権限は以下のとおりです。 120 | 121 | | 公式コマンド・メッセージ | 実行に必要な権限名 | 権限の意味 | 122 | | :--- | :-- | :-- | 123 | | `/shoutout` | `moderator:manage:shoutouts` | `/shoutout` 公式コマンドを実行できる | 124 | | `/color` | `user:manage:chat_color` | チャット欄で表示されるボットユーザー名の色を設定できる | 125 | | `(任意のメッセージ)`
`/me` | `chat:edit` | チャット欄に投稿できる | 126 | | (全般) | `chat:read` | チャット欄に接続できる | 127 | 128 | さて、トークン文字列を取得するのに外部サービスを利用すると、そのサービスはトークン文字列を知り得て、要求して承認された権限を悪用できてしまう可能性もあるかもしれません。なので、ここから先は **セキュリティー意識に応じてトークン文字列の取得方法を選んでください** 。 129 | 130 | 131 | #### トークン取得ウェブサービスが **トークン文字列を悪用しないと信じる** 場合 132 | 「3-2-1 Twitch OAuth Token Generator」は、 トークン文字列の取得を仲介してくれるウェブサービスです。以下の手順でトークン文字列を取得してください。 133 | 134 | まず、ブラウザーを開いて、**ボットとして運用するユーザーでTwitchにログインした状態にしてください** 。 135 | - ボットとして運用するユーザーを別に用意する場合は、配信で使っているユーザーでいったんログアウトしてボットとして運用するユーザーでログインし直すか、ブラウザーのシークレットモードなどと呼ばれる機能を使ってボットとして運用するユーザーでログインしてください 136 | - Chrome:「シークレット ウィンドウ」 137 | - Edge:「InPrivate ウィンドウ」 138 | - Firefox:「新しいプライベートウィンドウ」 139 | 140 | そして、以下のURLをコピーし、ブラウザーにペーストして、URLにアクセスしてください。 141 | 142 | ``` 143 | https://id.twitch.tv/oauth2/authorize?client_id=m36dcrvwztjjkqejyikbrd1nlkpeuj&redirect_uri=https%3A%2F%2F321extensions.co.uk%2Ftwitchapps&response_type=token&scope=moderator:manage:shoutouts+user:manage:chat_color+chat:edit+chat:read 144 | ``` 145 | 146 | 「321 Oauth Generator wants to access your account」というトップメッセージとその下にTwitchアカウントが表示されたページが出現しますので、そのアカウントがボットとして運用するユーザーであることを確認したうえで、「Authorize」を押してください。すると次の画面に遷移するので、その画面の「Your OAuth Token」の右下の「Show」を押してください。「 **`oauth:9y0urb0tuser0authacceesst0ken9`** 」などという文字列が表示されます。このうち **`oauth:` より右の(おそらく30桁前後となる)文字列** (この例の場合、 `9y0urb0tuser0authacceesst0ken9` ) がトークン文字列になります。次に、隣にある「Copy」を押してください。 `oauth:` を含む文字列がペーストできるようになります。 147 | 148 | 149 | #### トークン取得ウェブサービスがトークン文字列を悪用しないと信じない場合 150 | 自前でトークン文字列を取得してください。 151 | - 例えば [Twitch APIに必要なOAuth認証のアクセストークンを取得しよう](https://qiita.com/pasta04/items/2ff86692d20891b65905) に書かれている方法 152 | - 「トークンを取得してみる」 153 | - 「1. The OAuth implicit code flow」 154 | 155 | どちらの場合でも、トークン文字列を取得できたら、ブラウザーは閉じてかまいませんが、その前にトークン文字列を一時的にどこかにコピペなどして忘れないようにしてください。 156 | 157 | 158 | 159 | ### `config.json5` ファイルへの設定の記述 160 | 161 | まず、 `config.json5` ファイルを、テキストエディタ(メモ帳など)で開いてください。 `config.json5` は、ダウンロード時点では以下の内容になっています。 162 | 163 | ``` 164 | // Twitch EventSub Response Bot - Config (v3.0.0--) 165 | { 166 | // ■ メッセージ送信先となるチャンネルに関する設定たち 167 | "messageChannel": { 168 | // チャンネル配信者のユーザー名(チャンネルURLの末尾) 169 | // (* 全て英小文字でも、英大文字と英小文字が混在していても、どちらでも可) 170 | "broadcasterUserName": "yourchannelname", 171 | }, 172 | // 173 | // ■ メッセージ送信を行うボットに関する設定たち 174 | "bot": { 175 | // ボットとして運用するユーザーのOAuthアクセストークン 176 | // (* 使う機能が要求する権限をボットとなるユーザーが持っていること) 177 | // (* 使う機能が要求する権限をトークンが持っていること) 178 | "oAuthAccessToken": "9y0urb0tuser0authacceesst0ken9", 179 | // 180 | // チャンネルで表示されるボットの名前の色 181 | // (* トークンが "user:manage:chat_color" 権限を持っていること) 182 | // "blue", "blue_violet", "cadet_blue", "chocolate", "coral", 183 | // "dodger_blue", "firebrick", "golden_rod", "green", "hot_pink", 184 | // "orange_red", "red", "sea_green", "spring_green", "yellow_green", 185 | // "#RRGGBB" (* Turboユーザーのみ設定可), "doNotChange" (* 色を変えない), 186 | "nameColor": "blue", 187 | }, 188 | // 189 | // ■ イベントたちに対する応答たちに関する設定 190 | "responses": { 191 | // レイド 192 | "/raid": [ 193 | // [ 194 | // 送信前の待機時間(秒), 公式コマンド・メッセージ (* ユーザーコマンドを含む), 195 | // (* 必要あれば追加情報1, 追加情報2, ...,) 196 | // ] の組たち 197 | // (* 上から順に1つずつ実行) 198 | // 199 | // コマンドやメッセージの中で置換される文字列たち 200 | // {{raidBroadcasterUserName}} -> レイド元のユーザー名(チャンネルURLの末尾) 201 | // 202 | // メッセージ (* ユーザーコマンドを含む) の例 203 | [ 5, "!raided {{raidBroadcasterUserName}}", ], 204 | // 205 | // 公式コマンドの例 206 | // /shoutout 207 | // (* ボットとなるユーザーが モデレーター 以上であること) 208 | // (* トークンが "moderator:manage:shoutouts" 権限を持っていること) 209 | [10, "/shoutout", ], 210 | // 211 | // (* 公式コマンド・メッセージを実行しない場合は、 212 | // 該当する [ ] の行を削除するか、行の頭に // を挿入(コメントアウト)) 213 | // [10, "Sample message", ], 214 | // 215 | // (* ほかのコマンドは、要望があれば追加対応するかも) 216 | ], 217 | // 218 | // (* ほかのイベントは、要望があれば追加対応するかも) 219 | }, 220 | // 221 | // ■ メッセージたちに対する翻訳に関する設定 222 | "translation": { 223 | // 使用する翻訳サービスたちと優先使用順位 224 | // (* 各メッセージについて、翻訳できるまで、上に設定したサービスから順に使用) 225 | "servicesWithKeyOrURL": [ 226 | // DeepL翻訳で、認証キーを使用しない場合 (* 不具合がなければ変更不要) 227 | // エラーが出て翻訳されない場合が多いため、コメントアウト中 228 | // ["deeplTranslate", "https://www2.deepl.com/jsonrpc", ], 229 | // 230 | // Google翻訳で、 Google アカウント にひも付いた設定を必要としない場合 231 | // "translate.google.????/" 232 | // (* いずれかの国のURLを設定するが、不具合がなければ変更不要) 233 | ["googleTrans", "translate.google.co.jp", ], 234 | // 235 | // (* サービスを使用しない場合は、 236 | // 該当する [ ] の行を削除するか、行の頭に // を挿入(コメントアウト)) 237 | // 238 | // DeepL翻訳で、認証キーを使用する場合 239 | // ["deeplKey", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx", ], 240 | // 241 | // Google翻訳で、 Google Apps Script (GAS) を使用する場合 242 | // "https://script.google.com/macros/s/????/exec" (* GASのURL) 243 | // ["googleGAS", "https://script.google.com/macros/s/????/exec", ], 244 | ], 245 | // 246 | // 翻訳元言語の判定に使用するサービス (* 不具合がなければ変更不要) 247 | "fromLanguageDetection": "translate.google.co.jp", 248 | // 249 | // 翻訳しないメッセージたち 250 | "messagesToIgnore": { 251 | // 送信ユーザー名たち 252 | // (* 英大文字と英小文字が混在していても可) 253 | // (* ボットとして運用するユーザーについては、ここに記載がない場合、 254 | // 手動で投稿したメッセージたちは翻訳を実行) 255 | "senderUserNames": ["nightbot", "", ], 256 | // 257 | // 翻訳元言語たち 258 | // (* ノルウェー語, 中国語は、 259 | // DeepL翻訳とGoogle翻訳とで略称が異なるため、 260 | // これらの言語を設定する場合は両方の略称を併記するのがよい) 261 | // DeepL翻訳(認証キー使用, 不使用), Google翻訳すべてで利用可能な言語たち 262 | // "BG" (Bulgarian), "CS" (Czech), "DA" (Danish), 263 | (中略) 264 | // "SV" (Swedish), 265 | // DeepL翻訳(認証キー使用), Google翻訳で利用可能な言語たち 266 | // "ID" (Indonesian), "KO" (Korean), "TR" (Turkish), 267 | // "UK" (Ukrainian), 268 | // DeepL翻訳(認証キー使用, 不使用)で利用可能な言語たち 269 | // "ZH" (Chinese) 270 | // (* Google翻訳では "zh-cn" または "zh-tw" ), 271 | // DeepL翻訳(認証キー使用)でのみ利用可能な言語たち 272 | // "NB" (Norwegian), 273 | // (* Google翻訳では "no" ) 274 | // Google翻訳でのみ利用可能な言語たち 275 | // "af" (afrikaans), "sq" (albanian), "am" (amharic), 276 | (中略) 277 | // "yi" (yiddish), "yo" (yoruba), "zu" (zulu), 278 | // (* ほかにもあるが、本ボットでは未対応) 279 | "fromLanguages": ["", ], 280 | // 281 | // ユーザーコマンドの接頭辞たち (* "_" は記載がなくても翻訳を不実行) 282 | "userCommandPrefixes": ["!", "", ], 283 | // 284 | // メッセージ内に含まれる文字列たち 285 | "stringsInMessage": ["http", "", ], 286 | }, 287 | // 288 | // 翻訳先言語(たち) 289 | // (* 英語, ノルウェー語, ポルトガル語, 中国語は、 290 | // DeepL翻訳(認証キー使用, 不使用)とGoogle翻訳とで略称が異なるため、 291 | // これらの言語を設定する場合は両方の略称を併記するのがよい) 292 | // DeepL翻訳(認証キー使用, 不使用), Google翻訳すべてで利用可能な言語たち 293 | // "BG" (Bulgarian), "CS" (Czech), "DA" (Danish), "DE" (German), 294 | (中略) 295 | // "SK" (Slovak), "SL" (Slovenian), "SV" (Swedish), 296 | // DeepL翻訳(認証キー使用), Google翻訳で利用可能な言語たち 297 | // "ID" (Indonesian), "KO" (Korean), "TR" (Turkish), 298 | // "UK" (Ukrainian), 299 | // DeepL翻訳(認証キー不使用), Google翻訳で利用可能な言語たち 300 | // "EN" (English), 301 | // (* DeepL翻訳(認証キー使用)では "EN-GB" または "EN-US" ) 302 | // "PT" (Portuguese), 303 | // (* DeepL翻訳(認証キー使用)では "PT-BR" または "PT-PT" ) 304 | // DeepL翻訳(認証キー使用, 不使用)で利用可能な言語たち 305 | // "ZH" (Chinese) 306 | // (* Google翻訳では "zh-cn" または "zh-tw" ), 307 | // DeepL翻訳(認証キー使用)でのみ利用可能な言語たち 308 | // "EN-GB" (English (British)), "EN-US" (English (American)), 309 | // (* DeepL翻訳(認証キー不使用), Google翻訳では "en" ) 310 | (中略) 311 | // "PT-BR" (Portuguese (Brazilian)), "PT-PT" (Portuguese (European)), 312 | // (* DeepL翻訳(認証キー不使用), Google翻訳では "pt") 313 | // Google翻訳でのみ利用可能な言語たち 314 | // "af" (afrikaans), "sq" (albanian), "am" (amharic), 315 | (中略) 316 | // "xh" (xhosa), "yi" (yiddish), "yo" (yoruba), "zu" (zulu), 317 | // (* ほかにもあるが、本ボットでは未対応) 318 | "toLanguages": { 319 | // 既定の翻訳先言語(たち) 320 | "defaults": ["JA", "", ], 321 | // 322 | // 翻訳元言語が既定の翻訳先言語であった場合の、代わりの翻訳先言語(たち) 323 | "onesIfFromLanguageIsInDefaults": ["EN-US", "EN", "", ], 324 | }, 325 | // 326 | // 翻訳先メッセージの構成 327 | // 328 | // 翻訳先メッセージの中で置換される文字列たち 329 | // {{senderUserName}} -> 送信ユーザー名(チャンネルURLの末尾) 330 | // {{senderDisplayName}} -> 送信ユーザーの表示名 331 | // {{toMessage}} -> 翻訳された送信ユーザーのメッセージ 332 | // {{fromLanguage}} -> 翻訳元言語 333 | // {{toLanguage}} -> 翻訳先言語 334 | "messagesFormat": "{{senderUserName}}: {{toMessage}} ({{fromLanguage}} > {{toLanguage}})", 335 | }, 336 | // 337 | // ■ メッセージたちの 棒読みちゃん への受け渡しに関する設定 338 | "bouyomiChan": { 339 | // メッセージたちを受け渡すか否か 340 | // true (受け渡す), false (受け渡さない) 341 | "sendsMessages": false, 342 | // 343 | // 本ボット起動時に、自動で 棒読みちゃん を起動させる場合の、 344 | // 棒読みちゃん の実行ファイルの絶対パス 345 | // (* 例: "C:\\Users\\youru\\Documents\\SoftwareWithoutInstaller\\BouyomiChan_0_1_11_0_Beta21\\BouyomiChan.exe" ) 346 | // (* 「 \ 」(フォルダー区切りの記号)は 「 \\ 」(同じ記号2つ)に変更すること) 347 | // (* "" とした場合や、間違ったパスを設定した場合は、自動で起動されない) 348 | // (* 自動で起動させない場合は、本ボット起動前に 棒読みちゃん を手動で起動させておくこと) 349 | // (* 自動で起動させた場合は、本ボットの停止と共に 棒読みちゃん も自動で停止) 350 | "autoRunKillPath": "C:\\Users\\youru\\Documents\\SoftwareWithoutInstaller\\BouyomiChan_0_1_11_0_Beta21\\BouyomiChan.exe", 351 | // 352 | // 使用するローカル(本ボットを動かすPC)HTTPサーバ (localhost) のポート番号 353 | // (* 棒読みちゃん での設定値 (49152~65535) に合わせること) 354 | "portNo": 60080, 355 | // 356 | // 受け渡すメッセージたちに対する制限 357 | "limitsWhenPassing": { 358 | // 送信ユーザーのユーザー名ないし表示名の、末尾の算用数字部分を省略するか否か 359 | // true (省略する), false (省略しない) 360 | "ignoresSenderNameSuffixNum": true, 361 | // 362 | // 送信ユーザーのユーザー名ないし表示名の、先頭からの文字数の上限 (* 0~25) 363 | "numSenderNameCharacters": 25, 364 | // 365 | // 先頭からのエモート(スタンプ)数の上限 (* 0~125) 366 | "numEmotes": 3, 367 | }, 368 | // 369 | // 受け渡さないメッセージたち 370 | "messagesToIgnore": { 371 | // 送信ユーザー名たち 372 | // (* 英大文字と英小文字が混在していても可) 373 | // (* ボットとして運用するユーザーについては、ここに記載がない場合、 374 | // 手動で投稿したメッセージたちは受け渡しを実行) 375 | "senderUserNames": ["nightbot", "", ], 376 | // 377 | // ユーザーコマンドの接頭辞たち (* "_" は記載がなくても受け渡しを不実行) 378 | "userCommandPrefixes": ["!", "", ], 379 | // 380 | // メッセージ内に含まれる文字列たち 381 | "stringsInMessage": ["", ], 382 | }, 383 | // 384 | // 受け渡すメッセージの構成 385 | // 386 | // 受け渡すメッセージの中で置換される文字列たち 387 | // {{senderUserName}} -> 送信ユーザー名(チャンネルURLの末尾) 388 | // {{senderDisplayName}} -> 送信ユーザーの表示名 389 | // {{senderMessage}} -> 送信ユーザーのメッセージ 390 | "messagesFormat": "{{senderDisplayName}} san: {{senderMessage}}", 391 | }, 392 | } 393 | ``` 394 | 395 | 396 | #### 必須の設定 397 | ` // ■ メッセージ送信先となるチャンネルに関する設定たち` および ` // ■ メッセージ送信を行うボットに関する設定たち` 以降にある以下の箇所について、必要な変更をして、上書き保存してください。 398 | 399 | | 箇所 | 変更すべき部分 | 何に変更するか | 400 | | :--- | :-- | :-- | 401 | | `"broadcasterUserName": "yourchannelname"` | `yourchannelname` | 配信を行うユーザー名(チャンネルURLの末尾) | 402 | | `"oAuthAccessToken": "9y0urb0tuser0authacceesst0ken9"` | `9y0urb0tuser0authacceesst0ken9` | 上記の [ボットとして運用するユーザーのユーザーアクセストークン文字列の取得](#ボットとして運用するユーザーのユーザーアクセストークン文字列の取得) で得たトークン文字列 | 403 | | `"nameColor": "blue"` | `blue` | 上の行の「名前の色」で候補として挙げられている、色を表す文字列たちから1つ | 404 | 405 | 406 | #### イベントに自動で応答する機能の設定 407 | ` // ■ イベントたちに対する応答たちに関する設定` 以降にある以下の箇所を、やりたいことに応じて変更して、上書き保存してください。 408 | 409 | | 箇所 | 変更すべき部分 | 何に変更するか | 410 | | :--- | :-- | :-- | 411 | | `[ 5, "!raided {{raidBroadcasterUserName}}", ],` | `5` | 順序が1つ前のコマンド・メッセージが実行されてから、表示したいメッセージが実行されるまで待機する時間(秒)(最初に実行されるコマンド・メッセージの場合は、レイドを受けてからの時間) | 412 | | | `!raided {{raidBroadcasterUserName}}` | 表示したいメッセージ(これを利用して、 **ユーザーコマンドも実行可能** ) | 413 | | | `[ 5, "!raided {{raidBroadcasterUserName}}", ],` | このメッセージを表示したくない場合は、行ごと削除するか、行の頭に `//` を挿入(コメントアウト) | 414 | | `[10, "/shoutout", ],` | `10` | 順序が1つ前のコマンド・メッセージが実行されてから、 `/shoutout` 公式コマンドが実行されるまで待機する時間(秒) | 415 | | | `[10, "/shoutout", ],` | `/shoutout` 公式コマンドを実行したくない場合は、行ごと削除するか、行の頭に `//` を挿入(コメントアウト) | 416 | 417 | 例として `[ 5, "!raided {{raidBroadcasterUserName}}", ],` および `[10, "/shoutout", ],` の行を全く変更しない場合、レイドを受けたときに本ボットは以下の動作をします。 418 | - まず、5秒待機したのち、チャット欄に「 `!raided レイド元のユーザー名(チャンネル名)` 」というメッセージを表示 419 | - もし [Twitchでレイドされたときに自動でお礼と宣伝をする方法](https://naosan-rta.hatenablog.com/entry/2022/02/27/113227) のとおりに [Nightbot](https://nightbot.tv/) に `!raided` ユーザーコマンド表示したいメッセージを設定していた場合、チャット欄に「 `【表示名】さんレイドありがとうございます!【表示名】さん(【ゲーム名】をプレイ中)のチャンネルはコチラ→【URL】` 」と表示 420 | - 本ボットと [Nightbot](https://nightbot.tv/) を設定すれば、 **[Streamlabs](https://streamlabs.com/) ないし [StreamElements](https://streamelements.com/) といったサービス側の設定は不要** 421 | - 次に、10秒待機したのち、 `/shoutout レイド元のユーザー名` 公式コマンドを実行 422 | 423 | コマンド・メッセージの実行順序を変えたい場合は、 `[ ]` で囲まれた行の上下を入れ替えてください。 424 | 425 | `config.json5` の文字コードは、ダウンロード時点では `UTF-8(BOMなし)` ですが、上書き保存した際にほかの文字コードに変わってしまっても、問題なく動作するように作ったつもりです。 426 | 427 | 428 | #### チャット翻訳機能の設定 429 | ` // ■ メッセージたちに対する翻訳に関する設定` 以降にある箇所を、やりたいことに応じて変更して、上書き保存してください。なお、初期設定のままでも、上記の [必須の設定](#必須の設定) を行っていれば、チャット翻訳機能は動作します。 430 | 431 | - 初期設定以外のチャット翻訳サービスを使用したい場合: 432 | - DeepL翻訳で、認証キーを使用する場合: [DeepL翻訳の無料版APIキーの登録発行手順!世界一のAI翻訳サービスをAPI利用](https://auto-worker.com/blog/?p=5030) などを参考にして、キーを取得して入力したのち、行の頭の `//` を削除(アンコメント) 433 | - Google翻訳で、 Google Apps Script (GAS) を使用する場合: [Google翻訳APIを無料で作る方法](https://qiita.com/satto_sann/items/be4177360a0bc3691fdf) などを参考にして、翻訳スクリプトを作成・デプロイし、ウェブアプリのURLを取得して入力したのち、行の頭の `//` を削除(アンコメント) 434 | - 翻訳サービスたちの優先使用順に応じて、`[ ]` で囲まれた行の上下を入れ替え 435 | - チャット翻訳をしたくない場合: `["deeplTranslate", "https://www2.deepl.com/jsonrpc", ]` および `["googleTrans", "translate.google.co.jp", ]` を行ごと削除するか、行の頭に `//` を挿入(コメントアウト) 436 | - `["deeplTranslate", "https://www2.deepl.com/jsonrpc", ]` は、 `429 Client Error: Too Many Requests for url: https://www2.deepl.com/jsonrpc` というエラーが出て翻訳されない場合が多いため、初期設定の時点で行の頭に `//` を挿入済み 437 | 438 | なお、各メッセージの前後に以下のような指定を付加することで、メッセージ単位で翻訳のされ方を細かく制御できます。 439 | - `trnslt ^ (メッセージ)` : `config.json5` の設定により翻訳しないルールに該当するメッセージであっても強制的に翻訳 440 | - `(翻訳サービス名) ~ (メッセージ)` :翻訳サービスを指定 441 | - 例: `deepltranslate ~ とりま` → `anyhow (JA > EN)` vs. `googletrans ~ とりま` → `Torima (ja > en)` 442 | - `(翻訳元言語) > (メッセージ)` :メッセージを何語と認識するかを指定 443 | - 例: `湯` → `hot water (JA > EN)` vs. `zh > 湯` → `スープ (ZH > JA)` 444 | - `(メッセージ) > (翻訳先言語)` :メッセージを何語に翻訳するかを指定 445 | - 例: `こんばんは` → `good evening (JA > EN)` vs. `こんばんは > fr` → `Bonne soirée (JA > FR)` 446 | 447 | 上記のオプションは、記述の順番を守れば、 `trnslt ^ (翻訳サービス名) ~ (翻訳元言語) > (メッセージ) > (翻訳先言語)` などと、組み合わせて使用もできます。 448 | 449 | 450 | #### 棒読みちゃん連携機能の設定 451 | ` // ■ メッセージたちの 棒読みちゃん への受け渡しに関する設定` 以降にある箇所を、やりたいことに応じて変更して、上書き保存してください。なお、初期設定のままでは、上記の [必須の設定](#必須の設定) を行っていても、 [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) によるメッセージたちの読み上げはなされません。読み上げてもらうには、少なくとも以下の設定を行ってください。 452 | - [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) の変更と確認 453 | - バージョンを `Ver0.1.11.0 Beta21` に更新 454 | - 例えば [【読み上げ】棒読みちゃん連携](https://onecomme.com/docs/feature/bouyomichan/) を参考に、基本設定の `01)ローカルHTTPサーバ機能を使う` の設定値を `True` に変更し、 `02)ポート番号` の設定値を記憶 455 | - エラーメッセージが表示されるようになった場合は、 `02)ポート番号` の初期設定値である `60080` を `49152` ~ `65535` の間の別の整数値に変更するか、以下を参考に問題を解消 456 | - [棒読みちゃんの起動失敗時にパソコンの再起動をせず対応する手順](https://yo2.site/index.php/2020/03/04/post-1663/) 457 | - [棒読みちゃんβ21でエラー「HTTPサーバを開始できませんでした(Port:50080)」が出ます。](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14268011994) 458 | - 本ボットの `config.json5` の設定値の変更 459 | - `"sendsMessages"` の設定値を `false` → `true` に変更 460 | - `"portNo"` の設定値を、上記の `02)ポート番号` の設定値に変更 461 | 462 | 463 | 464 | 465 | ## 実行 466 | さあ、本ボットを使いましょう! 467 | 468 | なお、本ボットの起動や動作中であるかの確認が失敗する場合で、原因が上記の [ボットとして運用するユーザーのユーザーアクセストークン文字列の取得](#ボットとして運用するユーザーのユーザーアクセストークン文字列の取得) で得たトークン文字列であると推測される場合は、 ボットとして運用するユーザーでTwitchにログインし、「設定(アカウント設定)」の「 [リンク](https://www.twitch.tv/settings/connections) 」の「その他のリンク」から、トークン文字列取得に使用したサービスをいったん「リンク解除」し、もう一度同じ方法でトークン文字列を取得すると、解決するかもしれません。 469 | - 「Twitch Chat OAuth Password Generator(Twitch Chat OAuth Token Generator)ウェブサービスが、トークン文字列を悪用しないと信じる場合」を選択したのであれば、リンク解除するサービス名は「Twitch Chat OAuth Token Generator」 470 | 471 | 472 | 473 | ### 起動 474 | - .exeファイル版: `twitch-eventsub-response-py.exe` を実行 475 | - スクリプト版: Pythonで `./Code/main.py` を実行 476 | - ウィンドウを立ち上げたくない場合は、 `main.py` の 変数 `uses_tkinter_window` の値を `True` → `False` に変更すること 477 | 478 | .exeファイル版は、Windowsやセキュリティーソフトによりウイルスの疑いありと判定され、初回の起動が妨げられる可能性があります。その場合は、(もちろん本ボットはウイルスではないので)疑いを解除して起動できるようにしてください。 479 | - .exeファイルはスクリプト版に [PyInstaller](https://pyinstaller.org/en/stable/) を適用して生成しているが、 [PyInstaller](https://pyinstaller.org/en/stable/) を使用して生成した.exeファイルにはよく起こる現象 480 | 481 | 本ボットはネット通信を行うアプリであるため、初回起動時にファイアーウォールソフトが通信をブロックしようとする可能性があります。その場合は、 **通信を許可してください** 。 482 | 483 | 正常に起動すると、配信のチャット欄に「 YourBotUserName *bot for \\_ has joined.* 」と表示されます。 484 | 485 | また、本ボットのコンソール模擬ウィンドウ(自作の黒い画面)に以下のようなメッセージが表示されます。 486 | 487 | ``` 488 | -------------------- Twitch EventSub Response Bot (v3.3.2) -------------------- 489 | [Preprocess] 490 | JSON5 file path = C:\Users\youru\Desktop\twitch-eventsub-response-py-v3.3.2\config.json5 491 | parsing this file ... done. 492 | 493 | [Activation of Bot] 494 | Initializing bot ... 495 | Message channel user name = yourchannelname 496 | Bot token length = 30 497 | done. 498 | 499 | [Run of Bot] 500 | Joining channel ... 501 | Channel name = yourchannelname 502 | done. 503 | 504 | Making bot ready ... 505 | Bot user ID = 888888888 506 | Bot user name = yourbotusername 507 | Bot commands 508 | _kill 509 | _restart 510 | _test 511 | Bot cogs 512 | TERRaidCog 513 | [5, '!raided {{raidBroadcasterUserName}}'] 514 | [10, '/shoutout'] 515 | TERBouyomiCog 516 | TERTransCog 517 | Services 518 | GOOGLETRANS 519 | Getting instance ... done. 520 | Getting language detection function ... done. 521 | Setting bot name color = blue ... done. 522 | done. 523 | 524 | ``` 525 | - `Running "C:\Users\youru\Documents\SoftwareWithoutInstaller\BouyomiChan_0_1_11_0_Beta21\BouyomiChan.exe" ... done.` は、本ボット起動時に自動で [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) も起動させる設定にしている場合に表示 526 | - `Services` は、対応する翻訳サービスを使用する設定にしている場合に表示 527 | 528 | 529 | 530 | ### 動作中であるかの確認 531 | 配信のチャット欄に「 `_test` 」と入力すると、「 YourBotUserName *bot for \\_ is alive.* 」と表示されます。 532 | 533 | また、本ボットのコンソール模擬ウィンドウ(自作の黒い画面)に以下のようなメッセージが表示されます。 534 | 535 | ``` 536 | Testing bot (v3.3.2) ... 537 | Channel name = yourchannelname 538 | Bot user ID = 888888888 539 | Bot user name = yourbotusername 540 | Bot commands 541 | _kill 542 | _restart 543 | _test 544 | Bot cogs 545 | TERRaidCog 546 | TERBouyomiCog 547 | TERTransCog 548 | done. 549 | 550 | ``` 551 | 552 | この2つが表示されれば、本ボットは動作中です。 553 | 554 | 555 | 556 | ### 停止 557 | - 直接停止させる方法 558 | - .exeファイル版:本ボットのコンソール模擬ウィンドウ(自作の黒い画面)を閉鎖 559 | - スクリプト版:実行中の `./Code/main.py` スクリプトを停止 560 | - 場合によっては、本ボットのコンソール模擬ウィンドウ(自作の黒い画面)を閉鎖する必要もあり 561 | - 配信のチャット欄から停止させる方法:チャット欄に「 `_kill` 」または「 `_kill (0から255の間の整数値(ただし、3以外))` 」と入力 562 | - チャンネルの配信者またはボットとして使用するユーザーのみが実行可能 563 | - チャット欄に「 YourBotUserName *bot for \\_ has stopped.* 」と表示 564 | - `(0から255の間の整数値(ただし、3以外))` を入力した場合、本アプリのリターンコードに設定 565 | - 入力しなかった場合、 リターンコードは `0` 566 | - `3` を入力した場合、本ボットが [再起動](#再起動)(下記) 567 | 568 | 例えば、配信のチャット欄に `_kill 222` と入力した場合は、本ボットのコンソール模擬ウィンドウ(自作の黒い画面)に以下のようなメッセージが表示されます。 569 | 570 | ``` 571 | Killing bot ... 572 | Return code = 222 573 | done. 574 | 575 | [Postprocess] 576 | Return code = 222 577 | 578 | ------------------------------------------------------------------------------- 579 | ``` 580 | - `Killing BouyomiChan ... done.` は、本ボット起動時に自動で [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) も起動させている場合に表示 581 | 582 | 583 | 584 | ### 再起動 585 | 配信のチャット欄に「 `_restart` 」または「 `_kill 3` 」と入力すると、配信のチャット欄に「 YourBotUserName *bot for \\_ has stopped.* 」と表示されたあと、本ボットが再起動します。 586 | - チャンネルの配信者またはボットとして使用するユーザーのみが実行可能 587 | 588 | また、本ボットのコンソール模擬ウィンドウ(自作の黒い画面)に以下のようなメッセージが表示されます。 589 | 590 | ``` 591 | Killing bot ... 592 | Return code = 3 (Restart) 593 | done. 594 | 595 | [Postprocess] 596 | Return code = 3 (Restart) 597 | Killing BouyomiChan ... done. 598 | 599 | ------------------------------------------------------------------------------- 600 | 601 | Restart after 4 s. 602 | 603 | ``` 604 | - `Killing BouyomiChan ... done.` は、本ボット起動時に自動で [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) も起動させている場合に表示 605 | 606 | 607 | 608 | ### 異常中断 609 | 何らかの異常により本ボットの動作が中断し、動作が再開されない場合は、以下の手順を実施したあと、本ボットを再度 [起動](#起動) してください。 610 | 1. 上記の [停止](#停止) に書かれている方法で本ボットを停止 611 | 1. 使用しているならば [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) も停止 612 | 1. `config.json5` の設定値の確認 613 | 1. 使用PCがインターネットにつながっているかの確認 614 | 615 | ただし、上記の1.の方法で本ボットを停止させても、一部の機能が停止されずに(バックグラウンドで)動き続けている場合があります。その場合は、お手数ですが、以下の対応を行ってください。 616 | - タスク マネージャー(アクティビティモニタ)などから、該当するものを強制終了 617 | - .exeファイル版は、.exeファイルと同じフォルダー内に `_MEIxxxxxx` ( `xxxxxx` の部分は数字)フォルダーが残されていることがあるので、その場合は削除 618 | - このフォルダーは、 **本ボットが起動していない状態であればいつでも削除可能** 619 | - 削除できなければ、本ボットの一部機能が未停止 620 | - どうしても一部の機能が動き続けている場合は、使用PCの再起動 621 | 622 | 623 | 624 | 625 | ## アンインストール方法 626 | `README.pdf` が格納されているフォルダーを削除してください。 627 | 628 | 629 | 630 | 631 | ## 今後の展開 632 | いつリリースできるかはお約束できませんが、以下のような機能を追加したいと考えています。 633 | - マイク音声を認識して文字起こしや翻訳を行い、OBS上で表示する機能 634 | - [ゆかりねっとコネクターNEOのOBS連携](https://nmori.github.io/yncneo-Docs/spec/) に相当 635 | - 要望に応じて、自動で反応するイベントの種類や反応の内容の拡充 636 | - 本ボットのアプリ名には「 [EventSub](https://dev.twitch.tv/docs/eventsub/) 」とついているが、EventSub購読が必要な機能は搭載の見込みなし 637 | - 各ユーザーないし誰かがHTTPS対応サーバーを用意しなければならないため 638 | 639 | 640 | 641 | 642 | ## バージョン履歴 643 | 2025-06-07 (v3.3.2) 644 | 645 | ※ **もし v3.3.0 や v3.3.1 で正常動作しているならばバージョンアップは不要** 646 | - 機能に影響のない変更 647 | - `README.pdf` の「トークン取得ウェブサービスが **トークン文字列を悪用しないと信じる** 場合」のトークン取得方法の記述を変更 648 | 649 | 2025-05-26 (v3.3.1) 650 | 651 | ※ **もし v3.3.0 で正常動作しているならばバージョンアップは不要** 652 | - 機能に影響のない変更 653 | - `config.json5` の初期設定値の変更 654 | - ` // ■ メッセージたちに対する翻訳に関する設定` 以降にある `["deeplTranslate", "https://www2.deepl.com/jsonrpc", ],` の行の頭に `//` を挿入(コメントアウト) 655 | - `429 Client Error: Too Many Requests for url: https://www2.deepl.com/jsonrpc` というエラーが出て翻訳されない場合が多いため 656 | - ` // ■ メッセージたちの 棒読みちゃん への受け渡しに関する設定` 以降にある `"portNo": 50080,` を `"portNo": 60080,` に変更 657 | - 開発環境の Windows 11 のバージョンを 23H2 から 24H2 にアップしたところ、 `50080` 番ポートが別の何かに使用されるようになったため 658 | - 使用しているPythonパッケージのアップデート 659 | - `googletrans (4.0.0rc1)` とそれが依存しているパッケージを除く 660 | - 今後の展開に関連したPythonパッケージの先行導入 661 | - プログラムコードのリファクタリング 662 | 663 | 664 | 2024-01-11 (v3.3.0) 665 | - `trnslt ^ (メッセージ)` で `config.json5` の設定により翻訳しないルールに該当するメッセージであっても強制的に翻訳できるオプションを追加 666 | - 翻訳サービスの指定方法を `(翻訳サービス名) = (メッセージ)` → `(翻訳サービス名) ~ (メッセージ)` に変更 667 | - コンソール模擬ウィンドウ(自作の黒い画面)に出力されるメッセージを変更 668 | 669 | 670 | 2023-04-17 (v3.2.2) 671 | - 棒読みちゃん の連携をしていない状態で、終了時にエラーがでるケースを修正 672 | 673 | 674 | 2023-04-17 (v3.2.1) 675 | - チャットメッセージ発生時の処理順を 棒読みちゃん → 翻訳 に(したつもり) 676 | - コンソール模擬ウィンドウ(自作の黒い画面)の起動待機間隔を 1秒 → 1/64秒 単位に 677 | - (Windowsの標準のタイマーの分解能は 1/64秒 間隔らしい) 678 | 679 | 680 | 2023-04-16 (v3.2.0) 681 | - コンソール模擬ウィンドウ(自作の黒い画面)の導入 682 | - .exeファイル版に `run.bat` を同こんしないように 683 | - 今後は `twitch-eventsub-response-py.exe` を直接実行 684 | - 正常起動すると配信のチャット欄に表示されるメッセージを「 YourBotUserName *bot for \\_ has joined and is ready.* 」 → 「 YourBotUserName *bot for \\_ has joined.* 」に変更 685 | - 停止時に 棒読みちゃん も自動で停止させるケースの拡充 686 | 687 | 688 | 2023-04-15 (v3.1.0) 689 | - 本ボットの停止時に 棒読みちゃん も自動で停止させるケースの拡充 690 | 691 | 692 | 2023-04-14 (v3.0.0) 693 | 694 | ※ **`config.json5` の再設定が必要** 695 | - チャットメッセージを 棒読みちゃん に受け渡す機能を追加 696 | - `config.json5` の書式変更 697 | - `bouyomiChan` キーと値の追加 698 | - 翻訳先メッセージへの追加文字列の設定を見直し、 `messagesFormat` キーと値の追加 699 | - その他軽微な変更 700 | - `/me (メッセージ)` 公式コマンド実行時に ACTION という文字列がメッセージに付加されて翻訳されるのを回避 701 | - `(翻訳サービス名) = (メッセージ)` で翻訳サービス名を指定できるオプションを追加 702 | 703 | 704 | 2023-03-28:v2.0 705 | 706 | ※ **`config.json5` の再設定が必要** 707 | - チャット翻訳機能を追加 708 | - `config.json5` の書式変更 709 | - `translation` キーと値の追加 710 | 711 | 712 | 2023-03-10:v1.0 713 | - .exeファイル版に `run.bat` を同こんし、こちらのほうを実行することを推奨するように変更 714 | - チャット欄で表示されるボットユーザー名の色を設定できなくなっていたのを修正 715 | - Twitch APIの仕様変更が原因と推測 716 | - `_restart` および `_kill 3` コマンドの追加 717 | - .exeファイル版で `run.bat` を実行して本ボットを起動していた場合、本ボットが再起動する機能 718 | - チャンネルの配信者またはボットとして使用するユーザーのみが実行可能 719 | - `/shoutout` 公式コマンドの実行に失敗した場合のリトライの回数制限を撤廃 720 | 721 | 2023-03-08:v0.5 722 | - .exeファイル版で `_MEIxxxxxx` ( `xxxxxx` の部分は数字)一時フォルダーが.exeファイルがあるフォルダーに生成されるように変更 723 | 724 | 2023-02-26:v0.4 725 | 726 | ※ **`config.json5` の再設定が必要** 727 | - `_kill` または `_kill (0から255の整数値)` コマンドの追加 728 | - 本ボットを停止させる機能 729 | - チャンネルの配信者またはボットとして使用するユーザーのみが使用可能 730 | - `(0から255の整数値)` を入力した場合、本アプリのリターンコードに設定 731 | - 入力しなかった場合、 リターンコードは `0` 732 | - `/shoutout` 公式コマンドの実行に失敗した場合に、2分5秒後にリトライする機能の追加 733 | - リトライは1度だけ実行 734 | - `config.json5` の書式変更 735 | - `userName` キーを `broadcasterUserName` キーに変更 736 | 737 | 2023-02-24:v0.3 738 | 739 | ※ **`config.json5` の再設定が必要** 740 | - `config.json5` の書式変更 741 | - `commands` と `messages` のキーと実行順の指定を廃止し、上から順に実行されるように簡素化 742 | - `raid` を `/raid` に、 `shoutout` を `/shoutout` にそれぞれ変更 743 | 744 | 2023-02-22:v0.2 745 | - ボットとして運用するユーザーとして、配信で使っているユーザー以外も指定可能に 746 | - 1分間の間に複数のレイドを受けた場合、ボットがエラー終了しないように 747 | 748 | 2023-02-21:v0.1 749 | 750 | 751 | 752 | 753 | ## 参考資料 754 | - ボット全般 755 | - [Twitch Developer Documentation](https://dev.twitch.tv/docs/) 756 | - TwitchIO([documentation](https://twitchio.dev/en/latest/)、[GitHub](https://github.com/TwitchIO/TwitchIO)) 757 | - [TwitchIOの実装例](https://github.com/Charahiro-tan/TwitchIO_example_ja) 758 | - [TwitchIOでTwitchのBotを作る](https://qiita.com/maguro869/items/57b866779b665058cfe8) 759 | - [Twitch APIに必要なOAuth認証のアクセストークンを取得しよう](https://qiita.com/pasta04/items/2ff86692d20891b65905) 760 | - 「トークンを取得してみる」 761 | - 「1. The OAuth implicit code flow」 762 | - イベントに対する自動応答機能 763 | - [Twitchでレイドされたときに自動でお礼と宣伝をする方法](https://naosan-rta.hatenablog.com/entry/2022/02/27/113227) 764 | - チャットメッセージの翻訳機能 765 | - [チャット翻訳ちゃん](http://www.sayonari.com/trans_asr/trans.html) 766 | - [DeepL翻訳の無料版APIキーの登録発行手順!世界一のAI翻訳サービスをAPI利用](https://auto-worker.com/blog/?p=5030) 767 | - [Google翻訳APIを無料で作る方法](https://qiita.com/satto_sann/items/be4177360a0bc3691fdf) 768 | - チャットメッセージの棒読みちゃんへの受け渡し機能 769 | - [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) 770 | - [【読み上げ】棒読みちゃん連携](https://onecomme.com/docs/feature/bouyomichan/) 771 | - [棒読みちゃんの起動失敗時にパソコンの再起動をせず対応する手順](https://yo2.site/index.php/2020/03/04/post-1663/) 772 | - [棒読みちゃんβ21でエラー「HTTPサーバを開始できませんでした(Port:50080)」が出ます。](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14268011994) 773 | -------------------------------------------------------------------------------- /README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelladoco/twitch-eventsub-response-py/29af590a01038126daa1ee6fbe79e79397082e27/README.pdf -------------------------------------------------------------------------------- /Venvs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !requirements.txt 4 | -------------------------------------------------------------------------------- /Venvs/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs==2.6.1 2 | aiohttp==3.12.0 3 | aiosignal==1.3.2 4 | altgraph==0.17.4 5 | anyio==4.9.0 6 | async-timeout==5.0.1 7 | attrs==25.3.0 8 | certifi==2025.4.26 9 | chardet==3.0.4 10 | charset-normalizer==3.4.2 11 | deepl==1.22.0 12 | # Download source from https://github.com/ptrstn/deepl-translate, 13 | # rename every "deepl" "deepltranslate" in it, and install it manually. 14 | # deepltranslate==1.2.0 15 | frozenlist==1.6.0 16 | googletrans==4.0.0rc1 17 | h11==0.9.0 18 | h2==3.2.0 19 | hpack==3.0.0 20 | hstspreload==2025.1.1 21 | httpcore==0.9.1 22 | httpx==0.13.3 23 | hyperframe==5.2.0 24 | idna==2.10 25 | iso8601==2.1.0 26 | json5==0.12.0 27 | multidict==6.4.4 28 | packaging==25.0 29 | pefile==2024.8.26 30 | propcache==0.3.1 31 | PyAudio==0.2.14 32 | pyinstaller==6.13.0 33 | pyinstaller-hooks-contrib==2025.4 34 | pywin32-ctypes==0.2.3 35 | regex==2024.11.6 36 | requests==2.32.3 37 | rfc3986==1.5.0 38 | sniffio==1.3.1 39 | SpeechRecognition==3.14.3 40 | twitchio==2.10.0 41 | types-regex==2024.11.6.20250403 42 | typing_extensions==4.13.2 43 | urllib3==2.4.0 44 | yarl==1.20.0 45 | --------------------------------------------------------------------------------