Skip to content

Instantly share code, notes, and snippets.

@nonchris
Created October 9, 2021 22:47
Show Gist options
  • Save nonchris/95c07892bdcc4d8921588b803731cfe2 to your computer and use it in GitHub Desktop.
Save nonchris/95c07892bdcc4d8921588b803731cfe2 to your computer and use it in GitHub Desktop.
Discord-Bot to counts messages in a channel that fulfill certain requirements
#!/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