Issue storing user id's in a json file - python-3.x

So what I'm trying to do is to store user id's and add their score when they do a command. But the issue I'm getting is the user gets logged twice and it doesn't add the score. This is what happens in the json file:
{"272356126391238536": {"experience": 1}, "272356126391238536": {"experience": 1}}
This is a part of my code that defines the update and add:
async def update(amount, user):
if not user.id in amount:
amount[user.id] = {}
amount[user.id]['experience'] = 0
async def add(amount, user, exp):
amount[user.id]['experience'] += exp
This is the part in the command that the score updates from:
await update(buffer, ctx.message.author)
await add(buffer, ctx.message.author, 1)
When I change the ".id" to a ".name" It works fine but the issue with name is if a user changes his name his old score will be lost.

Make sure you use a ctx variable, so:
async def update(ctx, amount):
if not str(ctx.message.author.id) in amount:
amount[str(ctx.message.author.id)] = {}
amount[str(ctx.message.author.id)]['experience'] = 0
async def add(amount, user : discord.Member, exp):
amount[user.id]['experience'] += exp
This should work.

Related

Sync to Async Django ORM queryset foreign key property

Seemingly simple situation:
Django model has foreign key:
class Invite(models.Model):
inviter = models.ForeignKey(User, on_delete=models.CASCADE)
...
In async context, I do:
# get invite with sync_to_async decorator, then
print(invite.inviter)
Get async's favorite error:
You cannot call this from an async context - use a thread or sync_to_async
print(sync_to_async(invite.inviter)) # -> throws the same error
Sure, I can do:
#sync_to_async
def get_inviter(self, invite):
return invite.inviter
But, this is senile, if I have to do this for every queryset property call.
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
Yes, resolve the extra fields using select_related:
# Good: pick the foreign_key fields using select_related
user = await Invite.objects.select_related('user').aget(key=key).user
Your other string non-foreign such as strings and ints attributes should already
exist on the model.
Won't work, (although they feel like they should)
# Error django.core.exceptions.SynchronousOnlyOperation ... use sync_to_async
user = await Model.objects.aget(key=key).user
# Error (The field is actually missing from the `_state` fields cache.
user = await sync_to_async(Invite.objects.get)(key=key).user
Other examples for research
A standard aget, followed by a foreign key inspection yields a SynchronousOnlyOperation error.
I have a string key, and a ForeignKey user to the standard user model.
class Invite(models.Model):
user = fields.user_fk()
key = fields.str_uuid()
An example with alternatives that mostly don't work:
Invite = get_model('invites.Invite')
User = get_user_model()
def _get_invite(key):
return Invite.objects.get(key=key)
async def invite_get(self, key):
# (a) works, the related field is populated on response.
user = await Invite.objects.select_related('user').aget(key=key).user
async def intermediate_examples(self, key):
# works, but is clunky.
user_id = await Invite.objects.aget(key=key).user_id
# The `user_id` (any `_id` key) exists for a FK
user = await User.objects.aget(id=user_id)
async def failure_examples(self, key):
# (b) does not work.
user = await sync_to_async(Invite.objects.get)(key=key).user
invite = await sync_to_async(Invite.objects.get)(key=key)
# (c) these are not valid, although the error may say so.
user = await invite.user
user = await sync_to_async(invite.user)
# same as the example (b)
get_invite = sync_to_async(_get_invite, thread_sensitive=True)
invite = get_invite(key)
user = invite.user # Error
# (d) Does not populate the additional model
user = await Invite.objects.aget(key=key).user # Error
print(sync_to_async(invite.inviter)) # -> throws the same error
That's because it's equivalent to:
i = invite.inviter # -> throws the error here
af = sync_to_async(i)
print(af)
The correct usage is:
f = lambda: invite.inviter
af = sync_to_async(f)
i = await af()
print(i)
# As a one-liner
print(await sync_to_async(lambda: invite.inviter)())
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
(Disclaimer: Not tested in production.)
With nest_asyncio, you could do this:
def do(f):
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(f)())
print(do(lambda: invite.inviter))
Or take it even further:
class SynchronousOnlyAttributeHandler:
def __getattribute__(self, item):
from django.core.exceptions import SynchronousOnlyOperation
try:
return super().__getattribute__(item)
except SynchronousOnlyOperation:
from asgiref.sync import sync_to_async
import asyncio
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(lambda: self.__getattribute__(item))())
class Invite(models.Model, AsyncUnsafeAttributeHandler):
inviter = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
...
# Do this even in async context
print(invite.inviter)
Does something like this work? Instead of invite.inviter you do await async_resolve_attributes(invite, "inviter")
#sync_to_async
def async_resolve_attributes(instance, *attributes):
current_instance = instance
for attribute in attributes:
current_instance = getattr(current_instance, attribute)
resolved_attribute = current_instance
return resolved_attribute

Clear by user command does not clear all messages by user

