def API_draw_graph(): ''' : text => edges $ curl "http://127.0.0.1:5000/draw" -H "Content-Type:text/json" -d '{"notes": "\n\nA2\n= x3\n: y4\n\nB6\n~ z7\n"}' ''' text = request.get_json(force=True)['notes'] data = draw_graph(text) # None not a collection csrft = request.cookies.get('csrft') if csrft not in db.database.collection_names(): csrft = random_string() files = [(text, '(client)')] notes = N.make_notes(files) with db.as_collection(csrft): db.collection().remove() # drops all documents, keeps collection N.write_notes_to_database(notes) #TODO expire collection response = flask.jsonify(**data) response.set_cookie('csrft', csrft) return response
async def remove(self, ctx: commands.Context, imageName: str): ''' removes an image from the favourites list ''' try: tokens = imageName.split("/") # split tokens if len(tokens) > 1: collection("favouritensfw").delete_one({ "server": ctx.guild.id, "imageID": tokens[1], "user": ctx.author.id, "category": tokens[0] }) fullname = f"{tokens[0]}/{tokens[1]}" else: collection("favouritensfw").delete_one({ "server": ctx.guild.id, "imageID": tokens[0], "user": ctx.author.id, "category": "" }) fullname = f"{tokens[0]}" await reactOK(ctx) await ctx.send(f"`{fullname}` has been removed.", delete_after=10) except BaseException: await ctx.send("Image not found.")
async def custom_list(self, ctx: commands.Context, list_name: str): ''' Displays a list with the given name, opening it for editing. Creates a new list if the name doesn't exist. Opens a new list if you already have one open ''' if ctx.invoked_subcommand is None: if self.active[ctx.channel.id]: await self.close(ctx) # close list if L := collection("lists").find_one({ "server": ctx.guild.id, "name": list_name }): self.active[ctx.channel.id] = CustomList(L) await self.print_list(ctx) await ctx.send( f"You have opened the list `{list_name}`.\nMake sure to `{bot_prefix}list close` to save your list." ) else: # New list L = { "server": ctx.guild.id, "name": list_name, "author": ctx.author.id, "list": [] } self.active[ctx.channel.id] = CustomList(L) collection("lists").insert_one(L) await ctx.send( f"You have created the list `{list_name}`.\nMake sure to `{bot_prefix}list close` to save your list." )
async def remind(self, ctx: commands.context, time: str, *, message: str): ''' Adds a reminder in some amount of time. Time should be specified as "1d" "60m" "23h" etc. Supports minutes, hours, days. Defaults to hours if no unit given. ''' if time[-1] == "d": advance = datetime.timedelta(days=float(time[:-1].strip())) elif time[-1] == "m": advance = datetime.timedelta(minutes=float(time[:-1].strip())) elif time[-1] == "h": advance = datetime.timedelta(hours=float(time[:-1].strip())) else: advance = datetime.timedelta(hours=float(time.strip())) reminder = { "server": ctx.guild is not None, "message": message, "time": datetime.datetime.utcnow() + advance, "user": ctx.author.id, "channel": ctx.channel.id if ctx.guild else 0, "jumpurl": ctx.message.jump_url } collection("reminders").insert_one(reminder) await ctx.send( f"I will remind you on {(local_time() + advance).strftime('%B %d %Y at %I:%M %p %Z')}.")
async def rename(self, ctx: commands.Context, oldimageName: str, newimageName: str): ''' renames an image in the favourites list ''' tokens = oldimageName.split("/") # split tokens fav = None if len(tokens) == 1: fav = collection("favouritensfw").find_one({ "server": ctx.guild.id, "imageID": tokens[0], "user": ctx.author.id, "category": "" }) elif len(tokens) == 2: fav = collection("favouritensfw").find_one({ "server": ctx.guild.id, "imageID": tokens[1], "user": ctx.author.id, "category": tokens[0] }) if fav: await self.unfavourite(ctx, oldimageName) await self.favourite(ctx, newimageName, fav["URL"])
def rebuild_user_profiles(guild: discord.Guild): ''' Rebuilds user profiles within a given server ''' logger.info(f"{guild.name}: Scanning for new members...") DB_members = collection("users").find({"server": guild.id}) member_ids = [m["user"] for m in DB_members] actual_member_ids = [m.id for m in guild.members] # Scan to add new for member in guild.members: if member.id not in member_ids: logger.info(f"New Member Found in {guild.name}") collection("users").insert_one(new_user(member)) # Scan to remove old for mem_id in member_ids: if mem_id not in actual_member_ids and \ cfg["Settings"]["development-mode"] == "False": logger.info(f"Deleting Old Member Found in {guild.name}") collection("users").delete_one({ "server": guild.id, "user": mem_id })
async def inject(ctx: commands.Context, name) -> discord.Emoji: ''' Attempts to inject image into the server's list of emoji, returning it afterward ''' if document := collection("emotes").find_one( {"name": name, "server": ctx.guild.id}): LIMIT = 50 # UNLOAD EMOJI if len(ctx.guild.emojis) >= LIMIT - 1: unload = ctx.guild.emojis[0] # emoji to be unloaded -- oldest one if not collection("emotes").find_one( {"name": unload.name, "server": ctx.guild.id}): # If not loaded, we must first database it # upload the about-to-be-destroyed emoji await upload(ctx, unload.name, unload.url, "inline") await unload.delete(reason=f"Unloading emoji to make space for {name}") # LOAD NEW EMOJI async with session.get(document["URL"]) as resp: content = await resp.read() return await ctx.guild.create_custom_emoji(name=document["name"], image=content, reason=f"Requested by user {ctx.author.display_name}")
async def deop(self, ctx, *, member: discord.Member): ''' de-OPs a member ''' collection("users").update_one(ufil(member), {"$set": {"OP": False}}) op_list.cache_clear() await ctx.send(f"{member.display_name} is no longer OP.")
def text_filter(content: str, author: discord.Member, guild: discord.Guild) -> bool: ''' Detects banned words in a string ''' spaced_query = unidecode.unidecode(emojiToText(content.lower())) # remove non-ascii, attempt to decode unicode, # get into formattable form query = re.sub(r"\W+", '', spaced_query) # spaces server_words: dict = collection( "servers").find_one(guild.id)["global-banned-words"] words: dict = collection( "users").find_one(ufil(author))["moderation"]["banned-words"] words.update(server_words) # TODO with python 3.9 -- dictionary union for w in words: if words[w] < 100 and ( len(query) > 2 and fuzz.partial_ratio(query, w) >= words[w]) or query == w: return True for word in spaced_query.split(" "): if word[0] == w[0] and word[-1] == w[-1] and fuzz.partial_ratio(word, w) >= words[w]: return True # checking endcap letters return False
async def on_ready(self): ''' Reconstruct poll listeners ''' coros = [] for poll in collection("polls").find(): logger.info(f"Found poll in channel: {poll['channel']}") channel: discord.TextChannel = self.bot.get_channel( poll["channel"]) try: msg = await channel.fetch_message(poll["_id"]) except: # msg not found collection("polls").delete_one(poll) logger.warn("Message deleted for poll, unloading") continue ctx = await self.bot.get_context(msg) author = msg.guild.get_member(poll["author"]) coros.append( self.listen_poll(ctx, msg, poll["timeout"], poll["prompt"], poll["options"], author)) await asyncio.gather(*coros)
async def poll(self, ctx: commands.Context, prompt: str, timeout: typing.Optional[int] = 60, *options): ''' Creates a poll, with an optional timeout. Specify a prompt, and then split options by spaces. Timeout less than zero will result in permenantly persistent polls. ex. `poll "apples or bananas?" "apples are better" "bananas are the best!"` ex. `poll "persistent poll" -1 "option 1" "option 2"` Polls automatically time out after 60 minutes of inactivity by default. ''' if len(options) < 36: lines = "\n".join( [f"{i+1}) {options[i]}" for i in range(len(options))]) e = discord.Embed(title=f"**__POLL__:\n{prompt}**", color=0xd7342a) for i in range(len(options)): e.add_field(name=f"{i+1}) {options[i]}: 0", value="No one", inline=False) e.set_author( name= f"{ctx.author.display_name}, react to this post with 🛑 to stop the poll.", icon_url=ctx.author.avatar_url) e.set_footer( text=f"Updated {local_time().strftime('%I:%M:%S %p %Z')}") msg = await ctx.send(embed=e) reacts = "123456789abcdefghijklmnopqrstuvwxyz" # Apply reactions for i in range(len(options)): await msg.add_reaction(textToEmoji(reacts[i])) await msg.add_reaction("🛑") collection("polls").insert_one({ "_id": msg.id, "channel": ctx.channel.id, "msg": msg.id, "prompt": prompt, "options": options, "timeout": timeout, "author": ctx.author.id }) logger.info("Storing poll...") await self.listen_poll(ctx, msg, timeout, prompt, options, ctx.author) else: await ctx.send( "Sorry, you can only choose up to 35 options at a time.")
async def optout(self, ctx: commands.Context): ''' Opts out from macros ''' collection("users").update_one({"user": ctx.author.id}, {"$set": { "macros": False }}) await ctx.message.add_reaction("ЪЉЇ")
async def removeannounce(self, ctx: commands.Context): ''' Removes an announcement from the announcement system ''' collection("announcements").delete_one({ "server": ctx.guild.id, "owner": ctx.author.id }) await reactOK(ctx) await self.build_announcement_cache() # rebuild cache
async def rebuild_weight_table(guild: discord.Guild): ''' Refills the daily member counts according to staleness rules. ''' # Attempt to set default daily count server_cfg: dict = collection("servers").find_one(guild.id) if server_cfg["default-daily-count"] == 0: # Daily member turned off for member in guild.members: collection("users").update_one(ufil(member), {"$set": { "daily-weight": 0 }}) return logger.warning( f"{guild.name}: Cache is being reconstructed. This will take a while.") # Process staleness staleness = server_cfg["daily-member-staleness"] if staleness >= 0: threshold = datetime.datetime.now() - \ datetime.timedelta(staleness) active_author_ids = set() for channel in guild.text_channels: active_ids_in_channel = await channel.history( limit=None, after=threshold).filter( lambda msg: msg.type == discord.MessageType.default).map( lambda msg: msg.author.id).flatten() active_author_ids.update(active_ids_in_channel) else: active_author_ids = set([member.id for member in guild.members]) for member in guild.members: if (not member.bot or not bool( cfg["Settings"]["exclude-bots-from-daily"]))\ and member.id in active_author_ids: daily = server_cfg["default-daily-count"] else: daily = 0 collection("users").update_one(ufil(member), {"$set": { "daily-weight": daily }}) logger.info( f"{guild.name}: Rebuilt weights for users in past {staleness} days")
async def threat(self, ctx, level: int, *, member: discord.Member): ''' sets a member's threat level ''' collection("users").update_one( ufil(member), {"$set": { "moderation.threat-level": level }}) threat_list.cache_clear() assert member.id in threat_list(ctx.guild.id, level) await ctx.send(f"{member.display_name} is now threat level `{level}`")
async def build_announcement_cache(self): await self.bot.wait_until_ready() for g in self.bot.guilds: additonal_announcements = [{ "server": g.id, "time": "08:00", "announcement": self.dailyannounce, "owner": None }] if d := collection("announcements").find({"server": g.id}): self.announcements[g.id] = list(d) + additonal_announcements else: self.announcements[g.id] = additonal_announcements
async def dailyannounce(self, channel: discord.TextChannel): ''' Daily announcement ''' m = await channel.send( "Good morning everyone! Today is " f"{local_time().strftime('%A, %B %d (Day %j in %Y). Time is %I:%M %p %Z')}. Have a great day." ) ''' Daily user ''' ctx = await self.bot.get_context(m) if luckyperson := channel.guild.get_member( await weighted_member_from_server(channel.guild)): collection("users").update_one(ufil(luckyperson), {"$inc": { "daily-weight": -1 }}) dailyrole = await role(channel.guild, "Member of the Day") for m in dailyrole.members: # remove bearer of previous daily role roles = m.roles roles.remove(dailyrole) await m.edit(roles=roles) roles = luckyperson.roles roles.append(dailyrole) await luckyperson.edit(roles=roles) await channel.send( f"Today's Daily Member is **{luckyperson.display_name}**") col = luckyperson.colour a = discord.Embed( color=col, title=f"{luckyperson.display_name}'s Avatar", url=str(luckyperson.avatar_url_as(static_format="png"))) a.set_image(url=luckyperson.avatar_url) await ctx.send(embed=a) general_cog = self.bot.get_cog("General") await general_cog.avatar(ctx, member=luckyperson) if not sum_of_weights(channel.guild): # rebuild if necessary await rebuild_weight_table(channel.guild)
def edge(arc: Edge, collection=None): ''' # motivation: "x , y < z" >>> save(',', 'x', 'y') >>> pp(db.find_one({'node': 'y'})) {'edges': [[',', 'x', 'y']], 'node': 'y'} >>> save('<', 'y', 'z') >>> pp(db.find_one({'node': 'y'})) {'edges': [[',', 'x', 'y'], ['<', 'y', 'z']], 'node': 'y'} >>> pp(db.find_one({'node': 'x'})) {'edges': [[',', 'x', 'y']], 'node': 'x'} >>> pp(db.find_one({'edge': ','})) {'edge': ',', 'nodes': [[',', 'x', 'y']]} >>> pp(db.find_one({'edge': '<'})) {'edge': '<', 'nodes': [['<', 'y', 'z']]} # hyperedge: "a < b where c" >>> save('< where', 'a', 'b', 'c') >>> pp(db.find_one({'edge': '< where'})) {'edge': '< where', 'nodes': [['< where', 'a', 'b', 'c']]} ''' if not collection: collection = db.collection() #TODO addToSet duplicates for vertex in arc.nodes: query = {'node': vertex} update = {'$addToSet': {'edges': arc.json}} collection.update(query, update, upsert=True)
def new_user(user: discord.Member): ''' Configures a new user for use, returns a dictionary ready to be updated ''' # Attempt to set default daily count server_cfg: dict = collection("servers").find_one(user.guild.id) if not user.bot or cfg["Settings"]["exclude-bots-from-daily"] == "False": daily = server_cfg["default-daily-count"] else: daily = 0 return { "user": user.id, "server": user.guild.id, "last-online": "Now" if str(user.status) == "online" else "Never", "OP": False, "daily-weight": daily, "notify-status": [], "identity": "", "guessing-game": { "highest-streak": 0 }, "moderation": { "stop-pings": False, "stop-images": False, "banned-words": {}, "kick-votes": [], "mute-votes": [], "threat-level": 0 }, }
async def createChannel(self, ctx: commands.Context, *, channelname: str): ''' Creates a channel, and gives the user who created it full permissions over it. If "custom-channel-group" is set in the server cfg, it will create the channel there, otherwise it will be the same category as where the command was called. ''' await ctx.trigger_typing() server_cfg: dict = collection("servers").find_one(ctx.guild.id) try: custom_group = server_cfg["channels"]["custom"] except Exception: if ctx.channel.category: custom_group = ctx.channel.category.id else: custom_group = 0 if custom_group: # put in specific category group = ctx.guild.get_channel(custom_group) chn = await group.create_text_channel(channelname) else: # put in outside chn = await ctx.guild.create_text_channel(channelname) await chn.set_permissions(ctx.author, manage_channels=True, manage_roles=True) await ctx.send(f"Channel has been created at {chn.mention}")
async def big(self, ctx: commands.Context, page=1): ''' Lists all big emotes in the server, based on page ''' paginator = commands.Paginator(prefix="", suffix="", max_size=200) bigemotes = collection("emotes").find( {"server": ctx.guild.id, "type": "big"}, {"name": True}).sort( "name", 1) if not bigemotes: return # empty for i in bigemotes: paginator.add_line(f"- {i['name']}") pages = paginator.pages def em_embed(pagenum): e = discord.Embed(color=0xd7342a, title=f"__**Big Emotes in {ctx.guild.name}**__") e.set_footer(text=f"({pagenum + 1}/{len(pages)})") e.description = pages[pagenum] return e await send_menu(ctx, [em_embed(num) for num in range(len(pages))])
async def addannounce(self, ctx: commands.Context, time, *, message): ''' Adds an announcement to the announcement system [24 hr time hh:mm]. Each user is allowed to have a maximum of one announcement. ''' collection("announcements").update_one( { "server": ctx.guild.id, "owner": ctx.author.id }, {"$set": { "time": time, "announcement": message }}, upsert=True) await reactOK(ctx) await self.build_announcement_cache() # rebuild cache
def get_all(self, collection="users"): collection_ref = db.collection(f'{collection}') docs = collection_ref.stream() arr = [] for doc in docs: print(f'{doc.id} => {doc.to_dict()}') arr.append(doc.to_dict()) return arr
def op_list(guild_id: int): ''' Returns list of OPS in a server ''' all_users = collection("users").find( {"server": guild_id, "OP": True}) return [u["user"] for u in all_users]
def threat_list(guild_id: int, threat_level: int): ''' Returns list of threats ids in a given server ''' all_users = collection("users").find( {"server": guild_id, "moderation.threat-level": {"$gt": threat_level - 1}}) return [u["user"] for u in all_users]
def rebuild_server_cfgs(guilds: list): ''' Rebuilds server configuration ''' logger.info("Scanning for new servers...") DB_guilds = collection("servers").find() guild_ids = [g["_id"] for g in DB_guilds] actual_guild_ids = [g.id for g in guilds] for guild in guilds: if guild.id not in guild_ids: logger.info(f"New Server Found: {guild.name}") collection("servers").insert_one(new_server(guild)) rebuild_user_profiles(guild) # check users too for g_id in guild_ids: if g_id not in actual_guild_ids and \ cfg["Settings"]["development-mode"] == "False": logger.info("Deleting Old Server & Members") collection("servers").delete_one({"_id": g_id}) collection("users").delete_many({"server": g_id}) logger.info("Server scan DONE")
async def stats(self, ctx: commands.Context): ''' Stats for guess ''' highscore = collection("users").find_one( ufil(ctx.author))["guessing-game"]["highest-streak"] currscore = self.streak[ctx.author.id] await ctx.send(f"**Info for {ctx.author.display_name}**\nCurrent Streak: {currscore}\nHighest Streak: {highscore}")
def tag_match(self, tag=[]): collection_ref = db.collection(u'users') query = collection_ref.where(u'tags', u'array_contains_any', tag) result = query.stream() arr = [] for r in result: # print(f'{r.id} => {r.to_dict()}') arr.append(r.to_dict()) return arr
def content_filter(message: discord.Message) -> bool: ''' Checks a message for additional offending characteristics based on member Filters pings and images ''' u = collection("users").find_one(ufil(message.author)) return (u["moderation"]["stop-pings"] and message.mentions) or ( u["moderation"]["stop-images"] and ( message.attachments or message.embeds))
async def on_member_join(self, member: discord.Member): ''' When a member joins the server. ''' announcements_channel_id = collection( "servers").find_one( member.guild.id)["channels"]["announcements"] if channel := member.guild.get_channel(announcements_channel_id): await channel.send(f"Welcome {member.display_name}!")