├── .gitignore ├── LICENSE ├── README.md ├── blacklist.txt ├── cogs ├── admin.py ├── configurations.py ├── info.py └── viewlists.py ├── init.py ├── requirements.txt ├── run_setup.py ├── utils ├── functions.py └── paginator.py └── whitelist.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Secret Token 132 | token.txt 133 | 134 | # Database 135 | delink.db 136 | 137 | 138 | # Directories 139 | .data 140 | 141 | # config 142 | config.yaml 143 | 144 | # credentials 145 | cred.yaml 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hugonun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delink-bot 2 | 3 | A Discord bot to combat phishing links for Steam trades and Discord gifts. 4 | 5 | ## Features 6 | 7 | - Removes domains listed on the blacklist. 8 | - Automatically deletes domains that have a certain pattern and are not yet on the blacklist. 9 | - Allows you to make your own URL blacklist and whitelist for your server. We will periodically check and add it to the global list. 10 | - Supports other phishing and scam databases, giving us a total of +15000 URLs. 11 | 12 | ## Invite link 13 | 14 | Invite the bot to your server: https://discord.ly/delink 15 | 16 | ## Requirement 17 | 18 | `python3 -m pip install -r requirements.txt` 19 | 20 | ## Setup 21 | 22 | Create the file `token.txt` and add your Discord token inside it. 23 | 24 | Then run the bot using `python3 init.py`. 25 | 26 | ## For fast setup and run bot after 27 | 28 | `python run_setup.py` 29 | -------------------------------------------------------------------------------- /blacklist.txt: -------------------------------------------------------------------------------- 1 | # Note: Domains using .gift or .gifts TLD should also get listed here, despite there being a TLD filter. 2 | dissord.gift 3 | discordn.gift 4 | discordi.gift 5 | discordt.gift 6 | discord-cpp.com 7 | discord-app.net 8 | discorcll.online 9 | discorcl.link 10 | 1nitro.club 11 | appnitro-discord.com 12 | asstralissteam.org.ru 13 | discord-steam-promo.com 14 | discordgifte.com 15 | dicsord-ticket.com 16 | discord-appnitro.com 17 | ds-nitro.com 18 | nitro-discordapp.com 19 | nitrodsgiveways.com 20 | steam-nitro.online 21 | glfte.com 22 | steamcommunytu.ru 23 | dilscordis.com 24 | dicsord-gifte.ru 25 | steamcommunitiuy.com 26 | streamcomnmnnunity.xyz 27 | steam-discord.xyz 28 | disrcod-drop.com 29 | join-newhypesquad.com 30 | formulary-hypesquad-join.com 31 | disceord.gift 32 | dilsordnltro.com 33 | dsscordgift.xyz 34 | djlscord.com 35 | dlscrod-game.ru 36 | discorsd.gift 37 | disckord.com 38 | nitrospromotions.com 39 | disdornitro.com 40 | diiscord-nitro.com 41 | dlsccord-apps.club 42 | dliscord.gift 43 | discordf.gift 44 | nitro-app-store.com 45 | discord-app.com 46 | discord.givaewey.com 47 | discord.giveawey.com 48 | steamnitros.com 49 | discord-promox.com 50 | airdrop-nitro.com 51 | steam-nltro.ru 52 | -------------------------------------------------------------------------------- /cogs/admin.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | import discord 3 | from utils.paginator import Pag 4 | from utils.functions import * 5 | 6 | class Admin(commands.Cog): 7 | """Admin-only commands that make the bot dynamic.""" 8 | 9 | def __init__(self, bot): 10 | self.bot = bot 11 | 12 | @commands.command(hidden=True) 13 | @commands.is_owner() 14 | async def load(self, ctx, extension): 15 | """Loads a module.""" 16 | try: 17 | self.bot.load_extension(f'cogs.{extension}') 18 | except Exception as e: 19 | await ctx.send('{}: {}'.format(type(e).__name__, e)) 20 | else: 21 | await ctx.send('\N{OK HAND SIGN}') 22 | 23 | @commands.command(hidden=True) 24 | @commands.is_owner() 25 | async def unload(self, ctx, extension): 26 | """Unloads a module.""" 27 | try: 28 | self.bot.unload_extension(f'cogs.{extension}') 29 | except Exception as e: 30 | await ctx.send('{}: {}'.format(type(e).__name__, e)) 31 | else: 32 | await ctx.send('\N{OK HAND SIGN}') 33 | 34 | @commands.command(name='reload', hidden=True) 35 | @commands.is_owner() 36 | async def _reload(self, ctx, extension): 37 | """Reloads a module.""" 38 | try: 39 | self.bot.unload_extension(f'cogs.{extension}') 40 | self.bot.load_extension(f'cogs.{extension}') 41 | except Exception as e: 42 | await ctx.send('{}: {}'.format(type(e).__name__, e)) 43 | else: 44 | await ctx.send('\N{OK HAND SIGN}') 45 | 46 | @commands.command(hidden=True) 47 | @commands.is_owner() 48 | async def adminstats(self, ctx): 49 | """Show stats.""" 50 | i = 0 51 | x = [] 52 | guildlist = sorted(ctx.bot.guilds, key=lambda x: x.member_count, reverse=True) 53 | for guild in guildlist: 54 | i += 1 55 | x += ['[{0}] {1} ({2}members) [{3}]'.format(i, guild.name, guild.member_count, guild.id)] 56 | 57 | paginator = Pag(client=self.bot, pages=[]) 58 | 59 | cchunk = chunkarray(array=x, size=20) 60 | await paginator.chunktopage(chunk=cchunk, color=discord.Color.red(),title="Admin stats", insertbefore="**Servers using the bot:**") 61 | await paginator.start(ctx=ctx) 62 | 63 | def setup(bot): 64 | """Add class as a cog""" 65 | bot.add_cog(Admin(bot)) 66 | -------------------------------------------------------------------------------- /cogs/configurations.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.ext.commands.core import has_guild_permissions 4 | from utils.functions import * 5 | import tldextract 6 | import whois 7 | 8 | class configurations(commands.Cog): 9 | """All commands pertaining to configurations like adding or removing links.""" 10 | def __init__(self, bot): 11 | self.bot = bot 12 | 13 | @commands.command() 14 | @commands.check_any(has_guild_permissions(manage_webhooks=True),has_guild_permissions(manage_guild=True), has_guild_permissions(administrator=True)) 15 | async def removelink(self, ctx, table:str, msg): 16 | """Remove a link from the custom white/black list""" 17 | tabletoedit = whattabletoedit(table=table) 18 | if tabletoedit != 'blacklist' and tabletoedit != 'whitelist': 19 | return await ctx.send(tabletoedit) 20 | 21 | url = findurls(msg)[0] 22 | if not url: 23 | await ctx.send('No valid URL has been given.') 24 | else: 25 | if deleteurl(ctx.guild.id,tldextract.extract(url).registered_domain,tabletoedit): 26 | await ctx.send(f'URL `{tldextract.extract(url).registered_domain}` has been deleted!') 27 | else: 28 | await ctx.send(f'URL `{tldextract.extract(url).registered_domain}` has not been found.') 29 | 30 | @removelink.error 31 | async def removelink_error(self, ctx, error): 32 | """Handle errors thrown from the removelink command""" 33 | if isinstance(error, commands.MissingRequiredArgument): 34 | await ctx.send(f'To use the removelink command do: {self.bot.command_prefix}removelink ') 35 | elif isinstance(error, commands.NoPrivateMessage): 36 | await ctx.send('This command may not be used in DMs.') 37 | elif isinstance(error, commands.MissingPermissions): 38 | await ctx.send('You are missing the required permissions.') 39 | 40 | @commands.command() 41 | @commands.check_any(has_guild_permissions(manage_webhooks=True),has_guild_permissions(manage_guild=True), has_guild_permissions(administrator=True)) 42 | async def addlink(self, ctx, table:str, msg): 43 | """Add a link to the custom white/black list""" 44 | tabletoedit = whattabletoedit(table=table) 45 | if tabletoedit != 'blacklist' and tabletoedit != 'whitelist': 46 | return await ctx.send(tabletoedit) 47 | 48 | reversetabletoedit = "whitelist" if tabletoedit == "blacklist" else "blacklist" 49 | 50 | url = findurls(msg)[0] 51 | if not url: 52 | await ctx.send('No valid URL has been given.') 53 | else: 54 | if checkurl(ctx.guild.id,tldextract.extract(url).registered_domain,reversetabletoedit): 55 | await ctx.send(f'Error: URL `{tldextract.extract(url).registered_domain}` already exists in your {reversetabletoedit}, to remove it, use `{self.bot.command_prefix}removelink {reversetabletoedit} {tldextract.extract(url).registered_domain}`') 56 | else: 57 | inserturl(ctx.guild.id,tldextract.extract(url).registered_domain,tabletoedit) 58 | await ctx.send(f'URL `{tldextract.extract(url).registered_domain}` has been added!') 59 | 60 | 61 | 62 | @addlink.error 63 | async def addlink_error(self, ctx, error): 64 | """Handle errors thrown from the addlink command""" 65 | if isinstance(error, commands.MissingRequiredArgument): 66 | await ctx.send(f'To use the addlink command do: {self.bot.command_prefix}addlink ') 67 | elif isinstance(error, commands.NoPrivateMessage): 68 | await ctx.send('This command may not be used in DMs.') 69 | elif isinstance(error, commands.MissingPermissions): 70 | await ctx.send('You are missing the required permissions.') 71 | 72 | @commands.command() 73 | async def whois(self, ctx, domain:str, msg): 74 | """Check domain whois information""" 75 | url = findurls(domain)[0] 76 | w = whois.whois(url) 77 | embed = discord.Embed(title=f'Whois Lookup - {url}' , description=w, color=0xffffff) 78 | await ctx.send(embed=embed) 79 | 80 | 81 | def setup(bot): 82 | """Add class as a cog""" 83 | bot.add_cog(configurations(bot)) 84 | -------------------------------------------------------------------------------- /cogs/info.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | import time 4 | 5 | class Info(commands.Cog): 6 | """Commands pertaining to system information like ping, server stats, etc.""" 7 | def __init__(self, bot): 8 | self.bot = bot 9 | 10 | @commands.command() 11 | async def ping(self, ctx): 12 | """Get the bot's current websocket and API latency.""" 13 | start_time = time.time() 14 | message = await ctx.send("Testing Ping...") 15 | end_time = time.time() 16 | await message.edit(content=f"Pong! {round(self.bot.latency * 1000)}ms\nAPI: {round((end_time - start_time) * 1000)}ms") 17 | 18 | @commands.command() 19 | async def invite(self, ctx): 20 | """Invite the bot to your server.""" 21 | await ctx.send("https://discord.com/oauth2/authorize?client_id=903114852631969802&scope=bot&permissions=2147871808") 22 | 23 | @commands.command() 24 | async def support(self, ctx): 25 | """Join our support server.""" 26 | await ctx.send("https://discord.com/invite/Gf3panp2bw") 27 | 28 | 29 | def setup(bot): 30 | """Add class as a cog""" 31 | bot.add_cog(Info(bot)) 32 | -------------------------------------------------------------------------------- /cogs/viewlists.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from utils.functions import * 4 | from utils.paginator import Pag 5 | 6 | from init import whitelist, blacklist 7 | 8 | class Viewlists(commands.Cog): 9 | """View a specified list""" 10 | 11 | def __init__(self, bot): 12 | self.bot = bot 13 | self.SIZED_CHUNKS = 10 14 | 15 | @commands.command(aliases=['viewblacklist', 'bl', 'blist']) 16 | async def blacklist(self, ctx): 17 | """View the current blacklisted links, globally and local.""" 18 | paginator = Pag(client=self.bot, pages=[]) 19 | 20 | urls = [item for t in retriveurls(ctx.guild.id,'blacklist') for item in t] 21 | 22 | if not urls: 23 | urls = ['Currently no blacklisted urls'] 24 | 25 | cchunk = chunkarray(array=urls, size=self.SIZED_CHUNKS) 26 | gchunk = chunkarray(array=blacklist, size=self.SIZED_CHUNKS) 27 | await paginator.chunktopage(chunk=cchunk, color=discord.Color.red(),title="Viewing **blacklisted** urls", insertbefore="**Custom blacklist:**") 28 | await paginator.chunktopage(chunk=gchunk, color=discord.Color.red(),title="Viewing **blacklisted** urls", insertbefore="**Global blacklist:**") 29 | await paginator.start(ctx=ctx) 30 | 31 | @commands.command(aliases=['viewwhitelist', 'wl', 'wlist']) 32 | async def whitelist(self, ctx): 33 | """View the current whitelisted links, globally and local.""" 34 | paginator = Pag(client=self.bot, pages=[]) 35 | 36 | urls = [item for t in retriveurls(ctx.guild.id,'whitelist') for item in t] 37 | 38 | if not urls: 39 | urls = ['Currently no whitelisted urls'] 40 | 41 | cchunk = chunkarray(array=urls, size=self.SIZED_CHUNKS) 42 | gchunk = chunkarray(array=whitelist, size=self.SIZED_CHUNKS) 43 | await paginator.chunktopage(chunk=cchunk, color=discord.Color.green(),title="Viewing **whitelisted** urls", insertbefore="**Custom whitelist:**") 44 | await paginator.chunktopage(chunk=gchunk, color=discord.Color.green(),title="Viewing **whitelisted** urls", insertbefore="**Global whitelist:**") 45 | await paginator.start(ctx=ctx) 46 | 47 | def setup(bot): 48 | """Add class as a cog""" 49 | bot.add_cog(Viewlists(bot)) -------------------------------------------------------------------------------- /init.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | import tldextract 4 | from utils.paginator import Pag 5 | from utils.functions import * 6 | import os 7 | 8 | description = '''Link filter bot.''' 9 | 10 | intents = discord.Intents.default() 11 | help_command = commands.DefaultHelpCommand( 12 | no_category = 'Need some help?' 13 | ) 14 | bot = commands.AutoShardedBot(command_prefix='!d ', help_command= help_command, description=description, intents=intents) 15 | 16 | setupdb() 17 | 18 | # Whitelist TODO: Move it out 19 | with open('whitelist.txt', 'r') as file: 20 | whitelist = file.read().splitlines() 21 | 22 | whitelist = [item for item in whitelist if not(item == '' or item.startswith('#'))] 23 | 24 | ################# 25 | ### Bot event ### 26 | ################# 27 | 28 | @bot.event 29 | async def on_ready(): 30 | print(f'Logged in as {bot.user} (ID: {bot.user.id})') 31 | print('------') 32 | activity = discord.Activity(name="links. | !d help", type=3) 33 | await bot.change_presence(status=discord.Status.online, activity=activity) 34 | 35 | for filename in os.listdir('./cogs'): 36 | if filename.endswith('.py'): 37 | bot.load_extension(f'cogs.{filename[:-3]}') 38 | 39 | @bot.event 40 | async def on_message(message): 41 | await bot.process_commands(message) 42 | if message.author.bot: 43 | return 44 | # read blacklist.txt and whitelist.txt, and filter from there 45 | urllist = findurls(message.content) 46 | if urllist: 47 | for url in urllist: 48 | urlextract = tldextract.extract(url) 49 | # Filter by TLD and common strings 50 | if urlextract.suffix in ['gift','gifts'] or any(baddomainname in urlextract.domain for baddomainname in ['discordgift','discord-gift','discordnitro','discord-nitro']): 51 | if url.startswith(('http://', 'https://')): 52 | if urlextract.registered_domain not in whitelist and not checkurl(message.guild.id, urlextract.registered_domain, 'whitelist'): 53 | await deletemsg(message) 54 | # Filter by blacklist 55 | checkblacklisturl(message.guild.id,url) 56 | if checkblacklisturl(message.guild.id,url): 57 | if urlextract.registered_domain not in whitelist and not checkurl(message.guild.id, urlextract.registered_domain, 'whitelist'): 58 | await deletemsg(message) 59 | 60 | # Check again on edit 61 | @bot.event 62 | async def on_message_edit(before, after): 63 | await on_message(after) 64 | 65 | # For analytic purposes 66 | @bot.event 67 | async def on_guild_join(guild): 68 | channel = bot.get_channel(914397885129424976) 69 | await channel.send('[{0}] {1} ({2}members)'.format(len(bot.guilds), guild.name, guild.member_count)) 70 | 71 | @bot.event 72 | async def on_guild_remove(guild): 73 | channel = bot.get_channel(914397885129424976) 74 | await channel.send('Guild left - [{0}] {1} ({2}members)'.format(len(bot.guilds), guild.name, guild.member_count)) 75 | 76 | # Token 77 | with open('token.txt', 'r') as file: 78 | dtoken = file.read().replace('\n', '') 79 | 80 | bot.run(dtoken) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugonun/delink-bot/189bf6227b2311252be2793bef2e79088e21197d/requirements.txt -------------------------------------------------------------------------------- /run_setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | """Ask for secret token and then write to token.txt file""" 3 | x=input("What is your discord bot Token: ") 4 | with open('token.txt', 'w') as token_file: 5 | token_file.write(x) 6 | 7 | """Install all required requirements""" 8 | os.system("python3 -m pip install -r requirements.txt") 9 | 10 | """Start bot""" 11 | os.system("python3 init.py") 12 | -------------------------------------------------------------------------------- /utils/functions.py: -------------------------------------------------------------------------------- 1 | import re 2 | import threading 3 | import sqlite3 4 | import os 5 | import tldextract 6 | import requests 7 | 8 | # make directory for database if it doesnt exist already 9 | path = '{0}/.data/'.format(os.getcwd()) 10 | os.makedirs(path, exist_ok=True) 11 | 12 | con = sqlite3.connect('.data/delink.db') 13 | cur = con.cursor() 14 | 15 | # temporary copy, TODO: move to separate file 16 | with open('whitelist.txt', 'r') as file: 17 | whitelist = file.read().splitlines() 18 | 19 | with open('blacklist.txt', 'r') as file: 20 | blacklist = file.read().splitlines() 21 | 22 | whitelist = [item for item in whitelist if not(item == '' or item.startswith('#'))] 23 | blacklist = [item for item in blacklist if not(item == '' or item.startswith('#'))] 24 | 25 | def updateblacklist(): 26 | # Threaded function to update blacklist every hour 27 | global blacklist 28 | r = requests.get('https://api.hyperphish.com/gimme-domains') 29 | if r.status_code == 200: 30 | blacklist = blacklist + r.json() 31 | threading.Timer(60*60, updateblacklist).start() 32 | 33 | updateblacklist() 34 | 35 | def chunkarray(array: list, size: int): 36 | """Return a list of specified sized lists from a list""" 37 | return [array[i:i + size] for i in range(0, len(array), size)] 38 | 39 | def whattabletoedit(table): 40 | """Determine what table to edit""" 41 | blacklistOptions=['blacklist','b','bl','blist','black'] 42 | whitelistOptions=['whitelist','w','wl','wlist','white'] 43 | 44 | if table in blacklistOptions: 45 | return 'blacklist' 46 | elif table in whitelistOptions: 47 | return 'whitelist' 48 | else: 49 | return 'That is not a vaild option please say {0} to blacklist a url or {1} to white list a url'.format(", ".join(blacklistOptions[:-1]) +" or "+blacklistOptions[-1], ", ".join(whitelistOptions[:-1]) +" or "+whitelistOptions[-1]) 50 | 51 | def findurls(s): 52 | """Use a regex to pull URLs from a message""" 53 | regex = r"(?i)\b(((https?|ftp|smtp):\/\/)?(www.)?[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+(\/[a-zA-Z0-9#]+\/?)*\/*)" 54 | url = re.findall(regex,s) 55 | return [x[0] for x in url] 56 | 57 | async def deletemsg(message): 58 | """Delete specified message""" 59 | await message.delete() 60 | await message.channel.send('WARNING: Your message has been deleted for containing a possible scam URL. <@' + str(message.author.id) + '>', delete_after=5) 61 | 62 | def setupdb(): 63 | """Ensure the database is setup correctly""" 64 | cur.execute('''CREATE TABLE IF NOT EXISTS blacklist 65 | (guild_id int, url text, UNIQUE(guild_id, url))''') 66 | 67 | cur.execute('''CREATE TABLE IF NOT EXISTS whitelist 68 | (guild_id int, url text, UNIQUE(guild_id, url))''') 69 | 70 | cur.execute('''CREATE TABLE IF NOT EXISTS guildsettings 71 | (guild_id int, logchannel int, mutelogchannel int, muterank int, deletesbeforemute int, UNIQUE(guild_id))''') 72 | 73 | cur.execute('''CREATE TABLE IF NOT EXISTS mutecandidates 74 | (user_id int, guild_id int, deletions int, lastdelete int)''') 75 | 76 | con.commit() 77 | 78 | def inserturl(guild_id, url, table): 79 | """Insert a URL into the specified table. EX: blacklist or whitelist""" 80 | cur.execute('''INSERT OR IGNORE INTO %s VALUES (?,?)''' % (table), (guild_id,url)) 81 | con.commit() 82 | 83 | def deleteurl(guild_id, url, table): 84 | """Delete a URL from the specified table. EX: blacklist or whitelist""" 85 | cur.execute('''DELETE FROM %s WHERE guild_id = ? AND url = ?''' % (table), (guild_id,url)) 86 | con.commit() 87 | if cur.rowcount < 1: 88 | return False 89 | else: 90 | return True 91 | 92 | def retriveurls(guild_id, table): 93 | """Retrieve all URLs from the specified table. EX: blacklist or whitelist""" 94 | cur.execute('''SELECT url FROM %s WHERE guild_id = ?''' % (table), (guild_id,)) 95 | return cur.fetchall() 96 | 97 | def checkurl(guild_id, url, table): 98 | """Check if a URL is in the white/black list table""" 99 | cur.execute('''SELECT url FROM %s WHERE guild_id = ? AND url = ?''' % (table), (guild_id,url)) 100 | return cur.fetchone() 101 | 102 | def checkblacklisturl(guild_id, url): 103 | urlextract = tldextract.extract(url) 104 | # Check registered domain 105 | if urlextract.registered_domain in blacklist: 106 | return True 107 | # Check subdomains 108 | elif urlextract.subdomain and not urlextract.subdomain == "www": 109 | suburl = urlextract.subdomain.split('.') 110 | if suburl[0] == 'www': 111 | suburl.pop(0) 112 | suburl.reverse() 113 | oldsub = '.' 114 | for sub in suburl: 115 | urltocheck = sub + oldsub + urlextract.registered_domain 116 | oldsub = '.' + sub + oldsub 117 | if urltocheck in blacklist: 118 | return True 119 | # Check in DB (TODO: Check subdomains too) 120 | if checkurl(guild_id, urlextract.registered_domain, 'blacklist'): 121 | return True 122 | # Pass 123 | else: 124 | return False 125 | 126 | -------------------------------------------------------------------------------- /utils/paginator.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import discord 3 | 4 | class Pag(): 5 | def __init__(self, client, pages): 6 | self.buttons = [u"\u23EA", u"\u2B05", "⏹️", u"\u27A1", u"\u23E9"] 7 | self.currentpage = 0 8 | self.pages = pages 9 | self.client = client 10 | 11 | 12 | def set_pages(self, pages): 13 | """Dynamically set the pages""" 14 | self.pages = pages 15 | 16 | async def start(self, ctx): 17 | """Create a reaction listener and handle reaction responses""" 18 | msg = await ctx.send(embed=self.pages[0]) 19 | 20 | for button in self.buttons: 21 | await msg.add_reaction(button) 22 | 23 | timedout = False 24 | while not timedout: 25 | try: 26 | reaction, user = await self.client.wait_for("reaction_add", check=lambda reaction, user: user == ctx.author and reaction.message.id == msg.id and reaction.emoji in self.buttons, timeout=60.0) 27 | except asyncio.TimeoutError: 28 | embed = self.pages[self.currentpage] 29 | embed.set_footer(text="Timed out") 30 | timedout = True 31 | await msg.clear_reactions() 32 | 33 | else: 34 | previous_page = self.currentpage 35 | if reaction.emoji == u"\u23EA": 36 | self.currentpage = 0 37 | 38 | elif reaction.emoji == u"\u2B05": 39 | if self.currentpage > 0: 40 | self.currentpage -= 1 41 | 42 | elif reaction.emoji == u"\u27A1": 43 | if self.currentpage < len(self.pages)-1: 44 | self.currentpage += 1 45 | 46 | elif reaction.emoji == u"\u23E9": 47 | self.currentpage = len(self.pages)-1 48 | 49 | elif reaction.emoji == "⏹️": 50 | timedout = True 51 | await msg.clear_reactions() 52 | return 53 | 54 | if self.currentpage != previous_page: 55 | await msg.edit(embed=self.pages[self.currentpage]) 56 | 57 | for button in self.buttons: 58 | await msg.remove_reaction(button, ctx.author) 59 | 60 | async def createPage(self, title: str, description: str, color: discord.Color): 61 | """Create and return a formatted Embed""" 62 | embed = discord.Embed() 63 | if title: 64 | embed.title = title 65 | if description: 66 | embed.description = description 67 | if color: 68 | embed.color = color 69 | return embed 70 | 71 | async def chunktopage(self, chunk: list, color: discord.Color, title: str, insertbefore: str): 72 | """Take formatted chunks and make a page using the paginator class""" 73 | for tempchunk in chunk: 74 | tempchunk.insert(0,insertbefore) 75 | contenttoadd = '\n'.join(tempchunk) 76 | self.pages.append(await self.createPage(title=title,description=contenttoadd,color=color)) -------------------------------------------------------------------------------- /whitelist.txt: -------------------------------------------------------------------------------- 1 | # Official domains 2 | discord.gift 3 | discord.gg 4 | discord.com 5 | discordapp.com 6 | discordapp.net 7 | discordmerch.com 8 | 9 | # Friendly domains 10 | discordresources.com 11 | discord.wiki 12 | discorddb.com 13 | discord.js.org 14 | discord.io 15 | discordjs.guide 16 | 17 | 18 | # .gift and .gifts domains in the top 1M Alexa ranking 19 | crediter.gift 20 | packedwithpurpose.gifts 21 | 123movies.gift 22 | admiralwin.gift 23 | gol.gift 24 | newhome.gifts 25 | --------------------------------------------------------------------------------