Created
October 9, 2021 22:47
-
-
Save nonchris/95c07892bdcc4d8921588b803731cfe2 to your computer and use it in GitHub Desktop.
Discord-Bot to counts messages in a channel that fulfill certain requirements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
import os | |
import re | |
from typing import Union, Dict | |
import discord | |
import discord.errors as d_errors | |
from discord.ext import commands | |
""" | |
This bot is based on a template by nonchris | |
https://github.com/nonchris/discord-bot | |
""" | |
intents = discord.Intents.all() | |
PREFIX = "c!" | |
# inspired by https://github.com/Rapptz/RoboDanny | |
# This function will be evaluated for each message | |
# you can define specific behaviours for different messages or guilds, like custom prefixes for a guild etc... | |
def _prefix_callable(_bot: commands.Bot, msg: discord.Message): | |
user_id = _bot.user.id | |
# way discord expresses mentions | |
# mobile and desktop have a different syntax how mentions are sent, so we handle both | |
prefixes = [f'<@!{user_id}> ', f'<@{user_id}> '] | |
if msg.guild is None: # we're in DMs, using default prefix | |
prefixes.append(PREFIX) | |
return prefixes | |
# TODO: This would be the place to add guild specific custom prefixes | |
# you've got the current message hence the guild-id which is perfect to store and load prefixes for a guild | |
# just append them to base and only append the default prefix if there is no custom prefix for that guild | |
prefixes.append(PREFIX) | |
return prefixes | |
bot = commands.Bot(command_prefix=_prefix_callable, intents=intents) | |
# login message | |
@bot.event | |
async def on_ready(): | |
"""! | |
function called when the bot is ready. Emits the '[Bot] has connected' message | |
""" | |
print() | |
member_count = 0 | |
guild_string = "" | |
for g in bot.guilds: | |
guild_string += f"{g.name} - {g.id} - Members: {g.member_count}\n" | |
member_count += g.member_count | |
print(f"Bot '{bot.user.name}' has connected, active on {len(bot.guilds)} guilds:\n{guild_string}") | |
await bot.change_presence( | |
activity=discord.Activity(type=discord.ActivityType.watching, name="Counting images...")) | |
def extract_id_from_string(content: str) -> Union[int, None]: | |
"""! | |
Scans string to extract user/guild/message id\n | |
Can extract IDs from mentions or plaintext | |
@return extracted id as int if exists, else None | |
""" | |
# matching string that has 18 digits surrounded by non-digits or start/end of string | |
match = re.match(r'(\D+|^)(\d{18})(\D+|$)', content) | |
return int(match.group(2)) if match else None | |
@commands.has_permissions(administrator=True) | |
@bot.command("count-images", aliases=["ci"]) | |
async def count_images(ctx: commands.Context, *limit): | |
# we wanna give the option to set different limits | |
if limit: | |
try: | |
limit = int(limit[0]) | |
except TypeError: | |
limit = 500 | |
else: | |
limit = 500 | |
# dict holding members and their count of found memes | |
count_dict: Dict[discord.Member, int] = {} | |
guild: discord.Guild = ctx.guild # here for convenience | |
# message we send and edit as status update | |
status_message = await ctx.send("This might take a while") | |
async with ctx.channel.typing(): # show that bot is doing stuff | |
i = 1 # count how much work is done | |
async for m in ctx.channel.history(limit=limit): | |
message: discord.Message = m # we like it when the IDE knows what we're doing | |
# check if attachment or embed exists as an indicator for a send meme | |
# increase counter by one | |
if message.attachments or message.embeds: | |
try: | |
count_dict[guild.get_member(message.author.id)] += 1 | |
except KeyError: | |
count_dict[guild.get_member(message.author.id)] = 1 | |
# fun to the terminal | |
print(f"{message.author}:\n{message.content}") | |
print(f"{message.embeds=}") | |
print(f"{message.attachments=}") | |
print() | |
# occasional status post | |
i += 1 | |
if i % 10 == 0: | |
await status_message.edit(content=f"Status:\nCounted {i} / {limit} messages") | |
# time.sleep(0.2) # we don't wanna get banned or rate-limited by discord - do we? | |
# convert dict to list and sort it so that member with highest value is first | |
list_of_values_sorted = sorted([itm for itm in count_dict.items()], key=lambda x: x[1], reverse=True) | |
# build nice strings and join them | |
display_string = "" | |
for itm in list_of_values_sorted: | |
try: | |
display_string += f"{itm[0].display_name}: {itm[1]}\n" | |
except AttributeError: # there are members that decide to leave servers - can you imagine?! | |
pass | |
# prepare and send embed | |
if not display_string: | |
display_string = "No one found" | |
emb = discord.Embed(title="Who sent the most links / attachments?") | |
# replace markdown symbols | |
emb.add_field(name="The winner is:", | |
value=display_string.replace('|', '\|').replace('*', '\*').replace('_', '\_').replace('`', '\`')) | |
emb.set_footer(text="The counts only count attachments and links, not further checks are done!\n" | |
"Members who left the server are not counted.") | |
# make file to send too | |
with open("result.txt", "w") as f: | |
f.write(display_string) | |
try: | |
await ctx.send(embed=emb, files=[discord.File(open("file.txt"), filename="result.txt")]) | |
# when field value limit is reached | |
except d_errors.HTTPException: | |
await ctx.send("Too long to display", files=[discord.File(open("file.txt"), filename="result.txt")]) | |
# lets run this! | |
bot.run(os.getenv("TOKEN")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment