#dev-contrib
1 messages · Page 2 of 1
There's nothing else relevant here, and running the command again was fine
!docs cleardoccache discord.py
I think I don't have discord.py as a symbol
hmmmmm
I can't repo that error
I wonder if the warning log is being treats as an error for some reason
oh
it's just a warning with stack info
I have a feeling it has to do with the ratelimit because that's what it's right beside, but I don't know how to reproduce a ratelimit
cool
Another unrelated one, I'm pretty sure this was already there. Just need to remember to fix it
Traceback (most recent call last):
File "C:\Users\hassa\AppData\Local\pypoetry\Cache\virtualenvs\bot-QP3Q7ay1-py3.10\lib\site-packages\discord\ext\tasks\__init__.py", line 239, in _loop
await self.coro(*args, **kwargs)
File "C:\GitHub\PyDis\bot\bot\exts\fun\off_topic_names.py", line 56, in update_names
channel_0_name, channel_1_name, channel_2_name = await self.bot.api_client.get(
ValueError: not enough values to unpack (expected 3, got 0)
I don't have any OTs in the DB
There's a good chance I wrote that PR
It's been so long so I opened the bot lol
How did the expire in docs work before you converted it to an int
was it an int in 3.9
doesn't look like it
it's a new thing in aioredis 2.X
if it's a float, it assumes you're giving it ms
Yikes
so *1000
I'm collecting any undocumented changes and I'm trying to get them to document them
This is helpful
yea, the aioredis 1.3 -> redis-py migration is rough
aioredis/client.py lines 1848 to 1855
def expire(self, name: KeyT, time: ExpiryT) -> Awaitable:
"""
Set an expire flag on key ``name`` for ``time`` seconds. ``time``
can be represented by an integer or a Python timedelta object.
"""
if isinstance(time, datetime.timedelta):
time = int(time.total_seconds())
return self.execute_command("EXPIRE", name, time)```
We could in theory just pass the TD
Where does it multiply by a 1000
Seems to have been the same behavior since the file was created
hmmmm
I found a reason for it
since passing a float would give out of range errors
we could pass it as a dt actually, yea
this is aioredis fwiw, not redis-py
not that there's much difference, just extra options
The redis-py implm looks like this too, just with the added options which don't apply to us anyway
yea, can't find the page in my history too
Don't worry, it's fine
Is using variables from outside a nested function not allowed
bugbear is warning against https://docs.python-guide.org/writing/gotchas/#late-binding-closures
I feel like that'd produce a lot more false negatives than positives, but I guess that's what bug bear is about
This PR is a lot smaller than I expected
How did you even find those lol
They moved the docs to a completely different, very ugly page
went through browser history around when I made the commit
but yea, this isn't applicable anymore
So this is removed behavior?
we use the seconds elsewhere in that file, so I'm tempted to leave it as-is
or make it a float again
It's fine as is
Yea, I think it is
most of it is tests
like kaizen fixes of tests
because I was annoyed at warnings
It is very clean now, I'll give you that
yea it's nice
Reading it, it says that floats are converted to milliseconds, and passed to a function which does use milliseconds, so I don't think it's changed behavior. It's weird however you were getting range errors
What we have now is correct, so I'll just drop it
With that, I go to bed
Thanks Chris
Here's your reminder: How do you tie a reminder to a replied message like that
[Jump back to when you created the reminder](#dev-contrib message)
why python discord bots are using discord.py (no offense just asking reason 🙂 )
Back when they were written it was only prominent framework for python bots
ok
Here's your reminder: How do you tie a reminder to a replied message like that
[Jump back to when you created the reminder](#dev-contrib message)
Is making a command to display open github issues not something worth it?
To list all open issues?
That would be a lot of issues depending on the repo
Oh I meant unassigned ones my bad
If there are alot, maybe just the most recent x ones
Why do you want to do that in Discord?
fair, initially i thought it may help when you want to contribute especially to ones doing it for the first time but nevermind
i think the idea has some merit. a lot of us have the repos already in our browser history or bookmarked, but a command that fetches issues based on approved but waiting assignment, or up for grabs might be a quick easy way to show a new contributor what to look for
there may be other use cases as well internally like for internal org or admin repos. or fetching stale issues from meta
actually probably wouldn't be able to use it for private repos, ig
what about this issue https://github.com/python-discord/sir-lancebot/issues/1023
is the user who said they want to work on it still going to be waited on, or can I request to do it?
ig you could request to be assigned seeing as there ware no comments after that
okay, done
For future reference, issues have to be approved before anyone is assigned. You can ask us to take a look if it’s not been approved yet. Anyway I’ve gone ahead and approved and assigned tou
thanks
When I forked sir lancebot, I get this pop up saying it's not protected. Just to make sure, i should click it right?
well that's just your own branch since you forked it
Also what happened to the embeds?
the branch protection rules aren't copied over
you probably don't have to protect it unless you want to PR to your own fork before PRing to the pydis sirlancebot
(but don't PR to main on forks)
for https://github.com/python-discord/sir-lancebot/issues/1023, which category jokes should I use from the API? Allow the user to chose from 'neutral', 'chuck', 'all'?
Is this the pyjokes API you're referring to?
yeah
I'd say either let the user choose or "all"
At a quick glance none of them seem inappropriate
Maybe default to "all", but the user can specify if they wish to
Feel free to ping me for a review when you're done too btw
@surreal veldt
Eh I mean up to you really
I don't think it really needs to be one in this case
Especially if you're only displaying the joke
Lastly, is it worth making a class for the list of categories and handling if the input is not an option, then typehinting the argument in the command to the classs?
Eh could be nice to be more explicit on possible values
Although the typehint would be misleading
Since you'd be saying what you expect and not what it actually is
I think just doing JOKE_CATEGORIES = {"chuck", "neutral", "all"} at the top of the file and then checking if the category is in that set would be better (so a typehint of str) @surreal veldt
out of curiosity why a set?
Or since it's a cog self.joke_categories
Because it indicates you'll never have duplicates & it also has the benefit of faster in checks (the latter not really mattering but a nice bonus)
oh, if you're doing if x in y, the operation is fastest if y is a set?
Yeah
By quite a bit iirc
!timeit ```py
x = {"neutral", "chuck", "all"}
print("n" in x)```
@static canyon :white_check_mark: Your 3.11 timeit job has completed with return code 0.
500000 loops, best of 5: 649 nsec per loop
!timeit ```py
x = ["neutral", "chuck", "all"]
print("n" in x)
@static canyon :white_check_mark: Your 3.11 timeit job has completed with return code 0.
500000 loops, best of 5: 746 nsec per loop
So about 15% faster in sets than a list here
For the command though the time difference doesn't really matter because it's still "very quick", and is thus more of a bonus
yup so for a list it's O(n) average
if using a set, the in check is O(1) average
apparently there's a worst case of O(n) but I'm not sure how that happens
I guess when the item isn't in the set?
So I tried to test the code locally first but my bot isn't responding to me but it looks right so I'll just PR it
should it be in the fun.py cog?
.src fun
Your input was invalid: Unable to convert fun to valid command or Cog.
Usage:```
.source [source_item]
.src Fun
I think its own file might be better
just two functions is okay?
Depends on how many lines the code is
import discord
from discord.ext import commands
import pyjokes
class Jokes(commands.Cog):
def __init__(self, bot : commands.Bot) -> None:
self.bot = bot
self.JOKE_CATEGORIES = {"neutral", "chuck", "all"}
async def send_joke(self, ctx : commands.Context, *, category : str) -> str:
joke = pyjokes.get_joke(category=category)
await ctx.send(joke)
@commands.command()
async def joke(self, ctx : commands.Context, category: str = "all"):
if category not in self.JOKE_CATEGORIES:
raise commands.BadArgument(f"`{category}` is not a valid joke category")
await self.send_joke(category=category)
def setup(bot):
bot.add_cog(Jokes(bot))
Yeah that should be fine to go in the Fun cog
I'd change send_joke to _send_joke though to indicate it's a util
In fact I don't think we even need a separate function for it seeing as it's only 2 lines anyway
I'd keep it all in the command personally
It's not like we'll be running that code anywhere else
Also make sure you do the correct import order
Yeah fair, I'm just used to using functions for some reason
Don't think it's a big thing either way but generally functions are used to either prevent duplicate code (which won't happen here) or improve the flow of code (and I'd argue it doesn't help for just two lines)
import stdlib
import stdlib2
from stdlib3 import foo
import piplib
import pilib2
from piplib2 import bar
from local.package.module import spam```this is the correct order of imports (and blankline usage) for our projects btw
I.e. your from discord should go after import pyjokes
Otherwise it'll give a linting error
We also include docstrings for all of our commands so that they get a description in .help, so can you add one please -- something along the lines of py """Retrieves a joke of the specified `category` from the pyjokes api."""
@surreal veldt ^
Got it
@static canyon I made the PR https://github.com/python-discord/sir-lancebot/pull/1081
How do I link sir lancebot issues like how I do bot#1234?
sir-lancebot#1081
The format is organisation/repo#number
for python-discord repos, you can skip thee organisation
Also, the CI is failing cause it can't find the module pyjokes
you will need to add it to the pyproject.toml
poetry add "pyjokes=0.6.0" should do it.
Can I manually add it to the file?
Because when i run poetry commands it says Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases.
Do you have python installed?
Yeah
Cool, could you disable the shortcut as it says in the error?
Although I believe there's something with wrong with my vscode interpretor
Windows ships with a command python that when ran opens the windows store to Python install
which conflicts with running the normal python command on the command line
because if I do pip install something it says that it installed but when I click the run button in vscode, it says module not found error. However if I run the script with the command from terminal it works
Yea, that will likely be because pip is pointed at a different environment that python is
or at least the version of python your vscode is using
do you know how I can fix it?
have you done this?
yeah but then this PS C:\Users\tenuk\sir-lancebot> poetry lock 'python3' is not recognized as an internal or external command, operable program or batch file.
a new error = progress :D
now that you have removed that shortcut I recommend reinstalling poetry
with pip?
No, by following this https://python-poetry.org/docs/master/
Introduction Poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. Poetry offers a lockfile to ensure repeatable installs, and can build your project for distribution.
System requirements Poetry requires Python 3.7+. It ...
So i used curl -sSL https://install.python-poetry.org | python3 - --uninstall
are you using wsl?
and it says 'python3' is not recognized as an internal or external command, operable program or batch file
oh oops
using windows
Yea, then just use python not python3
also if I am using python 3.10, and just writing python in terminal says 3.9, that's bad right?
that means your python 3.9 install has priority over your python 3.10 install
you can change that by changing the order they appear in your PATH config
well it still says C:\Users\tenuk>python Python 3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
the PATH thing?
Yea
after you change your path you need to restart all command lines
so that your path is reloaded
i closed my command prompt and opened it again
Do you have vsc running?
nope
also, if it helps, the Desktop/Coding/python at the bottom in the screenshot is this. Does it mean it's py 3.9?
Move the 3.10 scripts file to the top as well
Yea, that's your python 3.9 install
Second to last here
how can i uninstall it?
The bots run on 3.9 anyway, why are we trying to get 3.10 to work 😅
You can uninstall it from command panel
Control panel*
Yea, having both installed is fine
oh my bad, this was more of a personal problem because my vscode interpretor seems off
once you install poetry and setup a venv for the bot, you can set vscode to use that anyway
Did you reinstall poetry yet?
yeah
Cool, open sir-lancebot in your command line
done
poetry env use C:\Users\tenuk\Desktop\Coding\python\python.exe
this will create a poetry env using your 3.9 path
okay - Creating virtualenv sir-lancebot-gb-kPGWv-py3.9 in C:\Users\tenuk\AppData\Local\pypoetry\Cache\virtualenvs Using virtualenv: C:\Users\tenuk\AppData\Local\pypoetry\Cache\virtualenvs\sir-lancebot-gb-kPGWv-py3.9
Package operations: 73 installs, 0 updates, 0 removals```
cool
that means your venv is ready
if you do ctrl+shift+p in vsc, it will open the quick menu
in there, search for select inte
oh by the way I didn't add pyjokes to the toml file
and click select python interpreter
then go to C:\Users\tenuk\AppData\Local\pypoetry\Cache\virtualenvs\sir-lancebot-gb-kPGWv-py3.9 and select python.exe
sorry what do I do?
oh okay i think i did it
it will open a file selector window
cool, in vsc do Terminal > open in terminal
do that activate the new venv?
i don't see open in terminal option
done
Did it run the activate script?
cool, cool, that's worked
now do I add pyjokes to the file?
you can do these things now
I also suggest running poetry run task precommit
to setup the pre-commit ilinting hooks
oh and also i forgot to create a new branch before making updates, is that bad?
which updates?
modifying the sir lancebot code
ahh right
what you can do is make a new branch now, with that commit in it
then switch back to main and reset it to upstream/main
if I proceed now, does it do anything bad though?
yeah
it'll mean that after your PR is merged, you'll need to reset your fork to upstream/main
how do i make a new branch with the commit I made in it? because I already PR'd it
(its worth making the change to a new branch if you haven't opened the pr yet)
(but if you've opened the pr just don't change it)
i only have to do the changes to the pyproject so I guess there's no point
i think i did it
sir-lancebot#1081
also, what happened to the embed? why is it bigger than it should be?
did you manually add it to the file?
anyway, you will need to run poetry lock to update the lock file and then push that
the lock file is what is used by poetry install the pyprojcet.toml describes what versions we are happy to use when creating the lock file
Ideally you use add, since lock and update like to bump every single dependency
But it’s not a big deal
Yea :D
I uses the command
Cool, if it's locked now, you can push the lock file and it'll pass CI
Maybe you just didn’t add the lock file to your commit?
There’s a file called poetry.lock in the repository which should have some changes
I need poetry lock for that?
git add poetry.lock
If you used the poetry add command, it’ll already have made the changes
basically stage the changed poetry.lock file
I do git add .
and was there a poetry.lock file in the staged changes?
Okay now it's done
@surreal veldt the pyjokes import still isn't in the right place. You've put it in the "stdlib" imports group, instead of the "pip" imports group
Also, can you add a description and add Closes #{issue_number} under the "Related Issues" header of the PR
And when you're committing things, ideally each commit should have a different name
now that you have poetry setup, you can run poetry run task lint and it will autofix everything it can
E.g.
"Add jokes command"
"Add pyjokes to pyproject.toml"
"Add pyjokes to poetry.lock"
That way it's easy to know what changes you made without having to even look
It's a bit late to change now so don't worry about it but something to consider for futute
so I get this error https://paste.pythondiscord.com/ezezanidaj
ahh got it my bad
so like this right? ```py
import json
import logging
import random
from collections.abc import Iterable
from pathlib import Path
from typing import Callable, Optional, Union
import pyjokes
from discord import Embed, Message
from discord.ext import commands
from discord.ext.commands import BadArgument, Cog, Context, MessageConverter, clean_content
from bot.bot import Bot
from bot.constants import Client, Colours, Emojis
from bot.utils import helpers
Try doing $env:SETUPTOOLS_USE_DISTUTILS = 'stdlib'
then running again
The problem and proposed workarounds are described here https://github.com/pre-commit/pre-commit/issues/2178#issuecomment-1002163763
i believe it's done now
Yup! looks good
Just had 1 comment as Discord.py has a built-in way to do the thing you were doing :D
Also, can you update your PR description to use the things it's asking for
Does lancebot have a number guessing game by the way?
like a higher or lower type game?
Yeah
Not as far as I know
but lance does have many things that I don't know about lol
Feel free to open an issue if you wanted to suggest it
Also mind elaborating on this?
In the PR template we include comments on things we want in the description
the bits between <!-- -->
we expect the description to be filled out as it mentions
Where do I find that?
Are you not logged in?
If you log in, it should show an edit button in the top right
it also shows this when you first open the PR
since it's easier here, hope it's okay, is this what I should do? ```py
async def joke(self, ctx: commands.Context, category: Literal["neutral", "chuck", "all"] = "all") -> None:
"""Retrieves a joke of the specified category from the pyjokes api."""
joke = pyjokes.get_joke(category=category)
await ctx.send(joke)
trying it now, although, i assume it'll raise a BadArgument which the bot will use in the error handler and send an embed giving the raw error right?
Yup :)
It'll raise BadLiteralArgument, but it'll still be handled
Yea, I think we handle all BadArgument subclasses the same
just by checking if they're an instance of BadArgument
Agree agree
okay done
What's this tatus file in the PR?
looks almost like a git status command but piped to a file
huhhh i did do git status may have accidentally being put to the file
wait, i'm not sure what did I do
is that a new file I made, can I delete it?
yup
do git rm tatus
this will delete the file from your system and also stage the file deletion, ready for you to commit the change
the joke command doesn't work?
.joke
It’s still deploying
You’ll see a message in #dev-log from the bot when it’s live
say, when I restart the bot container locally, why does it send the available help channels message again? unlike how it works here
we store those messages in redis
just curious
The current message is stored in redis
ah
Chris too quick
so if the redis storage is made persistent, it won't do that, right?
Yeah
cool
We tried fetching it, but it gets complicated because of the api and the other message in #❓|how-to-get-help
Something along those lines
It’s been a while
so the bot here never has never restarted?
It does restart, but it does use redis
So it can find the old one
Yep, although it seems pointless have a from discord.ext import commands when there's also a from discord.ext.commands import ...
Hey @vale ibex the deployment was skipped
I'd be inclined to remove the prior
The PR is merged 😅
Ah nvm then
actions shenanigans?
hey
Bad reviewers smh /s
sick
100% Chris
Lmao
will pusha commit
ddoesn't say why on desktop either
just that it was skipped
it even skipped a fresh commit on main that I just pushed
arthur deploy restart sir-lancebot
:white_check_mark: Restarted deployment sir-lancebot in namespace default.
This should re-pull latest too
It didn’t build so it won’t redeploy the image
was what renamed?
The lint action name
I mean from the API
It does some funky stuff to give it a url valid name
As opposed to the actual name
Ahh right
I mean it's just "Lint" so I'm not sure how badly you can mess that up
I can check after my call though
Alternative things to check is if these strings are still the ones used ```
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'
success was valid up until a couple days ago so I hope it’s not that lol
I imagine I'd see much more noise across the interwebs if they decided to change that lol
so this is a github issue?
Yup, we’re trying to figure it out
Well it's working on my fork
I think it's just github being github
Yeah seems like the API was returning malformed data for a bit
Chris are you re-running righ tnow
Who's doing that 👁️
Amazing that it'll happily say invalid value, but not what the value is
yea odd, usually that gets skipped if the build was skipped
Chuck Norris burst the dot com bubble.
@surreal veldt it shouldn't have taken this long, but it is live now. Thanks!
This is 90% of what it means to be devops lmao
very good timing on this Kat
just as cluster networking goes down lol
lmfaoooo
Shouldn't commands like the fun commands only be limited to #bot-commands?
Are they not?
No I dont think so
Could you show me an example of where it's not being blocked?
Well I didn't see it anywhere but I (personally) thought its something that would be done
Yea, it is done, we have a global check of sir lance
which is why I asked for an example, since it would be a bug if that's not happening
Is bot#2217 something that could be approved to work on?
Or is it still unsure what style we want to format it to?
I personally quite like the idea of replacing with a relative timestamp
I think @cold island had some opinions of replacing with discord timestamps because of how it renders on mobile?
Discord timestamps are a great idea which was terribly implemented
I think I agree with that as well yeah
Probably just working out the phrasing?
Mainly the relative timestamp's "in" sounds weird as it should really be "for duration"
Yeah I'm seeing the same thing, anyone know why this is happening?
What’s the issue?
Here's an embed from almost a month ago.
As far as I can tell..... They're exactly the same
The embed is like stretched out and does not at all look like what Shenanigans sent above (compare the ss sent by Raven and the one sent by Shenanigans)
This is not something from our side, maybe discord is changing the style
Oh alright
Discord mobile will literally not show you relative timestamps, it'll appear as dates instead
<t:1658961000:R>
Did they fix it? 🤔
Seems to work now?
on iOS and some android beta iirc
I don't have it on my old phone that's running android stable

DST? 
Timezones shouldn't affect absolute deltas lol
I think the platforms do different rounding
Yea, it's fun
it's technically 2 hours 55 mins as of right now
some platform implementation is rounding that to 2 I guess
lol yeah. I just saw an hour diff and thought DST. mb
I do not accept your apology, please have a 5000 word apology on my desk by Friday
thanks.
In any case, I think a pretty non-controversial way to deal with the issue raised is making the response say the intended duration
yeah I think that might work better for phrasing the response as well
Yea, I think saying the intendeed duration in static test is good
since we already have a timestamp for the expiry date anyway
i found this interesting, while playing around with editing infractions and using infraction resend
notice the new duration of 10 minutes and 19 seconds, because it took me 19 seconds to edit the infraction and resend it
would we just have it say 10 minutes going forward? (i think that's fine)
Yea, the duration should be the absolute time between creation and expiry
Currently I think the issue is the data type / record does not store the creation time / duration
It just stores the expiry time as far as I can see
so the human time rendering module has to calculate the time difference from expiry until now, which results in that literal duration
in the screenshot above the absolute time between creation and expiry is 10m19s. it's an edited infraction
if you didn't edit the duration, then the inserted_at time should still be the same
if you edit the duration, then I'd expect the inserted_at to be updated to the new time
inserted_at is the time in which the record was added
Change it to when the infraction is created/edited
So the bot tells site when it was created
that way we can calc the difference
Otherwise it will always be off, since expiry_date is calculated by bot, and inserted_date is set by postgres some time after
If we wanted that I'd rather have a duration field
we could add a created_at time
That's what inserted_at is though
inserted_at is when postgres inserts it currently
Duration field might be good. If only recording start and end time it will be off for DST switchover days for example.
all times are in UTC
Ah okay I see
So inserted_at will always have the slight difference to the actual creation time, so can't be used to calcualte the absolute delta
can't just the expiry_date be updated?
It just seems weird to me to add a new field and to start editing "inserted_at" just so that we could calculate the delta. Let's just store the delta
we can't calculate a duration without a start and an end
does that slight difference matter?
I'd mostly suggested to change inserted_at to be more like created_at
yes, it's what is this whole issue is trying to solve
uhh. mb again. what issue?
bot#2217
The infractions display being off slightly
Since the time calculation is not based on the actual sending time of the command
Just for context, it's not off, it says how much time is left
It's just that 99.99% of the time it's not useful info
since I'm not entirely sure what the purpose of inserted_at is currently
Since it sends it immediately
It tells us when the infraction was applied
📨 👌 applied mute to
@Examplefor 1 day until June 10, 2022 8:00 AM.
does this suggestion posted in that issue have any demerits?
if we stored the duration in the db, and didn't change how inserted_at works at all, then editing an infraction's duration after the fact would look odd,
Since the expiry is calculated on date infraction was edited+expiry
But when we resend an infraction the inserted_at date is what is used for the created
So the insertion time would still be the original insertion time, the duration would be the new duration and the expiry would be the time the infraction was edited+the duration
So the difference between expiry and inserted would be more than the absolute expiry
The resend doesn't say when it was applied
It just says duration and expiry
Ah right, so it would just be the moderation list commands that would be off then
Should we store when it was last edited?
This is the current state isn't it?
Yea
There was a whole project to redo the infractions table schema to include this kind of context, but it didn't take off
I think the issues still exist
somewhere deep under the pile of newer issues
hah
let me check
Would your suggestion be to store the current duration in a number of seconds?
yeeeah probably
There's a duration field type though I think in pg
I think we used it for the new filter schema
But yeah it basically gives you the duration in seconds
ah, does it use postgres's interval type?
I can... check
models.DurationField in django
I have no idea what it means for pg
pydis_site/apps/api/models/bot/filters.py line 40
infraction_duration = models.DurationField(```
A field for storing periods of time - modeled in Python by timedelta. When used on PostgreSQL, the data type used is an interval and on Oracle the data type is INTERVAL DAY(9) TO SECOND(6). Otherwise a bigint of microseconds is used.
Yea interval on postgres
nice
I think adding a duration field seems fine, maybe with a last_edited field too, that way we can solve the issue above
can be a separate thing if needed though
I think that calls for a bigger overhaul. The old plan was to have a command that gives more info beyond the summary in !i s
I am going to guess the schema rewrite was to add a whole audit log for all edits?
yep
oh, I just noticed that this is already for the infraciton duration
That's the filters
do we just, not do the issue above until the new filter schema?
Not infractions
yep
I guess we technically don't need to add an entry to the post call at all? The actual sending of the infractions message still has the original infraction dictionary
https://github.com/python-discord/bot/blob/4af4f0a083550386f781b7626f0f7a3d45934166/bot/exts/moderation/infraction/_scheduler.py#L119
bot/exts/moderation/infraction/_scheduler.py line 119
async def apply_infraction(```
if the infractions dictionary included the absolute time the command was called, it can be passed into time.format_with_duration for a (fairly) accurate duration calculation
Yea, that was my suggestion for a created_at field, to store when the infraction command was called, but that won't work for edited infractions without more fields/work
So currently the suggestion is to add a duration field to the infraction model, to store the current duration
I might have an idea for a new way to show infractions, will try to create a mock-up over the weekend
I think there's a bunch of suggesetions in #mod-meta from a while ago when this was last discussed 😅
yep ik
There's a thread I think
@vale ibex thinking about it some more... if we store the "last applied" date instead of duration that will also easily tell us whether it was edited
Yea, sorta what I was suggesting here
yeah i know haha
I think we could
that or "last edited" either is fine
and just initialise it with the command ran time initially
yeah
@fossil veldt are you familiar with django?
since this issue will include some site changes
nothing complicated, just a new field+migration
not really 😳
Are you interested in picking it up for this?
If not I can make the site changes for you
yeah um, that might be good 
Sure I can PR it in a sec
or could you walk me through the stuff I need to do
not really sure how the migration works for django 
python manage.py makemigrations and it just auto-creates the migrations by comparing the current models in code vs a full migrated db
site#751
All I did was make the change in the infraction model, then I started up the app so it created a fully migrated db
then I ran python manage.py makemigrations and it auto-generated the migration file
oh okay, so it made that 0084_infraction_last_applied.py file?
Yep
I remember it being pretty easy when I had to do it for the dm_sent field
I.e. site component of bot#1864
what procedure actually runs the migration file for the server?
Yea, django automatically runs the migrations on startup
ah okay that makes sense
so is this expected to be updated on edited infractions as well?
Yea
Ah, there is a problem here, and I'm not sure how to resolve it
The last_applied date we want to use is the same "now" date used to calculate the expiry
but that is created in the Duration converter
and not accessible by the command handler
We could change the "now" var in the convertor to now = datetime.now(timezone.utc).replace(microsecond=0) and then do the same in the very first line of the command
and hope that they don't cross a second barrier
any difference would cause the humanise function to return something "weird"
IE if the duration was 2hours, but we used a time that resulted in the infraction being saved with a duration of 1hr59m569s, then it would be shown like that to the user
Actually, something that would liekly be easier is change that converter to use the DurationDelta convertor instead
which returns the actual time delta
then we an do that logic in the command instead, and have access to everything we need
Nah, that's some custom convertors in bot.converters
that's what I'm thinking too. One caveat is that we can also specify the expiry in ISO
Yea, we'd need to Union with ISODateTime like the current Expiry type does
and then isinstance check to do the now+duration if needed
or calc the delta otherwise
mhm
so currently the Duration type is the datetime of expiry?
The Expiry convertor that is used by the infraction management commands is a union of Duration and ISODateTime both of which return a datetime object after converting
What we're suggesting is making a new Union ```py
InfractionExpiry = t.Union[DurationDelta, ISODateTime]
using this then we can do an isinstance check for relativedelta or datetime to see what the format is, and go from there
relativedelta -> we have the delta, and need to calc the expiry date
datetime -> we have the expiry date and need to calc the delta
Could you store both and calculate nothing
We are going to store both in the db
we need to calculate in the command though, since we only expect mods to give us one of the two
well, if it isn't your's, and it can't be mine, the mods are a good choice
I assume the conversion is done pretty early right, like in the command functions?
Yea
it's done before the command is called by d.py
while you're here site#751 thanks
very complicated django
How's this different from inserted_at
it will be set by the bot to when it was last edited
the default is just there for backwards compat
IE if an infraction is changed, last_applied will be updated to when that happened
Oh okay
so the duration+expiry makes sense
Can we make it default to the created column
Instead of now
So it’d be accurate retroactively
*me's
uhhhh, I'm pretty sure those are calcualted at the same time
well, not literally the same time ofc
we could set it to be server_default
and then it would be afaik
I mean set it for what the current values in the DB is
So an infrac from last week would get that column as last week
Should this get transferred back to the site repository? https://github.com/python-discord/api-old/issues/12
is bot not compatible with running on 3.10?
not until bot#2229 is merged
you don't need to have multiple poetry installs
Multiple?
if you have multiple python versions installed, poetry can work across them
you can do poetry env use path/to/python.exe
when creating the poetry env
and it will use that version to create the env
ooooh so that's where this was
can it be a pyenv install? Or does it not matter

I'd been looking for that several months ago
Added a commit to site#751
I think doing an update like this is fine
I think only bulk_updates need to have batch considerations right?
I've tested it, and it works
I'm just not sure about scalibility
the docs don't mention anything about worrying about batching
whereas they do for bulk_update
We can pull the prod database and run the migration, but we don’t have that much data
We’re on like 100K infracs?
I mean if it works on prod I don't care about more than that
Yeah lol
I'll test it out
Can Arthur trigger BB
yea
I'll just pull and push to my local postgres, rather than testing directly in prod
It's arthur cj trigger blackbox fwiw
cj for cronjob
I see
Yeah
Log in as me 👌
always do
meh
lol
lol it worked
took like .5 s
~65k infractions fwiw
so currently the existing records will be updated with a inserted_at field? What would the value be?
Existing infractions already have an inserted_at field
oh I mean the uh last_applied
we're adding a last_applied field, and setting that to be the same as inserted_at on upgrade
oh so the old last_applied records are just a duplicate of inserted_at?
so displaying old records should just use the last_applied record, which will just be slightly off but still work
It's just so that the bot doesn't need to do special handling of NULL values
It’s accurate too, since we don’t have any way to change it currently
So it’s not off at all
Assuming last applied is when it was last sent
it's not 100% accurate
since duration/expiry is calculated at command time
inserted_at is calculated as insertion time
it's why the DM messages are a few seconds off atm
well, a second off
I meant it’s accurate to what kind of value to expect
so it shows 1hr59m instead of 2h lol
But yeah it’d technically be a second off you troll
yea, so it will only really work for new infractions, which is fine since old infractions are only seen by mods
Once the site PR is merged, you can run docker compose pull to pull the latest site image locally
then you can use the new field
but new infractions must now supply the last_applied record always right, or it wil be null?
yea, new infractions, and infraction edits should set that
Who’s your lol
Right
until then we need the default otherwise any new infractions will error :D
Eh we don’t need to remove it
should inserted_at be a required argument for this then? https://github.com/python-discord/bot/blob/main/bot/exts/moderation/infraction/_utils.py#L78-L87
bot/exts/moderation/infraction/_utils.py lines 78 to 87
async def post_infraction(
ctx: Context,
user: MemberOrUser,
infr_type: str,
reason: str,
expires_at: datetime = None,
hidden: bool = False,
active: bool = True,
dm_sent: bool = False,
) -> t.Optional[dict]:```
and I'll try to trace every usage and update it to report last_applied
or it can be optional, and if None I'll calculate it in the post_infraction method
last_applied, not inserted, but yes
um
why does this have a * argument?
https://github.com/python-discord/bot/blob/main/bot/exts/moderation/infraction/infractions.py#L286
bot/exts/moderation/infraction/infractions.py line 286
async def note(self, ctx: Context, user: UnambiguousMemberOrUser, *, reason: t.Optional[str] = None) -> None:```
is it safe to add a last_applied positional argument here before the *?
should that be a part of the command's interface?
It's so the reason string doesn't need to be quoted when it has spaces
It affects the way d.py parses the arguments for the command
Yes you could add that as a positional arg but I share Numerlor's doubts.
Any idea why I'm getting this error when I try to start up Sir Lancebot?
I tried running pip install matplotlib cuz I didn't already have it installed but that didn't fix the error
So I'm not sure what's going on here
You mean by going to the URL of my forked repo and sync fork?
I did that and it didn't fix the error
No, that isn't what I meant. What you did was update what is on GitHub's servers. What you need to do is update your local files.
Though since your remote is now up to date you just need to use git pull to get those changes (assuming you have the main branch checked out already)
If that doesn't fix it then maybe you need to rebuild the container, though if you're using Docker Compose to run it, it should be using the source code you pulled from git already.
Yep, I ran docker-compose up --build and that worked. Thanks!
oops, did not realize that was a command 😅
got too carried away with find usages
If you weren't aware, anything with the commands.command or commands.group decorator is a dpy command
In this case it's just command because of the import at the top of the file is
yeah I'm just trying to figure out how to refactor the API for async def post_infraction and everything that uses it
bot#1951 did basically what you need to do here, just with dm_sent
Hopefully that helps a bit
Well mainly if I add last_applied as a positional argument, it changes thec all in 7 methods in infractions.py, as well as methods in bigbrother.py
E.g you'll need to add another parameter to post_infraction
and then some of those methods use **kwargs, so it actually goes further up the chain and affects probably 30 or so methods
That seems unlikely
Lemme start up my laptop and I'll have a proper look
well no it's not that much uh
lemme try to count again
so just the usage of that post function is 13 methods (including 4 in tests)
Right yeah, that seems correct
but then for something like apply_ban, it's also called itself by another 10 functions:
I'm thinkning you can just do```diff
async def post_infraction(
ctx: Context,
user: MemberOrUser,
infr_type: str,
reason: str,
expires_at: datetime = None,
hidden: bool = False,
active: bool = True,
dm_sent: bool = False,
-
last_applied: datetime = datetime.now(tz=timezone.utc)
) -> t.Optional[dict]:```
Then because it has a default you don't actually have to update the calls
You'll only need to change the edit_infraction command then, so that it updates last_applied
wait can I do that actually? 
I thought the value would only be called on definition?
!e
from datetime import datetime
def f(val = datetime.utcnow()):
print(val)
for i in range(10):
f()
@fossil veldt :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | 2022-07-28 07:01:37.170827
002 | 2022-07-28 07:01:37.170827
003 | 2022-07-28 07:01:37.170827
004 | 2022-07-28 07:01:37.170827
005 | 2022-07-28 07:01:37.170827
006 | 2022-07-28 07:01:37.170827
007 | 2022-07-28 07:01:37.170827
008 | 2022-07-28 07:01:37.170827
009 | 2022-07-28 07:01:37.170827
010 | 2022-07-28 07:01:37.170827
But you can probably do a partial or something
I could just leave it None and do an assignment to utcnow though
Does infraction_edit call post_infraction? Cause if not we don't even need to add last_applied to the parameters
uh 
We can just do last_applied = datetime.now(tz=timezone.utc) inside the function (assuming a from datetime import datetime, timezone import)
Since it's always going to be that
Yeah, it doesn't call it
well the idea is that the mute/ban infraction functions would now parse DeltaDuration instead of Duration currently. And then we calculate the old Duration based on utcnow() + DeltaDuration, and then send both the old Duration + the utcnow() we calculated. So the human-time parser has 2 values that definitely delta to the original delta
the mute/ban infraction functions would now parse DeltaDuration instead of Duration currently.
So this would be a change to the commands' duration typehint?
So Duration is the actual end point (e.g. 2022-07-28 10:30:00), and not the duration (e.g. 10 min)
apparently
which is kind of weird but uh
and DurationDelta would be a real duration
Is there a way to delete this from my repos? (I forked it)
Lemme take a look at the converters rq
class Duration(DurationDelta):
"""Convert duration strings into UTC datetime.datetime objects."""``````py
class DurationDelta(Converter):
"""Convert duration strings into dateutil.relativedelta.relativedelta objects."""```
Yeah, it seems that way
yeah the Duration does return now + delta
So we're instead gonna use DurationDelta, send that to Discord, and then send DurationDelta + utcnow() to the API as the expiry?
I mean honestly, I think if we just put the last_applied in the post function as datetime.utcnow() it probably won't be that bad
it's not exact as some amount of microseconds / milliseconds will have passed from the command call to the post call, but is a lot better than currently (which the delta is calculated after the server response)
you can delete the fork like you delete any repo
yeah
So where does last_applied come into this?
Rather, why is it needed?
well actually we record time.utcnow, that will be the last_applied, and then we calculate the end time using DurationDelta + last_applied
and then those 2 things gets sent to the API
and then the user message takes those 2 values, calculates the delta again (which should be exact this time)
Right, so the edit is also gonna have this process
In site are we now going to only store the duration, and not the end time then?
well without it the API response only has the expiry datetime, currently the message duration text is calculated using the current utc delta after getting the response (which might be several seconds)
so the proposal is to store the initial time (last_applied) and keep the current expiry time
so the duration wouldn't be stored but rather calculated from the 2 values...? 
Would that not just have the same issue?
Because you're still calculating from timestamps which are slightly off
Off by enough to make it that it'll be 1 second off, like it currently is
well they wouldn't be slightly off right? Because they are derived from the same initial place, we get the DurationDelta, and utcnow(), the sum of those is the expiry time, and utcnow() is recorded as the last_applied time
Right, ignore that actually
I was thinking it gets calculated from the typehint, but we're using DurationDelta instead now
So how does this work in infraction_edit?
The same?
I guess it would be
infraction_edit would just have to also send a last_applied I guess, but no duration
Yeah
so the new reported duration is the old expiry minus the new last_applied
That's just a literal API request -- await bot.api.patch(...)
Don't think there's a function
Since this is what I did for dm_sent
So the database has last_applied and expiry, but NOT duration, correct?
yeah
yeah
Yeah that sounds good
the only thing is if I make last_applied a positional requirement for the post call, it affects quite a lot of functions
(Note that it has to be now(tz=timezone.utc) and not utcnow() now though iirc)
even for stuff like notes and warnings with no duration, it would still have to now report a last_applied
Yeah, just define it inside the function
Is there a separate created field?
so I'd send the calculated value for duration usages like mute and ban, and just leave warnings / notes to use that default value?
I guess that's the PGSQL inserted_at (near enough)
Will we be keeping that?
Yes
Cool
I think so? Last was discussing it here
#dev-contrib message
if we do remove it we'll have to make sure to send a last_edited for all new calls, since that'll be used for duration calculations 
Wouldn't it make more sense (and be easier) to set this new field in the server side? Or is it ambiguous when it should be set?
I think we are keeping inserted_at
well ideally we would just send last_edited for all calls, since the server will be a bit later than the original utcnow() called in the bot
Surely you'd have to report last_applied to the API for all infractions?
but um
yes. So if we do remove the default then all infractions will need to report last_applied
But I'm saying don't have it as a parameter, just define it inside
I was just saying less duration-sensitive stuff like warnings and notes can use the default calculated in the post infractions function, instead of being sent in
Since it'll be the same value for ALL infractions -- datetime.now(tz=timezone.utc)
oh do you mean we send the DurationDelta to post infractions now?
because currently we are sending the Duration end time
post_infraction doesn't send the message to Discord if you're thinking that it does
notify_infraction sends the DM to the user infracted
yeah I knew that part, it's just, it needs to send the correct durations to the API because the notify comes from the Infraction awaited from the API response?
And apply_infraction sends the message to pydis
Does that sort of inaccuracy really matter? After all, inserted_at is set server side currently. I believe the interface becomes more complicated and prone to mistakes if the value comes from the client, especially if it's not marked as a required field.
so currently for a tempmute flow:
@command(aliases=["mute"])
@ensure_future_timestamp(timestamp_arg=3)
async def tempmute(
self, ctx: Context,
user: UnambiguousMemberOrUser,
duration: t.Optional[Expiry] = None,
*,
reason: t.Optional[str] = None
) -> None:
if not isinstance(user, Member):
await ctx.send(":x: The user doesn't appear to be on the server.")
return
if duration is None:
duration = await Duration().convert(ctx, "1h")
await self.apply_mute(ctx, user, reason, expires_at=duration)
async def apply_mute(self, ctx: Context, user: Member, reason: t.Optional[str],
last_applied: datetime, **kwargs) -> None:
"""Apply a mute infraction with kwargs passed to `post_infraction`."""
...
infraction = await _utils.post_infraction(ctx, user, "mute", reason, active=True, **kwargs)
async def post_infraction(
ctx: Context,
user: MemberOrUser,
infr_type: str,
reason: str,
last_applied: datetime,
expires_at: datetime = None,
hidden: bool = False,
active: bool = True,
dm_sent: bool = False,
) -> t.Optional[dict]:
currently we only get the expiry datetime as the last_applied in post_infraction
with the updated type of t.Optional[InfractionExpiry] (assuming DeltaDuration), it'd have to either get to post_infraction as a pre-calculated Duration(expiry datetime) + last_applied (current utc within tempmute), or, as the DeltaDuration, and then post_infraction calculates the 2 values.
Would that not automatically happen by making the command use DurationDelta?
yes. That's a possibility. Though we might want to change the name since expires_at will be very misleading
Yeah, I agree change the name
Which does unfortunately mean refactoring all the calls anyway
But PyCharm can do that for you
right-click -> refactor -> change signature and it'll do all calls & usages within the func
that's not that bad since I need to refactor the types to the new type anyways
but there would need to be logic handing the type union since InfractionExpiry can be either the endpoint datetime or the DeltaDuration
Where does InfractionExpiry come from?
currently it's Expiry = t.Union[Duration, ISODateTime]
which both type to the same thing, expiry date time
it's a new added type for this pr
InfractionExpiry = t.Union[DurationDelta, ISODateTime]
but with this type the 2 unions are not the same time. ISODateTime is the expiry date time, DurationDelta is the delta
Well isn't that misleading given DurationDelta wouldn't be the Expiry
I.e. InfractionDurationOrExpiry or something
And yeah, we'd then isinstance to figure out which one
also I was kind of thinking if it should have infraction in the name anyways
since it's apparently used in other places
Should or shouldn't?
like superstarify uses infractions?
I guess that's fine though, it's technically an infraction still
superstarify is an infraction. It prevents people from changing their nick and changes it to a celebrity (used when they have a bad nick/username)
ah okay
I don't think we need to specify it's for infractions
I'd just call it DurationOrExpiry I guess
Yeah
that's literally what it is
That's what I'd go for
And DurationOrExpiry > ExpiryOrDuration because of the order of the Union
I guess expires_at can be duration_or_expiry then? 
though that's kind of a long kwarg
It's fine
I'm sure we've got way worse lol
If you really wanted to shorten it then maybe dur_or_expiry but don't think it's worth it / necessary
Don't really like that
(joke)
Smh
oh no, pycharm can't find usages
apparently every call to expires_at is through a **kwargs 😢
lmao
Is this for post_infraction?
Cause if so then it would be thinking about it
yeah
Yeah, you'll just have to manually change then
At least it should mostly be in the infractions.py file

By the way when this is done feel free to ping me for a review
Remember to edit the superstarify call too btw
Think that's the only one in a different file
so currently I have these changes in the post_infractions body
+ current_time = datetime.utcnow()
payload = {
"actor": ctx.author.id, # Don't use ctx.message.author; antispam only patches ctx.author.
"hidden": hidden,
"reason": reason,
"type": infr_type,
"user": user.id,
"active": active,
"dm_sent": dm_sent,
+ "last_applied": current_time,
}
+ # Parse duration or expiry
+ if duration_or_expiry is not None:
+ if isinstance(duration_or_expiry, datetime):
+ payload['expires_at'] = duration_or_expiry.isoformat()
+ elif isinstance(duration_or_expiry, relativedelta):
+ payload['expires_at'] = (current_time + duration_or_expiry).isoformat()
^
Yeah, it's weird 🤷
But yeah, this looks good otherwise
notify_infraction uses arrow.utcnow() apparently? 
that would avoid the delta import from dateutil yeah
Maybe just add a comment
but it could potentially match a wrong type I guess?
else: # is a relativedelta
You don't really need to account for that
If anything it's probably good to not account for it because then we'll get an error when we see it (which we want because it's unexpected behaviour)
If you look, it's so that it can humanize it
ah hm
We don't need to worry about that here so datetime is the better option imo
oh though, I guess I still have to import for the type hint
duration_or_expiry: datetime = None,
that needs to be unioned or I could import the type from converters
this
And make sure to add Optional[] since you default to None
So duration_or_expiry: t.Optional[DurationOrExpiry] = None or w/e
thanks for all the help btw 
No worries 😄
It can be quite hard to get used to the bot codebase and understand the different intricacies
I probably still don't know a large amount of things
E.g. never touched help channel code
wow the alias expands to this thing
lmao
