├── config ├── options.txt ├── banonjoin.txt ├── globalbans.txt └── userchanges.json ├── automod ├── lib │ ├── __init__.py │ └── event_emitter.py ├── version.py ├── __init__.py ├── exceptions.py ├── response.py ├── config.py ├── utils.py ├── register.py └── constants.py ├── run.py ├── requirements.txt ├── avatars └── no_avatar.jpg ├── start.bat ├── README.md ├── .gitignore ├── linux-installer.sh ├── linux-config.sh └── windows-installer.bat /config/options.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automod/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/banonjoin.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/globalbans.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/userchanges.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /automod/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.9' -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from automod import AutoMod 2 | 3 | AutoMod().run() 4 | -------------------------------------------------------------------------------- /automod/__init__.py: -------------------------------------------------------------------------------- 1 | from .bot import AutoMod 2 | 3 | __all__ = ['AutoMod'] 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | fuzzywuzzy 3 | aiofiles 4 | python-slugify 5 | discord.py==0.16.6 -------------------------------------------------------------------------------- /avatars/no_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattBSG/ModTools/HEAD/avatars/no_avatar.jpg -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python run.py 3 | echo. 4 | echo The process was either closed or was crashed 5 | echo. 6 | pause 7 | -------------------------------------------------------------------------------- /automod/exceptions.py: -------------------------------------------------------------------------------- 1 | class CommandError(Exception): 2 | def __init__(self, message): 3 | self.message = message 4 | super().__init__() 5 | 6 | 7 | class ExtractionError(Exception): 8 | def __init__(self, message): 9 | self.message = message 10 | super().__init__() 11 | -------------------------------------------------------------------------------- /automod/response.py: -------------------------------------------------------------------------------- 1 | class Response(object): 2 | def __init__(self, content, reply=False, delete_incoming=False, pm=False, trigger=False, ignore_flag=False): 3 | self.content = content 4 | self.reply = reply 5 | self.delete_incoming = delete_incoming 6 | self.pm = pm 7 | self.trigger = trigger 8 | self.ignore_flag = ignore_flag -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer maintained and no longer works (or soon won't) 2 | It has not been migrated to newer versions of discord.py and will not be able to log onto discord because the version of the gateway it uses no longer is available. It only serves as an archive now. 3 | 4 | This is a personal fork with light modification with fixes, additions, and changes for ModTools by [SexualRhinoceros](https://github.com/sexualrhinoceros); all rights reserved for content created by them. This repository is not associated with SexualRhinoceros or his communities, and no support will be given by them or me for use of this unofficial repository. 5 | 6 | [View my website](http://mattbsg.xyz) 7 | 8 | 9 | For installation and other information please [visit the wiki](https://github.com/MattBSG/ModTools/wiki) 10 | -------------------------------------------------------------------------------- /automod/lib/event_emitter.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import traceback 3 | 4 | 5 | class EventEmitter(object): 6 | def __init__(self): 7 | self._events = collections.defaultdict(list) 8 | 9 | def emit(self, event, *args, **kwargs): 10 | if event not in self._events: 11 | return 12 | 13 | for cb in self._events[event]: 14 | # noinspection PyBroadException 15 | try: 16 | cb(*args, **kwargs) 17 | 18 | except: 19 | traceback.print_exc() 20 | 21 | def on(self, event, cb): 22 | self._events[event].append(cb) 23 | return self 24 | 25 | def off(self, event, cb): 26 | self._events[event].remove(cb) 27 | 28 | if not self._events[event]: 29 | del self._events[event] 30 | 31 | return self 32 | -------------------------------------------------------------------------------- /automod/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | 4 | class Config(object): 5 | def __init__(self, config_file): 6 | config = configparser.ConfigParser() 7 | config.read(config_file) 8 | 9 | self.token = config.get('Credentials', 'Token', fallback=None) 10 | 11 | self.master_id = config.get('Permissions', 'OwnerID', fallback=None) 12 | self.command_prefix = config.get('Chat', 'CommandPrefix', fallback='!') 13 | 14 | # TODO: make this work right and not have a file hanging out in root directory 15 | self.globalbans_file = config.get('Files', 'GlobalBansFile', fallback='config/globalbans.txt') 16 | self.banonjoin_file = config.get('Files', 'BanOnJoinFile', fallback='config/banonjoin.txt') 17 | self.user_changes_file = config.get('Files', 'UserChangesFile', fallback='config/userchanges.json') 18 | 19 | # Validation logic for bot settings. 20 | 21 | if not self.master_id: 22 | raise ValueError("An owner is not specified in the configuration file") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # Configs 104 | .json 105 | .txt 106 | -------------------------------------------------------------------------------- /linux-installer.sh: -------------------------------------------------------------------------------- 1 | arch=$(uname -m | sed 's/x86_//;s/i[3-6]86/32/') 2 | # this is just a mess, leave me be 3 | if [ -f /etc/lsb-release ]; then 4 | . /etc/lsb-release 5 | os=$DISTRIB_ID 6 | ver=$DISTRIB_RELEASE 7 | 8 | if [ "$os" = "Ubuntu" ]; then 9 | if [ "$ver" = "12.04" ]; then 10 | installer=0 11 | levenshtein=0 12 | elif [ "$ver" = "14.04" ]; then 13 | installer=0 14 | levenshtein=0 15 | elif [ "$ver" = "16.04" ]; then 16 | installer=0 17 | levenshtein=1 18 | elif [ "$ver" = "16.10" ]; then 19 | installer=0 20 | levenshtein=1 21 | else 22 | installer=1 23 | fi 24 | else 25 | installer=1 26 | fi 27 | 28 | elif [ -f /etc/debian_version ]; then 29 | os=Debian 30 | ver=$(cat /etc/debian_version) 31 | installer=0 32 | else 33 | os=$(uname -s) 34 | ver=$(uname -r) 35 | fi 36 | 37 | if [ "$installer" = 1 ]; then 38 | echo -e "This system is running an unsupported OS. Details: \nOS: $os \nVersion: $ver" 39 | echo 40 | printf "\e[1;31mThe installer will not continue. Please reference the wiki for a list of supported operating systems. Exiting...\e[0m\n" 41 | exit 1 42 | fi 43 | 44 | clear 45 | 46 | if [ "$levenshtein" = 1 ]; then 47 | printf "\e[1;31mThis OS is not supported in installing python-Levenshtein.\e[0m This will not affect the bot.\n" 48 | fi 49 | 50 | 51 | if [ "$os" = "Ubuntu" ]; then 52 | 53 | # Adding repos that have dependancies that we need to download 54 | sudo add-apt-repository ppa:fkrull/deadsnakes -y 55 | sudo add-apt-repository ppa:mc3man/trusty-media -y 56 | apt-get update 57 | sudo apt-get upgrade -y 58 | 59 | echo 60 | echo Installing python and its modules.... 61 | sleep 1 62 | 63 | # Working out of the users current directory 64 | mkdir installation-files 65 | cd installation-files 66 | sudo apt-get install git python3.5 python3.5-dev unzip zlib1g-dev libjpeg8-dev -y 67 | wget https://bootstrap.pypa.io/get-pip.py 68 | sudo python3.5 get-pip.py 69 | sudo pip install -U -r requirements.txt 70 | if [ "$levenshtein" = 0 ]; then 71 | sudo pip install python-Levenshtein 72 | fi 73 | cd .. 74 | 75 | elif [ "$os" = "Debian" ]; then 76 | apt-get update 77 | sudo apt-get install build-essential libncursesw5-dev libgdbm-dev libc6-dev zlib1g-dev libsqlite3-dev tk-dev libssl-dev openssl unzip dialog -y 78 | mkdir installation-files 79 | cd installation-files 80 | wget https://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz 81 | tar -xvf Python-3.5.2.tgz 82 | cd Python-3.5.2 83 | sudo ./configure 84 | sudo make 85 | sudo make altinstall 86 | cd .. 87 | cd .. 88 | pip3.5 install --upgrade pip 89 | pip3.5 install -U -r requirements.txt 90 | pip3.5 install python-Levenshtein 91 | fi 92 | 93 | # cleanup; too tired to think of better solution 94 | rm -rfv installation-files 95 | 96 | echo 97 | echo Installation completed, starting configuration.... 98 | sleep 3 99 | 100 | # Hand off to config shell file 101 | 102 | clear 103 | chmod +x linux-config.sh 104 | ./linux-config.sh 105 | # This file will delete it-self as it is unnessecary to keep the file after it is used 106 | rm linux-installer.sh 107 | exit 0 108 | 109 | -------------------------------------------------------------------------------- /automod/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import asyncio 4 | import aiofiles 5 | 6 | from fuzzywuzzy import fuzz 7 | from slugify import slugify 8 | from datetime import datetime 9 | from .constants import DISCORD_EPOCH 10 | 11 | 12 | def load_json(filename): 13 | try: 14 | with open(filename, encoding='utf-8') as f: 15 | data = json.loads(f.read(), strict=False) 16 | return data 17 | 18 | except IOError as e: 19 | print("Error loading", filename, e) 20 | return [] 21 | 22 | async def load_json_async(filename): 23 | try: 24 | with open(filename, encoding='utf-8') as f: 25 | return json.loads(f.read(), strict=False) 26 | except IOError as e: 27 | print("Error loading", filename, e) 28 | return [] 29 | 30 | 31 | def load_file(filename): 32 | try: 33 | with open(filename) as f: 34 | results = [] 35 | for line in f: 36 | line = line.strip() 37 | if line: 38 | results.append(line) 39 | 40 | return results 41 | 42 | except IOError as e: 43 | print("Error loading", filename, e) 44 | return [] 45 | 46 | 47 | def write_json_norm(filename, contents): 48 | with open(filename, 'w') as outfile: 49 | outfile.write(json.dumps(contents, indent=2)) 50 | 51 | 52 | async def write_json(filename, contents): 53 | async with aiofiles.open(filename, mode='w') as outfile: 54 | await outfile.write(json.dumps(contents, indent=2).encode('ascii', 'ignore').decode('ascii')) 55 | 56 | 57 | def strict_compare_strings(string_one, string_two): 58 | highest_ratio = 0 59 | if fuzz.ratio(string_one, string_two) > highest_ratio: 60 | highest_ratio = fuzz.ratio(string_one, string_two) 61 | if fuzz.partial_ratio(string_one, string_two) > highest_ratio: 62 | highest_ratio = fuzz.partial_ratio(string_one, string_two) 63 | if fuzz.token_sort_ratio(string_one, string_two) > highest_ratio: 64 | highest_ratio = fuzz.token_sort_ratio(string_one, string_two) 65 | if fuzz.token_set_ratio(string_one, string_two) > highest_ratio: 66 | highest_ratio = fuzz.token_set_ratio(string_one, string_two) 67 | return highest_ratio 68 | 69 | 70 | def do_slugify(string): 71 | replacements = (('4', 'a'), ('@', 'a'), ('3', 'e'), ('1', 'l'), ('0', 'o'), ('7', 't'), ('5', 's')) 72 | for old, new in replacements: 73 | string = string.replace(old, new) 74 | string = slugify(string, separator='_') 75 | string = string.replace('_', '') 76 | return string 77 | 78 | def clean_string(string): 79 | string = re.sub('@', '@\u200b', string) 80 | string = re.sub('#', '#\u200b', string) 81 | string = re.sub('`', '', string) 82 | return string 83 | 84 | def compare_strings(string_one, string_two): 85 | highest_ratio = 0 86 | if fuzz.ratio(string_one, string_two)>highest_ratio: 87 | highest_ratio = fuzz.ratio(string_one, string_two) 88 | if fuzz.token_sort_ratio(string_one, string_two)>highest_ratio: 89 | highest_ratio = fuzz.token_sort_ratio(string_one, string_two) 90 | if fuzz.token_set_ratio(string_one, string_two)>highest_ratio: 91 | highest_ratio = fuzz.token_set_ratio(string_one, string_two) 92 | return highest_ratio 93 | 94 | 95 | def snowflake_time(user_id): 96 | return datetime.utcfromtimestamp(((int(user_id) >> 22) + DISCORD_EPOCH) / 1000) 97 | -------------------------------------------------------------------------------- /linux-config.sh: -------------------------------------------------------------------------------- 1 | # Fixes bug with PUTTY relating to line drawing charcters used by dialog. 2 | # https://www.novell.com/support/kb/doc.php?id=7015165 3 | # https://stackoverflow.com/a/37847838 4 | export NCURSES_NO_UTF8_ACS=1 5 | 6 | dialog --keep-tite --no-mouse --yesno "This file is used to configure AutoMod and the related settings. Do not run this if you have previously configured the options! Would you like to start configuration?" 8 64 7 | 8 | # 1 - Cancel pressed. 255 - ESC pressed. 9 | response=$? 10 | case $response in 11 | 1) exit;; 12 | 255) exit;; 13 | esac 14 | 15 | # Create temp file 16 | OUTPUT="/tmp/input.txt" 17 | >$OUTPUT 18 | 19 | dialog --keep-tite --no-mouse --inputbox "Ok, our dependancies should have been installed. Lets continue to configuration. Well confirm your options later on.\n\nGo to https://github.com/MattBSG/ModTools/wiki/Configuration\n\nFollow the instructions to create an application and bot account. Then enter the account's CLIENT ID in the following prompt. Make sure it is the CORRECT user id or ModTools will give the wrong invite link!\n\nEnter the userid of your bot account:" 32 64 2>$OUTPUT 20 | 21 | response=$? 22 | case $response in 23 | 1) exit;; 24 | 255) exit;; 25 | esac 26 | botid=$(<$OUTPUT) 27 | 28 | dialog --keep-tite --no-mouse --inputbox "On the same page as you got your userid you should see a field that says 'Token'.\n\nClick the 'Click to reveal' button, then copy and paste the ENTIRE string at the next prompt.\n\nEnter the token of your bot account:" 32 64 2>$OUTPUT 29 | 30 | response=$? 31 | case $response in 32 | 1) exit;; 33 | 255) exit;; 34 | esac 35 | bottoken=$(<$OUTPUT) 36 | 37 | dialog --keep-tite --no-mouse --inputbox "Now we need to get your userid.\n\nFind it by mentioning your self with a backslash before.\n\nFor example: \@DamFam#1234 will read something like <@66516516512568135>\n\nYou would enter '66516516512568135' without quotes. This will be set as the bot's ownerid.\n\nPlease enter YOUR userid:" 32 64 2>$OUTPUT 38 | 39 | response=$? 40 | case $response in 41 | 1) exit;; 42 | 255) exit;; 43 | esac 44 | ownerid=$(<$OUTPUT) 45 | 46 | dialog --keep-tite --no-mouse --inputbox "Last but not least, we need to know what you want to use for the command prefix.\n\nFor example: In !!help, !! is the command prefix.\n\nPlease enter what you would like for your command prefix (!! is the default):" 32 64 2>$OUTPUT 47 | 48 | response=$? 49 | case $response in 50 | 1) exit;; 51 | 255) exit;; 52 | esac 53 | prefix=$(<$OUTPUT) 54 | 55 | echo " 56 | BOT_USER_ACCOUNT = $botid" | cat - >> automod/constants.py 57 | echo "[Credentials] 58 | Token = $bottoken 59 | 60 | [Permissions] 61 | OwnerID = $ownerid 62 | 63 | [Chat] 64 | CommandPrefix = $prefix" | cat - >> config/options.txt 65 | 66 | dialog --no-mouse --keep-tite --msgbox "Awesome! You finished configuration of the bot. Lets verify that all the information you entered is correct.\n\nHere are the options you set (inside quotation marks):\n\nBot User ID = '$botid'\nBot Token = '$bottoken'\nYour ID = '$ownerid'\nCommand Prefix = '$prefix'\n\nConfiguration complete! If there is any incorrect information, you will need to manually edit it in either automod/constants.py or config/configs.txt\n\nYou can start your bot by running 'python3.5 run.py'\n\nFor more information refer to the wiki." 32 64 67 | 68 | # This file will delete it-self since if it is re-run then it will entirely break the config and need to be repaired 69 | rm linux-config.sh 70 | exit 0 71 | -------------------------------------------------------------------------------- /windows-installer.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :init 4 | setlocal DisableDelayedExpansion 5 | set cmdInvoke=1 6 | set winSysFolder=System32 7 | set "batchPath=%~0" 8 | for %%k in (%0) do set batchName=%%~nk 9 | set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs" 10 | setlocal EnableDelayedExpansion 11 | 12 | :checkPrivileges 13 | NET FILE 1>NUL 2>NUL 14 | if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) 15 | 16 | :getPrivileges 17 | if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges) 18 | 19 | ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" 20 | ECHO args = "ELEV " >> "%vbsGetPrivileges%" 21 | ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" 22 | ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" 23 | ECHO Next >> "%vbsGetPrivileges%" 24 | 25 | if '%cmdInvoke%'=='1' goto InvokeCmd 26 | 27 | ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%" 28 | goto ExecElevation 29 | 30 | :InvokeCmd 31 | ECHO args = "/c """ + "!batchPath!" + """ " + args >> "%vbsGetPrivileges%" 32 | ECHO UAC.ShellExecute "%SystemRoot%\%winSysFolder%\cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%" 33 | 34 | :ExecElevation 35 | "%SystemRoot%\%winSysFolder%\WScript.exe" "%vbsGetPrivileges%" %* 36 | exit /B 37 | 38 | :gotPrivileges 39 | setlocal & cd /d %~dp0 40 | if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul & shift /1) 41 | 42 | echo Admin elevation successful, installing dependancies 43 | 44 | 45 | pip install aiohttp 46 | pip install fuzzywuzzy 47 | pip install aiofiles 48 | pip install Pillow 49 | pip install -U discord.py 50 | easy_install python-slugify 51 | pip install python-Levenshtein 52 | echo. 53 | echo. 54 | echo Done! Starting configuration 55 | timeout /t 5 /nobreak 56 | 57 | 58 | 59 | cls 60 | echo Hi there. For this part you will need to set your ownerid 61 | echo for the bot, this is the same as your discord user id. 62 | echo. 63 | echo Opening the relevent page for you with more detailed instructions 64 | start "Getting userid" "https://github.com/MattBSG/ModTools/wiki/Configuration" 65 | echo. 66 | echo. 67 | echo Please enter your user id in the prompt 68 | set /p ownerid="Your User ID: " 69 | cls 70 | 71 | 72 | 73 | echo Got it. "%ownerid%" is the user id being set as bot owner 74 | echo. 75 | echo Now on that same page there is instructions to create a bot account; go ahead and do that now. 76 | echo You are going to be entering the Token in this next prompt. 77 | echo Its the thing your bot will use to login to discord 78 | echo. 79 | echo. 80 | set /p token="Your Bot Token: " 81 | cls 82 | 83 | 84 | 85 | echo Got it. "%token%" is the token the bot will use to login 86 | echo. 87 | echo On the same page you got your bot's token you will see its client id 88 | echo under APP DETAILS 89 | echo. 90 | echo. 91 | echo Please enter it here 92 | set /p clientid="Client ID: " 93 | cls 94 | 95 | 96 | 97 | echo Got it. "%clientid%" is the client id for your bot account 98 | echo. 99 | echo Last but not least go ahead you need to select what you would like 100 | echo your command prefix to be. 101 | echo. 102 | echo AKA in ^^!^^!ping "^!^!" is the prefix and goes before every command 103 | echo. 104 | echo. 105 | set /p prefix="Command Prefix: " 106 | cls 107 | echo Got it. "%prefix%" is the command prefix 108 | echo. 109 | echo. 110 | 111 | echo Writing settings to file... 112 | 113 | echo [Credentials]>"config\options.txt" 114 | echo Token = %token%>>"config\options.txt" 115 | echo.>>"config\options.txt" 116 | echo [Permissions]>>"config\options.txt" 117 | echo OwnerID = %ownerid%>>"config\options.txt" 118 | echo.>>"config\options.txt" 119 | echo [Chat]>>"config\options.txt" 120 | echo CommandPrefix = %prefix%>>"config\options.txt" 121 | echo.>>"automod\constants.py" 122 | echo BOT_USER_ACCOUNT = %clientid%>>"automod\constants.py" 123 | 124 | echo Done. 125 | 126 | echo That's it! Configuration is done! You can run the bot by executing start.bat 127 | echo. 128 | echo Pressing any key at the prompt will close this window 129 | echo and open the page to invite your bot to it's first server 130 | pause 131 | start "Bot Application Page" "https://discordapp.com/oauth2/authorize?client_id=%clientid%&scope=bot&permissions=536345663" -------------------------------------------------------------------------------- /automod/register.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from slugify import slugify 4 | 5 | from automod.constants import MUTED_IMGUR_SETUP_LINK, DOCUMENTATION_FOR_BOT 6 | from automod.response import Response 7 | from automod.version import VERSION 8 | from automod.constants import REGISTER_WORD 9 | 10 | 11 | class Register(object): 12 | def __init__(self, user, server): 13 | self.user = user 14 | self.step = 0 15 | self.server = server 16 | self.server_config_build = [] 17 | self.last_time = datetime.datetime.now() 18 | 19 | def return_server_config(self): 20 | return self.server_config_build 21 | 22 | # List below directly corrolates with adjacent appended config element. *'s indicate items changed in set up 23 | def build_empty_config(self): # ---------------------------------------------------------------------------------------------------------------------------------------------------------- 24 | self.server_config_build.append(VERSION) # 0 STRING; Version Number 25 | self.server_config_build.append(5) # 1 INT; * Number of Tokens given to a user 26 | self.server_config_build.append(5) # 2 INT; * Time till tokens are reset back to max amount 27 | self.server_config_build.append([]) # 3 LIST; * All Roles which are whitelisted ie: not subject to filtering / rate limiting 28 | self.server_config_build.append([]) # 4 LIST; All Users which are whitelisted ie: not subject to filtering / rate limiting 29 | self.server_config_build.append([]) # 5 LIST; * All strings in a sluggified format which are blacklisted and result in deletion / more action 30 | self.server_config_build.append('nothing') # 6 STRING; * Action to be taken upon black listed word if defined. Only accepts 'kick', 'ban', 'mute', 'nothing' or errors will arise 31 | self.server_config_build.append(12) # 7 INT; * The amount of hours until a user is considered a long time member of the server 32 | self.server_config_build.append(0) # 8 INT; The channel ID for the specific server's Mod Log 33 | self.server_config_build.append(0) # 9 INT; The channel ID for the specific server's Server Log 34 | self.server_config_build.append([False, # 10.0 BOOLEAN; Whether the Mod Log will be actually used or not 35 | False, # 10.1 BOOLEAN; * Whether the Server Log will be actually used or not 36 | False, # 10.2 BOOLEAN; * Whether Anti Twitch mode will be used which removes twitch emote names 37 | True, # 10.3 BOOLEAN; * Whether the bot will rate limit 38 | True, # 10.4 BOOLEAN; * Whether the bot will check for duplicate characters 39 | True]) # 10.5 BOOLEAN; * Whether the bot will check for duplicate messages 40 | self.server_config_build.append({}) # *11 DICT; Keeps track of rate limiting. Key = user ID, Value = List [time of last post, # of tokens left, List = [# of last messages based on # of tokens given]] 41 | self.server_config_build.append([]) # 12 LIST; * Channels to be ignored entirely by the bot 42 | self.server_config_build.append({}) # *13 DICT; A dict that holds the IDs of muted users as keys and the datetime of the mute as values 43 | self.server_config_build.append([]) # 14 LIST; * Roles which are given the ability to command the bot 44 | self.server_config_build.append(['77511942717046784']) # 15 LIST; Users who are given the ability to command the bot 45 | self.server_config_build.append([{}, {}]) # 16 LIST; Used for dynamic permissions, first dict holds allowed permissions, while the second holds denied permissions. 46 | self.server_config_build.append(None) # 17 NONE OBJECT; Reserved for future use 47 | 48 | async def do_next_step(self, args=None): 49 | method_name = 'step_' + str(self.step) 50 | method_call = getattr(self, method_name, lambda: None) 51 | return await method_call(args) 52 | 53 | async def restart(self): 54 | self.step = 2 55 | self.build_empty_config() 56 | return Response('Please make sure you respond with **ONLY** the information needed. Also, use `!skip` if you don\'t wish to complete a step or ' 57 | '`!restart`if you want to start over!\nFor the first step, I\'ll need to know which roles which you\'d like me omit from my ' 58 | 'filtering\n\t`example input: Moderators, Admin, Trusted`', 59 | pm=True 60 | ) 61 | 62 | async def step_0(self, args): 63 | self.step = 1 64 | self.build_empty_config() 65 | return Response('Hello {}! Let\'s get your server `{}` set up and ready to roll! \nA few prerequisites before we continue, ' 66 | 'you\'ll need to make sure the bot has all of the permissions of a regular Moderator *ie: Manage Server, Channels, Roles,' 67 | 'Messages, ect.* Also be sure the check out {} for information on setting up the **Muted** role! \n\nNow, to start the Registration' 68 | ' process, you need to go to {} and read through it. Follow the directions there and you\'ll be able to continue!'.format( 69 | self.user.name, self.server.name, MUTED_IMGUR_SETUP_LINK, DOCUMENTATION_FOR_BOT 70 | ), 71 | pm=True 72 | ) 73 | 74 | async def step_1(self, args): 75 | if REGISTER_WORD not in args: 76 | return Response('To continue the Registration process, you need to go to {} and read through everything. ' 77 | 'Follow the directions there and you\'ll be able to continue!'.format(DOCUMENTATION_FOR_BOT), 78 | pm=True 79 | ) 80 | else: 81 | self.step = 2 82 | return Response('Great! Now that you\'ve read everything, time for the configuration! \n\nPlease make sure you respond with **ONLY** the information needed. ' 83 | 'Also, use `!skip` if you don\'t wish to complete a step or `!restart`if you want to start over!\nFor the first step, I\'ll need to know which ' 84 | 'roles which you\'d like me omit from my filtering. This step can be skipped!\n\t`example input: Moderators, Admin, Trusted`', 85 | pm=True 86 | ) 87 | 88 | async def step_2(self, args): 89 | if '!restart' in args: 90 | return await self.restart() 91 | 92 | if args and '!skip' not in args: 93 | self.server_config_build[3] = args 94 | elif '!skip' in args: 95 | args = 'nothing since you decided to skip' 96 | else: 97 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 98 | pm=True) 99 | self.step = 3 100 | return Response('Okay, got it. Added {} to the list of white listed roles!\n\nNext up, I need to know which user groups you\'d like me to ' 101 | 'take orders from! They\'ll have full access to all of my commands. This step can be skipped!' 102 | '\n\t`example input: Moderators, Admin, Developers`'.format( 103 | args 104 | ), 105 | pm=True 106 | ) 107 | 108 | async def step_3(self, args): 109 | if '!restart' in args: 110 | return await self.restart() 111 | 112 | if args and '!skip' not in args: 113 | self.server_config_build[14] = args 114 | elif '!skip' in args: 115 | args = 'nothing since you decided to skip' 116 | else: 117 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 118 | pm=True) 119 | self.step = 4 120 | return Response('Okay, got it. Added {} to the list of privileged roles!\n\nNext up is token reset time in seconds' 121 | '\n\t`example input: 5`'.format( 122 | args 123 | ), 124 | pm=True 125 | ) 126 | 127 | async def step_4(self, args): 128 | if '!restart' in args: 129 | return await self.restart() 130 | try: 131 | this = int(args[0]) 132 | self.server_config_build[2] = this 133 | except: 134 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 135 | pm=True) 136 | self.step = 5 137 | return Response('Okay, got it. Added `{}` as the Token Reset Time!\n\nNext up is the number of tokens given to a user' 138 | '\n\t`example input: 5`'.format( 139 | this 140 | ), 141 | pm=True 142 | ) 143 | 144 | async def step_5(self, args): 145 | if '!restart' in args: 146 | return await self.restart() 147 | try: 148 | this = int(args[0]) 149 | self.server_config_build[1] = this 150 | except: 151 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 152 | pm=True) 153 | self.step = 6 154 | return Response('Okay, got it. Added {} as the number of user given tokens per reset period!\n\nNext up is the word filter. This step can be skipped!' 155 | '\n\t`example input: twitch.tv, discord.gg, faggots`'.format( 156 | this 157 | ), 158 | pm=True 159 | ) 160 | async def step_6(self, args): 161 | if '!restart' in args: 162 | return await self.restart() 163 | if args and '!skip' not in args: 164 | newargs = [] 165 | for thing in args: 166 | newargs.append(slugify(thing, stopwords=['https', 'http', 'www'], separator='_')) 167 | 168 | self.server_config_build[5] = newargs 169 | elif '!skip' in args: 170 | args = 'nothing since you decided to skip' 171 | else: 172 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 173 | pm=True) 174 | self.step = 7 175 | return Response('Okay, got it. Added {} to the list of black listed strings!\n\nNext up is the action to be taken upon finding a ' 176 | 'blacklisted word or if a person is rate limited over 4 times! \nI\'ll take `kick / ban / mute / nothing` as input for this option!' 177 | ' \n\t`example input: mute`'.format( 178 | args 179 | ), 180 | pm=True 181 | ) 182 | 183 | async def step_7(self, args): 184 | if '!restart' in args: 185 | return await self.restart() 186 | if 'kick' in args or 'ban' in args or 'mute' in args or 'nothing' in args: 187 | self.server_config_build[6] = str(args[0]) 188 | else: 189 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 190 | pm=True) 191 | self.step = 8 192 | return Response('Okay, got it. Added {} as the bad word action!\n\nNext up is the number of hours till a user is considered a long time member' 193 | '\n\t`example input: 36`'.format( 194 | args[0] 195 | ), 196 | pm=True 197 | ) 198 | 199 | async def step_8(self, args): 200 | if '!restart' in args: 201 | return await self.restart() 202 | try: 203 | if int(args[0]) > 10000000: 204 | return Response('The number you entered is too large! Please enter something more reasonable!\nPlease try again!', 205 | pm=True) 206 | self.server_config_build[7] = int(args[0]) 207 | except: 208 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 209 | pm=True) 210 | self.step = 9 211 | return Response('Okay, got it. Added {} as the number of hours till a user is considered a long time member!\n\nNext up is the channel you\'d' 212 | 'like all my announcements of changes to go to!\nThese will be sent when Rhino needs to communicate with the moderation teams ' 213 | 'who use the bot about new commands, new features, ect.\nMake sure its sent as the Channel ID which can be found by putting a `\` before the channel tag `ie \#channel_name`' 214 | '\nThis step can be skipped!\n\t`example input: 135866654117724160`'.format( 215 | args[0] 216 | ), 217 | pm=True 218 | ) 219 | 220 | async def step_9(self, args): 221 | if '!restart' in args: 222 | return await self.restart() 223 | if args and '!skip' not in args: 224 | self.server_config_build[17] = args[0] 225 | elif '!skip' in args: 226 | args = 'the default server channel since you decided to skip' 227 | else: 228 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 229 | pm=True) 230 | self.step = 10 231 | return Response('Okay, got it. Added {} as the channel to send all my broadcasts to!\n\nNext up is whether you want ' 232 | 'moderator action reasons to be reported! I accept `True` or `False` as inputs' 233 | '\n\t`example input: True`'.format( 234 | args 235 | ), 236 | pm=True 237 | ) 238 | 239 | async def step_10(self, args): 240 | if '!restart' in args: 241 | return await self.restart() 242 | if 'True' in args: 243 | self.server_config_build[10][0] = True 244 | args = 'will' 245 | elif 'False' in args: 246 | self.server_config_build[10][0] = False 247 | args = 'wont' 248 | else: 249 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 250 | pm=True) 251 | self.step = 11 252 | return Response('Okay, got it. I {} report action reasons!\n\nFinally, I\'ll need to know which channels you\'d like me to ignore all together.' 253 | '\nMake sure its sent as the Channel ID which can be found by putting a `\` before the channel tag `ie \#channel_name` This step can be skipped!' 254 | '\n\t`example input: 130787272781070337, 77514836912644096`'.format( 255 | args 256 | ), 257 | pm=True 258 | ) 259 | 260 | async def step_11(self, args): 261 | if '!restart' in args: 262 | return await self.restart() 263 | if args and '!skip' not in args: 264 | self.server_config_build[12] = args 265 | elif '!skip' in args: 266 | args = 'nothing since you decided to skip' 267 | else: 268 | return Response('I didn\'t quite catch that! The input I picked up doesn\'t seem to be correct!\nPlease try again!', 269 | pm=True) 270 | return Response('Okay, got it. Added {} to the list of ignored channels! \n\nThats it! Its over! Make sure you check out the syntax page' 271 | ' so that you can use me properly and I hope you have a nice day :D'.format( 272 | args 273 | ), 274 | pm=True, 275 | trigger=True 276 | ) 277 | -------------------------------------------------------------------------------- /automod/constants.py: -------------------------------------------------------------------------------- 1 | DISCORD_MSG_CHAR_LIMIT = 2000 2 | MUTED_IMGUR_SETUP_LINK = '' 3 | DOCUMENTATION_FOR_BOT = '' 4 | BOT_HANDLER_ROLE = 'Manage Roles' 5 | RHINO_SERVER = '129489631539494912' 6 | RHINO_SERVER_CHANNEL = '136304202979737600' 7 | SHITTY_BOT_IDS = ['105309315895693312', '104867073343127552', '115385224119975941'] 8 | NEW_MEM_SIMILARITY_PCT = 84 9 | OLD_MEM_SIMILARITY_PCT = 95 10 | REGISTER_WORD = 'baguette' 11 | DISCORD_EPOCH = 1420070400000 12 | RHINO_PATREON = '' 13 | RHINO_STREAMTIP = '' 14 | TWITCH_EMOTES = ['4head', 'anele', 'argieb8', 'arsonnosexy', 'asianglow', 'athenapms', 'babyrage', 'batchest', 'bcwarrior', 'biblethump', 15 | 'bigbrother', 'bionicbunion', 'blargnaut', 'bleedpurple', 'bloodtrail', 'bort', 'brainslug', 'brokeback', 'buddhabar', 16 | 'coolcat', 'corgiderp', 'cougarhunt', 'daesuppy', 'dansgame', 'dathass', 'datsheffy', 'dbstyle', 'deexcite', 'deilluminati', 17 | 'dendiface', 'dogface', 'doomguy', 'dududu', 'eagleeye', 'elegiggle', 'evilfetus', 'failfish', 'fpsmarksman', 'frankerz', 18 | 'freakinstinkin', 'fungineer', 'funrun', 'fuzzyotteroo', 'gingerpower', 'grammarking', 'hassanchop', 'heyguys', 'hotpokket', 19 | 'humblelife', 'itsboshytime', 'jebaited', 'jkanstyle', 'joncarnage', 'kapow', 'kappa', 'kappaclaus', 'kappapride', 'kappaross', 20 | 'keepo', 'kevinturtle', 'kippa', 'kreygasm', 'mau5', 'mcat', 'mikehogu', 'minglee', 'mrdestructoid', 'mvgame', 'ninjatroll', 21 | 'nonospot', 'notatk', 'notlikethis', 'ohmydog', 'omgscoots', 'onehand', 'opieop', 'optimizeprime', 'osfrog', 'oskomodo', 'ossloth', 22 | 'panicbasket', 'panicvis', 'partytime', 'pazpazowitz', 'peopleschamp', 'permasmug', 'petezaroll', 'petezarolltie', 'picomause', 23 | 'pipehype', 'pjsalt', 'pmstwin', 'pogchamp', 'poooound', 'praiseit', 'prchase', 'punchtrees', 'puppeyface', 'raccattack', 'ralpherz', 24 | 'redcoat', 'residentsleeper', 'ripepperonis', 'ritzmitz', 'rulefive', 'seemsgood', 'shadylulu', 'shazbotstix', 'shibez', 'smorc', 25 | 'smskull', 'sobayed', 'soonerlater', 'srihead', 'ssssss', 'stonelightning', 'strawbeary', 'supervinlin', 'swiftrage', 'tf2john', 26 | 'theking', 'theringer', 'thetarfu', 'thething', 'thunbeast', 'tinyface', 'toospicy', 'trihard', 'ttours', 'twitchraid', 'uleetbackup', 27 | 'unclenox', 'unsane', 'vaultboy', 'vohiyo', 'volcania', 'wholewheat', 'winwaker', 'wtruck', 'wutface', 'youwhy', "feelsgoodman", 'feelsbadman', 28 | 'minik', 'imGlitch', 'copyThis', 'pastaThat', 'ohmygoodness', 'pancakemix', 'pedobear', 'pokerface', 'rageface', 'rebeccablack', 29 | 'aplis', 'cigrip', 'chaccepted', 'fuckyea', 'datsauce', 'foreveralone', 'gaben', 'hailhelix', 'herbperve', 'idog', 30 | 'shoopdawhoop', 'swedswag', 'm&mjc', 'bttvnice', 'topham', 'whatayolk', 'watchusay', 'aplis! ', 'blackappa ', 'dogewitit ', 31 | 'savagejerky', 'kaged ', 'hhydro', 'taxibro', 'brobalt', 'buttersauce', 'baconeffect', 'suchfraud', 'candianrage', 32 | 'she\'llberight', 'ohhhkee', 'sexpanda', '(poolparty)', 'bttvwink', 'bttvangry', 'bttvconfused', 'bttvcool', 'bttvhappy', 33 | 'bttvsad', 'bttvsleep', 'bttvsurprised', 'bttvtongue', 'bttvunsure', 'bttvgrin', 'bttvheart', 'bttvtwink', 'vislaud', '(chompy) ', 34 | 'soserious ', 'batkappa ', 'karappa', 'yetiz', 'minijulia', 'motnahp ', 'sosgame', 'cruw', 'rarepepe', 'feelsbirthdayman', 'ronsmug', 35 | 'kappacool', 'zappa', 'sqshy', 'basedgod', 'burself', 'concerndoge', 'duckerz', 'fapfapfap', 'feelsbadman', 'feelsgoodman', 'firespeed', 36 | 'fishmoley', 'kkona', 'ohgod', 'poledoge', 'tehpolecat', 'angelthump', 'sourpls', 'saltycorn', 'vapenation', 'cmonBruh' 37 | ] 38 | 39 | BTTV_EMOTES = ["ohmygoodness", "pancakemix", "pedobear", "pokerface", "rageface", "rebeccablack", "aplis", "cigrip", 40 | "chaccepted", "fuckyea", "datsauce", "foreveralone", "gaben", "hailhelix", "herbperve", 41 | "shoopdawhoop", "swedswag", "m&mjc", "bttvnice", "topham", "whatayolk", "watchusay", "blackappa", 42 | "dogewitit", "savagejerky", "kaged", "hhydro", "taxibro", "brobalt", "buttersauce", "baconeffect", 43 | "suchfraud", "candianrage", "she'llberight", "ohhhkee", "sexpanda", "bttvwink", "bttvangry", 44 | "bttvconfused", "bttvcool", "bttvhappy", "bttvsad", "bttvsleep", "bttvsurprised", "bttvtongue", 45 | "bttvunsure", "bttvgrin", "bttvheart", "bttvtwink", "vislaud", "soserious", "batkappa", "karappa", 46 | "yetiz", "minijulia", "motnahp", "sosgame", "cruw", "rarepepe", "iamsocal", "hahaa", "feelsbirthdayman", 47 | "ronsmug", "kappacool", "zappa", "sqshy", "basedgod", "burself", "concerndoge", "duckerz", "fapfapfap", 48 | "feelsbadman", "feelsgoodman", "firespeed", "fishmoley", "hhhehehe", "kkona", "ohgod", "poledoge", 49 | "tehpolecat", "angelthump", "sourpls", "saltycorn", "fcreep", "vapenation", "ariw", "notsquishy"] 50 | 51 | SHITTY_BAN_LIST = ['198990163949518848', '199031946486087681', '198917908448018434', '199007095469768704', 52 | '198927656086011904', '198996406197813248', '198976654813429769', '198911480802967552', 53 | '198951170625110017', '198920258134867980', '198895854218772481', '198947789072367617', 54 | '198980857527009281', '198994397453352960', '198927899217231873', '199017602796748811', 55 | '198874999291904000', '198976432360259584', '198905157512200192', '199011560524480512', 56 | '198954194562973696', '198888875374936066', '198956775347716096', '198952633980026882', 57 | '198937692824666113', '198949317334007808', '199003588465393664', '199001189063327744', 58 | '198896510723817473', '198969049844023296', '198894380864634892', '198892640786317312', 59 | '198924384100941834', '198977137263378432', '198942447764504577', '198981775429599233', 60 | '198941725983637515', '198925245871161344', '198952522298163200', '198940806583812107', 61 | '198881027085565952', '198903060511195136', '198984698838515712', '199001008330768384', 62 | '198925974551789578', '198886419454754825', '198992772215078912', '198916185818005505', 63 | '198951254095953920', '198948594839977984', '198940292441833473', '198906081479491584', 64 | '198903191633526784', '198871541788835840', '198894098512347136', '198990588643639296', 65 | '199000616855404545', '199011620918132736', '198890012601745409', '198917654843621377', 66 | '198969037298991105', '198960194300936192', '198937472850198529', '199011936862470144', 67 | '198974428049178624', '198943970963881984', '198879316476559360', '199016388952588289', 68 | '198971103719325696', '198884155637891073', '199009638346260481', '199020015092105216', 69 | '198941681599512576', '198991286609510400', '199000012997263360', '198903287578099712', 70 | '198937548716900352', '198893468595126273', '198919096253808640', '198972978388664320', 71 | '198957093057724416', '198924834158149634', '198983368879112192', '198954487291969537', 72 | '198939906133721088', '198949337542295553', '198898634752917504', '198915487390892032', 73 | '198908158914199552', '198916654330019840', '199009570146877450', '198993108086554624', 74 | '198947392232488961', '198895314453790730', '199007704562270209', '199006531386081300', 75 | '198939110717521920', '198964254529945600', '198963930951843840', '198902725092704258', 76 | '198909952625082368', '199005346969288704', '198946400455622667', '199000805834096640', 77 | '198994844662759427', '198994754531229697', '198957558659153925', '198956273402642433', 78 | '198910114869018624', '198884094271029248', '198933590480781312', '198885053223141376', 79 | '198957384830287881', '198894183010795520', '198904922354352128', '199031546500481024', 80 | '198920824911036417', '198961972274790400', '198882821316673546', '198989028811472897', 81 | '198916838028083201', '199011523190849537', '198926979360096256', '198961786597146624', 82 | '198923582800134144', '198934704479535104', '198949499320795137', '198975054724202496', 83 | '198893416736620545', '198879343232024577', '199005448358199296', '198900447413534730', 84 | '199015931546959874', '198961733891522560', '198983261366648833', '198978714145390594', 85 | '198920183039918080', '199000153452052480', '199008569259982848', '198916809863200769', 86 | '198928099771940864', '198885148295430145', '198873854339317760', '198934691519135744', 87 | '199011376012722176', '199000913917116416', '198900252915400705', '198892272157196298', 88 | '198975000709955585', '198948739715563520', '198921129962766336', '198957861156552705', 89 | '198993610752917504', '198892208483467265', '198916904897740800', '198973035716280321', 90 | '198931685167988736', '198949212921135104', '198950761353183233', '198873041319624704', 91 | '198882634703568898', '198939823216656384', '198944069454528512', '198960125732454400', 92 | '198968566542761985', '198927753964158976', '199016500856750080', '198947328206438400', 93 | '198875574809264128', '198960246641786880', '198888810069753867', '198913747211911169', 94 | '198916706494578689', '198990513469259776', '198910094979760129', '198907847801569281', 95 | '198920325382012929', '198915038373740545', '198879716881465345', '198945269209890816', 96 | '198910595209101312', '198975044385374208', '198973518870740993', '198950962495225856', 97 | '198973236967505920', '198917323422171136', '198945116776300546', '198934446970372096', 98 | '198968438876667905', '199000854211198976', '198892880289464330', '198953455568683008', 99 | '198928543793545216', '198934352082501642', '198885824392200193', '198961670708527104', 100 | '118158677608759298', '198928289371258891', '198908092992192512', '198936288433274880', 101 | '198896593351606273', '198960883836256257', '198974273954643969', '198894435315089408', 102 | '199005423569731584', '198962205666967552', '198946233941753856', '199013356512739329', 103 | '198938737592238080', '198980880293822464', '198892730087243776', '198975016446984193', 104 | '199003338161913856', '198983213060849664', '199010581204697088', '198947226947420163', 105 | '198990617731137536', '199015695315369984', '198900510332420096', '198977474237956096', 106 | '198892499408781313', '198888662488842241', '198884321598242826', '198960172507332609', 107 | '198933940562558976', '198894256314777601', '198961292290031617', '198922107722268673', 108 | '199011098790199296', '198998719121719297', '199007226730381313', '199032501769994243', 109 | '198978751277563904', '198996141625442305', '198999974510460928', '198955534945222656', 110 | '198988773114118144', '198988486311673856', '198902274498625536', '198980317745250304', 111 | '198969172552712192', '199018306072477697', '198876960305315850', '198990712157503488', 112 | '199009097272655872', '198933246967283712', '198972361599352832', '198964221885546496', 113 | '198912907969429504', '198885260363169793', '198916483647012864', '198981865259008000', 114 | '198906518140092418', '198963774479138816', '199010042559594507', '198898574778433536', 115 | '198944023237492736', '198940282916569088', '198908655184117760', '198894500930781184', 116 | '198913492227588096', '199001717600288768', '198984562687082496', '198990935361585153', 117 | '198921243422883841', '198980942876901377', '198936231688667138', '198906387135201280', 118 | '198889927927267328', '198944340163297280', '198993155104833550', '198982707076792330', 119 | '198965055096750080', '198958247103823873', '198880895338283009', '199001138744262656', 120 | '198877096817328128', '199011677159686155', '198908026852343808', '198916757392457728', 121 | '198884028529639425', '198934652650651649', '198868828250177537', '198996944545251329', 122 | '198981463054483457', '198906766149287936', '198987262535401483', '198927646166351873', 123 | '199001260517490707', '198901211108212736', '198897651826163712', '198921199722561536', 124 | '198914456875433984', '198985641403482112', '198883059607535617', '198940083871678464', 125 | '198994825935192066', '198973592392826881', '199001313168588800', '198921339900264457', 126 | '198952696277893120', '198950531958308864', '198960772246667264', '199003159165665281', 127 | '198940095695421441', '198948530885230592', '198943591786086400', '198990395005337600', 128 | '198953375969181696', '198908545234763778', '198973811557793793', '198995159294148608', 129 | '198949481251602442', '198925470765547521', '199005333501247488', '198924154756399119', 130 | '198980622813757440', '198974669850673152', '198985355070799872', '198962944011272192', 131 | '198914233939787778', '198877389894189056', '198934158511177728', '198908075028119552', 132 | '199019918904131584', '199014955549196288', '199001510208602112', '198946008892178442', 133 | '198964491449401344', '199000866009645058', '198963468882280448', '198991329676623884', 134 | '198911822198341632', '199010765448019977', '199009150968004608', '198945455734784000', 135 | '198977404780281856', '198965245107109888', '149296506258587648', '198921455000485889', 136 | '198985591898112000', '199012730173259777', '198995243910168576', '198971261202857986', 137 | '198927139435708417', '198917853699637249', '199004644372905984', '198956435672006657', 138 | '198926467579510786', '198996063326044160', '198899741998841856', '198927845660033024', 139 | '198939284437204992', '198974638351450112', '198968556648398848', '198903302975389696', 140 | '198969731372285953', '199010277121851392', '198968054745530375', '198987042917449729', 141 | '199002875769126913', '198896142975500289', '198953189263802369', '198990433471299585', 142 | '198995440610312194', '198923910299910145', '198892803630039040', '198968892347908096', 143 | '198969785516556290', '199007172175069184', '198925736185167872', '199004714262593536', 144 | '198983853291995136', '198972198424281089', '198987659471749120', '198974339154968578', 145 | '198874728113504256', '199020135699316737', '198960448220037121', '198989597156442112', 146 | '198990315728666624', '199000069842796545', '199020655780429824', '198935155530924032', 147 | '198879026427723776', '198878083737059328', '198976714695507969', '198978091463344129', 148 | '198906459390476289', '198915557259476992', '198979010510848000', '198883291594620929', 149 | '198929851413954560', '198937523731431434', '198910044962684928', '198892562201706496', 150 | '198928822903504897', '198991993874022400', '198934638125776898', '198966462721949696', 151 | '198960063853887488', '198927495473528832', '198921507651452928', '198914967200727041', 152 | '198949559106273280', '198912105196421120', '198987643822800897', '198925329899716608', 153 | '198938815300239360', '198999079861223424', '198897397298888704', '198976101987516416', 154 | '198991794787188736', '198995515315191809', '198996262257688576', '198927472669097985', 155 | '199003506730991625', '198936512144998401', '198970647467130880', '199015804333719553', 156 | '198904092079161344', '198987154943246336', '199018214171082753', '199017535469780992', 157 | '198977519272067072', '198998608014475264', '198894130309365761', '199015732930019328', 158 | '199004766523621376', '198956964078813184', '198906431276056576', '198927543280205825', 159 | '198927162475020288', '198940703877758976', '198892001805074432', '198937984215547904', 160 | '198905488421683216', '198877984113950720', '198972917277523968', '198981645334740993', 161 | '198928875588157441', '198985929887580161', '198985982156996609', '198905550921138177', 162 | '199011882781245440', '198998896091987968', '198902667823546368', '198970378469507083', 163 | '198983001810534400', '199001702161055744', '198996044749471745', '198972075791220736', 164 | '199004581089247234', '198972139167023105', '198993753493602314', '198986158544257025', 165 | '198927383527555072', '198893101434011648', '198910648401264640', '198877243303264257', 166 | '198989819953545216', '198920623261614083', '198948434793857024', '198874384503406592', 167 | '198908879025733632', '198961311361662976', '198990665546334208', '198947252574748672', 168 | '198916138459987968', '198877298357698561', '198963515443249152', '199018364327165952', 169 | '198929866618437632', '198999132457664512', '198925027985457152', '198958227101057024', 170 | '199013777369202688', '198981187325132800', '198880837427527680', '198956660834828288', 171 | '198895423119818752', '198938045733404673', '198946458559447040', '199008501756854272', 172 | '198904317934174208', '199003125917548544', '198990906915946496', '198924442951352321', 173 | '198904255459885056', '198903684950786049', '198898451042271233', '198898268468543488', 174 | '199005915775631360', '199009234786975744', '198936356892835841', '198936668424765440', 175 | '198996786080251904', '198974718483759115', '199015614961025024', '198941659268907009', 176 | '198896020904607754', '198947719195262976', '198870657587609601', '198906145686028289', 177 | '198912018076401664', '198882679050076160', '198908795588444170', '138371214048624641', 178 | '198962938067943425', '198946809509314561', '198975476214136832', '198920881207115776', 179 | '198879154089754625', '198988533430616064', '198945506221621249', '198880367653027840', 180 | '198912922536247307', '198948018735546368', '198974947249487872', '199002067262636033', 181 | '199010971166048256', '199011640249810944', '198950983114424320', '198987420081979392', 182 | '198936709629607937', '198970203739127809', '198974882992619520', '198987346769739776', 183 | '199016418233155585', '198990126557298688', '198951941966004224', '198883817295970304', 184 | '198993010040635394', '198970864090218496', '198934511822569472', '198918085502173185', 185 | '198969129376415746', '198945583619244032', '198883763462209536', '198938453491187714', 186 | '198889515732041729', '198898701236830208', '198866862044479488', '198945991263649792', 187 | '198994181690097664', '199007850536632320', '198928112610836481', '199010696812298242', 188 | '198947428999757824', '198919037177167872', '199001057068580864', '198890372976345088', 189 | '198906835053314049', '198939691171577856', '198876455566966785', '198929837275086848', 190 | '198955104009715712', '198889671026147328', '198885108181106688', '198924377767673856', 191 | '199007745108738048', '198920642345697280', '198928272489185280', '199052385476280320', 192 | '198961617688330240', '198891951850782720', '198947498759421952', '198998666491592704', 193 | '199008669881466881', '199015164635250688', '198994041726173185', '193887485086924800', 194 | '198928490496655370', '198867734082224128', '199019706403782656', '199019650401435648', 195 | '198927780170170369', '198945877966979072', '199058835673972736', '198990031111716864', 196 | '198933391981281280', '198976457337208832', '198874903066181632', '198925411164487680', 197 | '198941895483850752', '199004214318333953', '198884282553335808', '199002002552913921', 198 | '198995503080407049', '198891503773417472', '198902538995499008', '199002376433041408', 199 | '198944228443684864', '198922802252873728', '199020193266139138', '198951916657442816', 200 | '198948454095912962', '198894032909369344', '198907072820150274', '198985389376012288', 201 | '198897584952049664', '199015750130860041', '199001945808044034', '198927215646343169', 202 | '198995336092581888', '198989111678337024', '198948691917275136', '198951336631468032', 203 | '198913777981194241', '198947277568606208', '198962835097649153', '198963360065126402', 204 | '198962622169743362', '199006173968596993', '198984580626120704', '198957450538385410', 205 | '189102478522843136', '198980595202654208', '198904289345667082', '198916109896777728', 206 | '198911769039732736', '199010854312738818', '198918690933047296', '198972227541139459', 207 | '198978759561445376', '198925357741506560', '198990536655241218', '198882867273662464', 208 | '198919016662695956', '199005047898505217', '118820404654637057', '198915801590267904', 209 | '198957080449777664', '198895362646212608', '198891882179330048', '198990482297192468', 210 | '198993972604043264', '199005024121126912', '198895900234481664', '198925678782054400', 211 | '198921914192756737', '198976835290136576', '198984883245154304', '198869334439755777', 212 | '198983277300809728', '198983994061094922', '198987118834483200', '199006449941217289', 213 | '198890646453354496', '198940585501917184', '198927995040169995', '198934405819924481', 214 | '198976847172599808', '198885725343580161', '198986988228050944', '198970627061841920', 215 | '198989758985273355', '199002015144083456', '198908705864024065', '198995382485778432', 216 | '198912742420119552', '198960672950845441', '198976365381419008', '198994019500556290', 217 | '198981217457012736', '198920565296332800', '199007524840538123', '199009180332457985', 218 | '198880075725275137', '198944265152364544', '103534009060974592', '198987474435964929', 219 | '198970146298134529', '198936582940524545', '199006099431620608', '198876199118700546', 220 | '198961559207149568', '198983864838782977', '198973465326387210', '198974227720699904', 221 | '198903111253884928', '198885016934023168', '198892838522454016', '198937190166822912', 222 | '199000090264862722', '198940536978145281'] 223 | --------------------------------------------------------------------------------