Message Counter for specific words discord.py - discord.py

I'm trying to build a message counter for discord.py that counts specific messages and then responds with the number of times the message was said in that day.
I have the base but I don't know how to build the actual counter... Here is my code:
import discord
from discord.ext import commands
import discord.utils
class Message_Counter(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_message(self, ctx, message):
if "oof" in message.content:
await ctx.send(str(counter))
elif "Thot" in message.content:
await ctx.send(str(counter))
def setup(client):
client.add_cog(Message_Counter(client))
Any help would be much appreciated. I'm using the rewrite branch of discord.py if that helps.
Basically for Thot it would respond with **Thot counter**: <number>
For oof it would respond with **oof counter**: <number>
so on so forth.
I would also like it to reset the counter on a daily basis so that around every 24 hours the counter starts over.

Using json (quick introduction to JSON here)
We want to create a json file with name counters.json in the same folder as the file(s) for your bot. Its contents should look like this:
{
"Thot": 0,
"oof": 0
}
Loading a json file into a dictionary works with the json library:
(If you have no idea what the "with open" stuff is about, here is a primer on file reading and writing operations)
import json
def load_counters():
with open('counters.json', 'r') as f:
counters = json.load(f)
return counters
Saving the dictionary back to json works in a very similar vein:
def save_counters(counters):
with open('counters.json', 'w') as f:
json.dump(counters, f)
Now that we have a way of loading and unloading our counters from json, we can change the bot code to use them:
import discord
from discord.ext import commands
import discord.utils
class Message_Counter(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_message(self, ctx, message):
if "oof" in message.content:
counters = load_counters()
counters["oof"] += 1
await ctx.send(str(counters["oof"]))
save_counters(counters)
elif "Thot" in message.content:
counters = load_counters()
counters["Thot"] += 1
await ctx.send(str(counters["Thot"]))
save_counters(counters)
def setup(client):
client.add_cog(Message_Counter(client))

Related

python asyncio class to create functions dynamically and execute with own interval parallel

I am trying to write a class that create methods dynamically that should executed parallel each with it's own duration with asyncio. But I am really new in the topic python asyncio and now on a point where I got stuck and have no idea how to go.
I collect servers with ip, port and command duration from config file and try to create methods in a loop and then gather these methods with async, here is my code:
import asyncio
from datetime import datetime
# from common.config import ConfigConstructor
class RCONserver:
def __init__(self, game: str, server_name=None):
self.game = game
self.server_name = server_name
# self.game_config = ConfigConstructor('cfg/rcon_server.yml')
async def send_rcon_command(self, ip: str, port: str, period: int, cnt: int):
await asyncio.sleep(int(period))
print(str(datetime.now()) + ": " + ip + " " + port)
def get_servers(self):
servers = []
for server in ['game1','game2']:
print(server)
if server[:4] == "game":
# s = self.game_config
# s.fetch_section(server)
# print(s)
servers.append(
self.send_rcon_command('192.168.178.1',
'30000',
300,
3)
return servers
async def main():
obj = RCONserver('game')
await asyncio.gather(*obj.get_servers())
asyncio.run(main())
The code is running but only one time for each server in the yml File.
What do I have to do to run it periodically for the given parameter watch period?
i think this should do the trick with loop and gather i can create functions dynamically and run each with it's own interval parallel:
import asyncio
from datetime import datetime
import random
class RCONServer:
def __init__(self):
self.rcon_loop = asyncio.get_event_loop()
def dt(self):
return datetime.now().strftime("%Y/%m/%d %H:%M:%S")
def build_rcon_functions(self):
rcon_servers = []
for server in ['game1','game2']:
rcon_servers.append(
self.rcon_command(server,
"192.168.0.1",
"30000",
"some_password",
random.randint(5, 10)
)
)
return rcon_servers
async def rcon_command(self, server: str, ip: str, port: str, passwd: str, interval: int):
while True:
await asyncio.sleep(int(interval))
print(self.dt(), ">", server)
async def run_loop(self):
rcon_tasks = self.build_rcon_functions()
try:
print(self.dt(), "> Start")
await asyncio.gather(*rcon_tasks)
self.rcon_loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print(self.dt(), "> End")
self.rcon_loop.close()
obj = RCONServer()
asyncio.run(obj.run_loop())
Some suggestions for optimizing? or some hints how it can be solved better?

Create an async generator "from scratch"

Most of the time I am using asyncio API. But how would I create an async generator "from scratch"?
Say I have a classic generator, and I want to make it async. How can I do that?
I naively thought that I could do something like below, but it does not run asynchronously (the 3 "for loops" are running one after the other, instead of concurrently). My hope was to make some_loop() asynchronous by calling it from some_async_loop() and releasing the event loop after each iteration with asyncio.sleep(0):
#!/usr/bin/env python3
import asyncio
async def run():
task = asyncio.ensure_future(run_async_loop())
asyncio.ensure_future(run_async_loop())
asyncio.ensure_future(run_async_loop())
await task
async def run_async_loop():
async for i in some_async_loop():
print(i)
async def some_async_loop():
for i in some_loop():
yield i
asyncio.sleep(0)
def some_loop():
for i in range(10):
yield i
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
I'm glad the fixing the call to asyncio.sleep() got things running.
I'm concerned partly about your run() function, because you're only await-ing on the first task, which means your code could exit before the other tasks are complete. I would suggest this:
async def run():
tasks = [run_async_loop(), run_async_loop(), run_async_loop()]
await asyncio.gather(*tasks)
I think you can also simplify your __main__ block:
if __name__ == '__main__':
asyncio.run(run())

Snipe command in cogs discord,py

I am reworking my discord bot with cogs and I'm stuck on the snipe function:
import discord
from discord.ext import commands
class Snipe(commands.Cog):
def __init__(self, client):
self.Client = client
#commands.Cog.listener()
async def on_ready(self):
print ("Snipe is now enabled")
async def on_message_delete(self, message):
messageauthor = {}
messagecontent = {}
messageauthor[message.channel.id] = message.author
messagecontent[message.channel.id] = message.content
#commands.command()
async def snipe(self, ctx):
channel = ctx.channel
try:
em = discord.Embed(description = f"said:\n{ctx.messagecontent[channel.id]}", color = 0x00c230)
em.set_author(name = f"Last deleted message in #{ctx.channel.name}")
em.set_thumbnail(url="https://cdn.discordapp.com/avatars/352793093105254402/8a2018de21ad29973696bfbf92fc31cd.png?size=4096")
em.set_footer(text = f"Snipe requested by {ctx.message.author}")
await ctx.channel.send(embed = em)
except:
embed = discord.Embed(colour = 0x00c230)
embed.set_author(name=f"There are no deleted messages in #{ctx.channel.name}!")
embed.set_thumbnail(url="https://cdn.discordapp.com/avatars/352793093105254402/8a2018de21ad29973696bfbf92fc31cd.png?size=4096")
embed.set_footer(text=f"Snipe requested by {ctx.message.author}")
await ctx.channel.send(embed=embed)
def setup(client):
client.add_cog(Snipe(client))
This is my code so far, this is the part that doesn't seem to work:
#commands.command()
async def snipe(self, ctx):
channel = ctx.channel
try:
em = discord.Embed(description = f"said:\n{ctx.messagecontent[channel.id]}", color = 0x00c230)
em.set_author(name = f"Last deleted message in #{ctx.channel.name}")
em.set_thumbnail(url="https://cdn.discordapp.com/avatars/352793093105254402/8a2018de21ad29973696bfbf92fc31cd.png?size=4096")
em.set_footer(text = f"Snipe requested by {ctx.message.author}")
await ctx.channel.send(embed = em)
When I try to snipe a deleted message it just shows the text for the "no messages to snipe" option.
If anyone can help with this/has their own code, edit it as much as you want ill work around that,
Thanks!
Well, in python, if you assign a variable in a function, it will be considered as a local variable. To make it global, you will need to use the global keyword. but since you are coding in a class, you can assign the variable to the class, like:
# to assign
self.messageauthor = {}
# to use
print(self.messageauthor)
So your new code will be:
#commands.command()
async def snipe(self, ctx):
channel = ctx.channel
try:
em = discord.Embed(title="Put something here", description = f"said:\n{ctx.messagecontent[channel.id]}", color = '0x00c230') #color code should be a str
em.set_author(name = f"Last deleted message in #{ctx.channel.name}")
em.set_thumbnail(url="https://cdn.discordapp.com/avatars/352793093105254402/8a2018de21ad29973696bfbf92fc31cd.png?size=4096")
em.set_footer(text = f"Snipe requested by {ctx.message.author}")
await ctx.channel.send(embed = em)
except:
embed = discord.Embed(title="", description="", colour = '0x00c230') # should be str
embed.set_author(name=f"There are no deleted messages in #{ctx.channel.name}!")
embed.set_thumbnail(url="https://cdn.discordapp.com/avatars/352793093105254402/8a2018de21ad29973696bfbf92fc31cd.png?size=4096")
embed.set_footer(text=f"Snipe requested by {ctx.message.author}")
await ctx.channel.send(embed=embed)
Also, title and description are required arguments to make an embed. and, the html color code should be a str. (Use ''). I hope this much helps you.

How to find the prefix of the bot inside a Cog when storing prefix in a json file

I want to have my Discord bot reply to a ping. And I would like to add the prefix of the bot along with the reply.
#commands.Cog.listener()
async def on_message(self, message= str):
with open('prefixes.json', 'r') as f:
prefixes=json.load(f)
prefix=prefixes[str(self.guild.id)]
#reply to a ping
if self.bot.user.mentioned_in(message):
await message.channel.send(f'Hey there, my prefix is `{prefix}`. Please enter `{prefix}help` for a more detailed overview!')
The error I'm getting:
AttributeError: 'Events' object has no attribute 'guild'
#commands.Cog.listener()
async def on_message(self,message):
with open('prefixes.json', 'r') as f:
prefixes = json.load(f)
prefix = prefixes[str(message.guild.id)]
if self.bot.user.mentioned_in(message):
await message.channel.send(f'My prefix is `{prefix}`')
Your mistake was to use self.guild.id. In this case it's message.guild.id.
You also don't need to assign a value to the variable message.
I hope this helped

I am new to python and i am trying to create a leaderboard

I would like to, as an admin, add points to a specific member's balance
Know how to create a JSON file with all the "points" someone has.
import discord
from discord.ext import commands
import random
import os
import json
# this line of code gives the bot a comand prefix
bot = commands.Bot(command_prefix="/")
# This line of code tells the bot to start up
#bot.event
async def on_ready():
print("The bot is now online!")
#bot.command(pass_context=True)
async def leaderboard(ctx):
await ctx.send("This is a work in progress, this will dieplay the leaderboard")
amounts = {}
#bot.command(pass_context=True)
async def balance(ctx):
id = str(ctx.message.author.id)
if id in amounts:
await ctx.send("You have {} ben points".format(amounts[id]))
else:
await ctx.send("You do not have an account")
#bot.command(pass_context=True)
async def register(ctx):
id = str(ctx.message.author.id)
if id not in amounts.keys:
amounts[id] = 100
await ctx.send("You are now registered")
_save()
else:
await ctx.send("You already have an account")
#bot.command(pass_context=True)
async def transfer(ctx, amount: int, other: discord.Member):
primary_id = str(ctx.message.author.id)
other_id = str(other.id)
if primary_id not in amounts:
await ctx.send("You do not have an account")
elif other_id not in amounts:
await ctx.send("The other party does not have an account")
elif amounts[primary_id] < amount:
await ctx.send("You cannot afford this transaction")
else:
amounts[primary_id] -= amount
amounts[other_id] += amount
await ctx.send("Transaction complete")
_save()
def _save():
with open('amounts.json', 'w+') as f:
json.dump(amounts, f)
#bot.command()
async def save():
_save()
#This line of code tells the bot to run
bot.run("Token")
I am not sure on what I am meant to do from here.
I might be over complicating the code if anyone can make it more efficient I will be incredibly grateful.
Here's the essential usage and everything you'll need to know for the basics of JSON:
# Creating a dictionary with some values
>>> data = {"foo": "bar", "key": "value"}
# Opening a file and writing to it
>>> with open("db.json", "w+") as fp:
... json.dump(data, fp, sort_keys=True, indent=4) # Kwargs for beautification
# Loading in data from a file
>>> with open("db.json", "r") as fp:
... data = json.load(fp)
# Accessing the values
>>> data["foo"]
'bar'
>>> data["key"]
'value'
This can be adapted to suit your needs, perhaps something like:
# Let's say the JSON has the following structure:
# {"users": {112233445566778899: {"points": 0}, 224466881133557799: {"points": 0}}}
# The "users" key is a bit redundant if you're only gonna store users in the file.
# It's down to what you'll be storing in the file and readability etc.
import json
import random
import typing
def add_points(member_id: str, amount: int):
# Open the file first as to load in all the users' data
# Making a new dict would just overwrite the current file's contents
with open("file.json", "r") as fp:
data = json.load(fp)
data["users"][member_id]["points"] += amount
# Write the updated data to the file
with open("file.json", "w+") as fp:
json.dump(data, fp, sort_keys=True, indent=4)
return data["users"][user_id]["points"]
#bot.command()
async def give(ctx, member: typing.Optional[discord.Member]=None, points:typing.Optional[int]=None):
if not member:
member = ctx.author
if not points:
points = random.randint(10, 50)
# Have a separate getter for the points - this was just for the sake of concise code
total_points = add_points(member.id, points)
await ctx.send(f"Successfully gave {member.mention} {points} points! Total: {total_points}")
I'll be happy to clarify anything if need be.
References:
Dictionary exercises - Might be worth taking a look into so then you're comfortable with how they work.
f-Strings - Python 3.6.0+
json.load()
json.dump()
typing.Optional
User.id
commands.Context
Context.author

Resources