I was trying to make a command that clears specified number of messages by a particular user, I am new to discord.py, my problem is that the command doesn't clear messages if any other user has sent a message/interrupted messages of the specified user.
Eg:
User 1: A
User 2: B
User 1: A
User 1: A
I try to clear 3 messages by user 1, but it clears only the last 2 and not the one that comes before User 2 sent a message. Here's my code:
#bot.slash_command(description='Clears messages by a specified user.')
async def user_clear(ctx,amount:int, member:discord.Member):
def check(message):
return message.author==member
await ctx.channel.purge(check=check,limit=amount)
await ctx.respond(f"Cleared {amount} messages by {member}!")
I'd appreciate any help!
Here's one which works:
#bot.command()
async def user_clear(ctx: commands.Context, amount: int, member: discord.Member = None):
counter = 0
async for message in ctx.channel.history(limit=100, before=ctx.message):
if counter < amount and message.author == member:
await message.delete()
counter += 1
await ctx.channel.send(f"Cleared {amount} messages by {member}!")

Creating several DB instances from a single POST request

I have a table like this:
class Mapping(db.Model):
map_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
bike_id = db.Column(db.String(255))
is_active_data = db.Column(db.Boolean, default=True)
created_by = db.Column(db.String(150))
updated_by = db.Column(db.String(150))
My POST method:
def save_MapperM(adddata):
create_data = Mapping(**adddata)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
Route:
#test.route("/addmapper"))
class MapperV(Resource):
#staticmethod
def post():
if request.method == 'POST':
save_data = request.get_json()
try:
return jsonify(save_MapperM(save_data))
except Exception:
return jsonify({'Unsuccessful': 'Looks like you missed something !!'})
Current Code :
The current code will take only one bike_id for every request.
Requirements:
I want to take multiple bike_id's as for one user id and store it as multiple records in the table level.
Example data format coming from UI:
{ user_id: 1, bike_id: 1,2,3,4 }
The easiest solution is to modify your save_MapperM function with a cycle:
def save_MapperM(adddata):
for bike_id in adddata["bike_id"]:
item_data = adddata.copy()
item_data["bike_id"] = bike_id
create_data = Mapping(**item_data)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
But be careful with this function as it allows to create Mapping instances with any parameters received from the POST request. It looks like it is intended but it can lead to security issues if your Mapping class has some private attributes which should be filled only on the server side. For example the user "Mike" can send a request:
{ "user_id": 1, "bike_id": [1, 2], "created_by": "Alex", "updated_by": "Alex" }
It will cause the save_MapperM function to create instances with created_by and updated_by values set to "Alex" which may not be true. It is better to get such attributes from the session data.
So your post method may look like this (post and save_MapperM functionality combined):
def post():
request_data = request.get_json()
for bike_id in request_data.get("bike_id", []):
item = Mapping(
user_id=request_data.get("user_id"),
bike_id=bike_id,
created_by=session.get("username"),
updated_by=session.get("username"),
)
db.session.add(item)
try:
db.session.commit()
except Exception:
return jsonify({"success": False})
return jsonify({"success": True})
The next step may be implementing request JSON data validation. It is OK when you have a couple of JSON keys with a simple structure but when you need to pass lots of data you need to be sure it is correct. You can use some of the serialization/ODM libraries for this Marshmallow for example.

discord.py get member instance from name#discriminator

I have a string with the name of an user user = 'somename#1111' how can I mention him? (I get the name from google sheets that's why it's a string)
I tried with:
#client.command()
async def mention(ctx):
user : discord.Member = 'somename#1111'
await ctx.send(f"{user.mention} hello!")
But it doesn't work
Although this method is valid, I'd recommend switching to saving user IDs instead of usernames + discriminators, as user IDs are constant, but users are able to change their names and discriminators.
#client.command()
async def mention(ctx):
user = discord.utils.get(ctx.guild.members, name="somename", discriminator="1111")
await ctx.send(f"{user.mention} hello!")
And with the ID:
#client.command()
async def mention(ctx):
user = ctx.guild.get_member(112233445566778899) # replace user ID there
try:
await ctx.send(f"{user.mention} hello!")
except:
await ctx.send("Sorry, that user has left the server!")
References:
utils.get()
User.discriminator
Guild.get_member()
Guild.get_member_named() - "Returns the first member found that matches the name provided."
I haven't used user.mention, but what I do is I get the user's id, then mention them like this:
await ctx.send(f'<#!{user.id}> hello!')

Is There A Way i Can Make A Auto-Warn And Auto-Kick Thing

Error: No, because i haven't made the code/ i don't know how to make the code, the code is below
#bot.event
async def on_message(message):
test = 'test'
if test in message.content.lower():
await message.author.send('you have been warned for using "bad words" in the server')
I want my code to warn the user 3 times then in the 4th time the bot kicks the user for writing test in the the server, is that possible?
You need to create a map of user ids to the number of times they have been warned. The easiest way to persist this map while the bot is offline is to store it in a JSON file, though as your bot scales you should eventually adopt a database.
from discord.ext.commands import Bot
from json import load
bot = Bot("!")
try:
with open("warns.json") as f:
warns = load(f)
except:
warns = {}
def should_warn(message):
return 'test' in message.content.lower()
async def warn_or_kick(member):
id = str(member.id)
if id not in warns:
warns[id] = 0
warns[id] += 1
if warns[id] >= 4:
await member.send("You have been kicked")
await member.kick()
else:
await member.send(f"This is warning number {warn[id]}")
with open("warns.json") as f:
dump(warns, f)
#bot.event
async def on_message(message):
if bot.user == message.author:
return
if should_warn(message):
await warn_or_kick(message.author)
else:
await bot.process_commands(message)
bot.run("TOKEN")

Resources