initial commit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
AeruandCopilot committed 2026-10-26 02:21:57 +01:00
commit c22dade3d5
21 files changed
+564

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
.env
whitelist.json
__pycache__/
venv/
.vscode/
+15
View File
@@ -0,0 +1,15 @@
# Disclaimer and Assumption of Responsibility
This repository is provided strictly for informational, educational, and illustrative purposes. All code is supplied "AS IS", without warranties or guarantees of any kind, whether express or implied, to the fullest extent permitted by applicable law.
Any reference to Discord or any other third-party platform is for illustrative purposes only. This project is not affiliated with, endorsed by, or sponsored by Discord. It is not intended to authorize, encourage, or guarantee compliance with any third-party platform's Terms of Service, policies, or rules.
**All use, deployment, adaptation, modification, and operation of this code are undertaken solely at the user's own initiative and risk.** The user is exclusively responsible for independently assessing the legality, safety, suitability, and compliance of any intended use, as well as for obtaining any necessary permissions.
To the fullest extent permitted by applicable law, the author disclaims responsibility for consequences arising from third-party use, modification, deployment, or misuse of this code, including account suspensions, bans, restrictions, revoked access, service disruptions, data loss, damages, or claims arising from violations of third-party terms or applicable law.
The publication of this code does not constitute an offer to operate a service, provide technical support, maintain the software, authorize third-party integrations, or participate in any deployment or activity carried out by users.
The author makes no representation that the code is suitable for any particular purpose, compliant with any third-party platform's policies, or free from defects, vulnerabilities, or operational failures.
Nothing in this disclaimer excludes or limits any liability, right, or obligation that cannot lawfully be excluded or limited under applicable law.
+11
View File
@@ -0,0 +1,11 @@
import io,time
import aiohttp,discord
from discord.ext import commands
class cat(commands.Cog):
def __init__(s,bot):s.bot=bot
@commands.command()
async def cat(s,ctx):
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=15)) as http,http.get("https://cataas.com/cat") as r:
r.raise_for_status();data=await r.read();ext=r.content_type.split('/')[-1].replace('jpeg','jpg')
await ctx.send(file=discord.File(io.BytesIO(data),filename=f"cat-{int(time.time())}.{ext}"))
async def setup(bot):await bot.add_cog(cat(bot))
+8
View File
@@ -0,0 +1,8 @@
from discord.ext import commands
class help(commands.Cog):
def __init__(s,bot):s.bot=bot
@commands.command(name="help")
async def help(s,ctx):
names=sorted(c.qualified_name for c in s.bot.walk_commands() if not c.hidden)
await ctx.send("```\n"+"\n".join(";"+n for n in names)+"\n```")
async def setup(bot):await bot.add_cog(help(bot))
+41
View File
@@ -0,0 +1,41 @@
import asyncio,re
from discord.ext import commands
def uid(s):
m=re.fullmatch(r"<@!?(\d+)>|(\d+)",s.strip())
return int(m[1] or m[2]) if m else None
class perm(commands.Cog):
def __init__(s,bot):
s.bot=bot;s.names={};s.sem=asyncio.Semaphore(2)
async def cog_check(s,ctx):return ctx.author.id==s.bot.user.id
async def name(s,i):
if i in s.names:return s.names[i]
u=s.bot.get_user(i)
if u is None:
async with s.sem:
try:u=await s.bot.fetch_user(i)
except Exception:u=None
await asyncio.sleep(.6)
n=(u.global_name or u.name) if u else "unknown"
if u:s.names[i]=n
return n
@commands.group(invoke_without_command=True)
async def perm(s,ctx):await ctx.send("`;perm list|add <id|mention>|remove <id|mention>`")
@perm.command(name="list")
async def _list(s,ctx):
ids=sorted(s.bot.wl.ids)
if not ids:return await ctx.send("whitelist empty")
n=await asyncio.gather(*(s.name(i) for i in ids))
await ctx.send("\n".join(f"{i} - {x}" for i,x in zip(ids,n)))
@perm.command(name="add")
async def _add(s,ctx,target:str):
i=uid(target)
if i is None:return await ctx.send("invalid id")
await s.bot.wl.mutate(i,True);s.bot.hook.put(f"whitelist: {i} added by {ctx.author.id}")
await ctx.send(f"{i} added ({await s.name(i)})")
@perm.command(name="remove")
async def _remove(s,ctx,target:str):
i=uid(target)
if i is None:return await ctx.send("invalid id")
await s.bot.wl.mutate(i,False);s.bot.hook.put(f"whitelist: {i} removed by {ctx.author.id}")
await ctx.send(f"{i} removed")
async def setup(bot):await bot.add_cog(perm(bot))
+26
View File
@@ -0,0 +1,26 @@
import asyncio,re
import aiohttp
from discord.ext import commands
URL="https://discordstatus.com/metrics-display/5k2rt9f7pmny/day.json"
async def local():
try:
p=await asyncio.create_subprocess_exec("ping","-c","1","-W","5","discord.com",stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.STDOUT)
out,_=await asyncio.wait_for(p.communicate(),8)
m=re.search(r"time[=<]\s*([\d.]+)\s*ms",out.decode(),re.I)
return round(float(m[1])) if m else None
except Exception:return None
async def glob():
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as h,h.get(URL) as r:
return int((await r.json(content_type=None))["summary"]["last"])
except Exception:return None
class ping(commands.Cog):
def __init__(s,bot):s.bot=bot
@commands.command()
async def ping(s,ctx):
l,g=await asyncio.gather(local(),glob())
f=lambda v:f"{v}ms" if v is not None else "n/a"
w=s.bot.latency
w=round(w*1000) if w==w and w!=float("inf") else None
await ctx.send(f"websocket ping: {f(w)}\nlocal discord ping: {f(l)}\nglobal discord ping: {f(g)}")
async def setup(bot):await bot.add_cog(ping(bot))
+119
View File
@@ -0,0 +1,119 @@
import io,unicodedata,urllib.request
from pathlib import Path
import discord
from discord.ext import commands
from PIL import Image,ImageDraw,ImageFilter,ImageFont,ImageOps
from pilmoji import Pilmoji
from core.threads import run_blocking
W,H,AV,PAD,MAXC=1200,480,268,42,420
BG0,BG1,CARD,STROKE,ACC,TXT,SUB,MARK=(7,9,17),(15,20,36),(18,24,44),(46,64,108),(82,116,214),(236,242,255),(128,150,192),(52,80,160)
FONT=Path(__file__).resolve().parents[1]/"font"/"NotoSans-Regular.ttf"
def norm(t):return unicodedata.normalize("NFKC",t)
def font(n):
try:return ImageFont.truetype(str(FONT),n)
except Exception:return ImageFont.load_default()
def fetch(url):
with urllib.request.urlopen(urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"}),timeout=20) as r:return r.read()
def names(t,users):
for u in {u.id:u for u in users}.values():
n="@"+(getattr(u,"display_name",None) or u.name);t=t.replace(f"<@{u.id}>",n).replace(f"<@!{u.id}>",n)
return t
def gradient():
im=Image.new("RGB",(W,H));d=ImageDraw.Draw(im)
for y in range(H):
t=y/(H-1);d.line((0,y,W,y),fill=tuple(int(a+(b-a)*t) for a,b in zip(BG0,BG1)))
return im.convert("RGBA")
def glow(im):
o=Image.new("RGBA",im.size,(0,0,0,0));d=ImageDraw.Draw(o);cx,cy,m=int(W*.74),int(H*.2),int(H*.7)
for i in range(18):
r=int(m*(1-i/18));d.ellipse((cx-r,cy-r,cx+r,cy+r),fill=(100,130,215,int(52*(1-i/18))))
o=o.filter(ImageFilter.GaussianBlur(12));im.paste(o,(0,0),o)
def tw(d,t,f):
l,_,r,_=d.textbbox((0,0),t,font=f);return r-l
def chop(d,w,f,mw):
out=[];cur=""
for c in w:
if cur and tw(d,cur+c,f)>mw:out.append(cur);cur=c
else:cur+=c
return out+([cur] if cur else [])
def wrap(d,t,f,mw):
lines=[]
for p in t.splitlines() or [t]:
ws=p.split()
if not ws:
if lines:lines.append("")
continue
cur=""
for w in ws:
for part in (chop(d,w,f,mw) if tw(d,w,f)>mw else [w]):
x=part if not cur else cur+" "+part
if tw(d,x,f)<=mw:cur=x
else:
if cur:lines.append(cur)
cur=part
if cur:lines.append(cur)
return lines
def lh(d,f):
_,t,_,b=d.textbbox((0,0),"Ag",font=f);h=b-t;return max(h+6,int(h*1.26))
def fit(d,t,mw,mh):
last=(["..."],font(15),lh(d,font(15)))
for s in range(110,14,-2):
f=font(s);l=wrap(d,t,f,mw);h=lh(d,f)
if len(l)*h<=mh:return l or ["..."],f,h
last=(l,f,h)
l,f,h=last;return (l or ["..."])[:max(1,mh//max(1,h))],f,h
def avatar(b):
a=ImageOps.fit(Image.open(io.BytesIO(b)).convert("RGB"),(AV,AV));m=Image.new("L",(AV,AV),0)
ImageDraw.Draw(m).ellipse((0,0,AV,AV),fill=255);r=Image.new("RGBA",(AV,AV),(0,0,0,0));r.paste(a,(0,0),m);return r
def build(ab,text,sig):
im=gradient();glow(im);d=ImageDraw.Draw(im)
ax,ay=PAD,(H-AV)//2;cl,ct,cr,cb=ax+AV+18,24,W-24,H-24
sh=Image.new("RGBA",im.size,(0,0,0,0));ImageDraw.Draw(sh).rounded_rectangle((cl+7,ct+10,cr+7,cb+10),radius=28,fill=(0,0,0,105))
sh=sh.filter(ImageFilter.GaussianBlur(10));im.paste(sh,(0,0),sh)
d.rounded_rectangle((cl,ct,cr,cb),radius=28,fill=CARD,outline=STROKE,width=2)
gd=AV+28;g=Image.new("RGBA",(gd,gd),(0,0,0,0));gg=ImageDraw.Draw(g);c=gd//2
for i in range(10):
r=c-i*4
if r<=0:break
gg.ellipse((c-r,c-r,c+r,c+r),fill=(*ACC,int(55*(1-i/10))))
g=g.filter(ImageFilter.GaussianBlur(4));im.paste(g,(ax-14,ay-14),g)
rd=AV+8;ring=Image.new("RGBA",(rd,rd),(0,0,0,0));ImageDraw.Draw(ring).ellipse((0,0,rd-1,rd-1),outline=(*ACC,175),width=3);im.paste(ring,(ax-4,ay-4),ring)
a=avatar(ab);im.paste(a,(ax,ay),a)
qx,zt,tr=cl+52,ct+44,cr-32;sf=font(27);sig=f"\u2014 {sig}";_,_,_,sh_=d.textbbox((0,0),sig,font=sf)
sy=cb-sh_-30;dy=sy-15;zh=max(48,dy-zt-16)
lines,qf,h=fit(d,text,tr-qx,zh)
d.text((cl+10,ct+6),"\u201c",font=font(max(54,min(100,int(h*1.55)))),fill=MARK)
cx=(qx+tr)//2;y=zt+max(0,(zh-len(lines)*h)//2)
with Pilmoji(im) as p:
for l in lines:
p.text((cx-tw(d,l,qf)//2,y),l,font=qf,fill=TXT,emoji_scale_factor=1.08,emoji_position_offset=(0,-2));y+=h
d.rectangle((qx,dy,tr,dy+1),fill=STROKE)
p.text((qx,sy),sig,font=sf,fill=SUB,emoji_scale_factor=1.0,emoji_position_offset=(0,-1))
out=io.BytesIO();im.convert("RGB").save(out,format="PNG");out.seek(0);return out
class q(commands.Cog):
def __init__(s,bot):s.bot=bot
@commands.command(name="q")
async def q(s,ctx,*args):
m=ctx.message;target=m.author;text=""
ref=m.reference
if not args and ref and ref.message_id:
r=ref.resolved if isinstance(ref.resolved,discord.Message) else None
if r is None:
try:r=await m.channel.fetch_message(ref.message_id)
except Exception:r=None
if r:
text=names((r.content or "").strip(),r.mentions)
if text:target=r.author
if not text:
rest=list(args)
if rest and rest[0].startswith("<@") and m.mentions:
target=m.mentions[0];rest=[x for x in rest if x not in(f"<@{target.id}>",f"<@!{target.id}>")]
text=names(" ".join(rest).strip(),m.mentions)
if not text:return await ctx.send("usage: ;q text | ;q @user text | reply with ;q")
text=norm(text[:MAXC])
url=str(target.display_avatar.replace(size=512,format="png").url)
ab=await run_blocking(fetch,url)
sig=norm(f"{getattr(target,'global_name',None) or target.display_name} (@{target.name})")
img=await run_blocking(build,ab,text,sig)
await ctx.send(file=discord.File(img,filename="quote.png"))
async def setup(bot):await bot.add_cog(q(bot))
+93
View File
@@ -0,0 +1,93 @@
import datetime as dt,os,platform,time
import psutil
from discord.ext import commands
from core.threads import run_blocking
def h(n):
n=float(n)
for u in("b","kib","mib","gib","tib"):
if abs(n)<1024:return f"{n:.2f} {u}"
n/=1024
return f"{n:.2f} pib"
def kv(d):return "\n".join(f"{k}: {v}" for k,v in d.items())
def rd(p):
try:
with open(p) as f:return f.read().strip()
except OSError:return ""
def system():
u=platform.uname();r={}
try:r=platform.freedesktop_os_release()
except Exception:pass
b=psutil.boot_time()
return kv({"system":u.system,"kernel":u.release,"version":u.version,"machine":u.machine,"platform":platform.platform(),"distro":r.get("PRETTY_NAME"),"distro id":r.get("ID"),"distro version":r.get("VERSION_ID"),"libc":" ".join(platform.libc_ver()),"python":f"{platform.python_implementation()} {platform.python_version()}","compiler":platform.python_compiler(),"build":" ".join(platform.python_build()),"boot":dt.datetime.fromtimestamp(b).isoformat(),"uptime":str(dt.timedelta(seconds=int(time.time()-b))),"entropy":rd("/proc/sys/kernel/random/entropy_avail"),"max pid":rd("/proc/sys/kernel/pid_max"),"max threads":rd("/proc/sys/kernel/threads-max"),"file max":rd("/proc/sys/fs/file-max"),"file nr":rd("/proc/sys/fs/file-nr"),"swappiness":rd("/proc/sys/vm/swappiness"),"overcommit":rd("/proc/sys/vm/overcommit_memory"),"tainted":rd("/proc/sys/kernel/tainted"),"cpu vulns":len(os.listdir("/sys/devices/system/cpu/vulnerabilities")) if os.path.isdir("/sys/devices/system/cpu/vulnerabilities") else 0})
def clock():
n=dt.datetime.now().astimezone()
return kv({"local":n.isoformat(),"utc":dt.datetime.now(dt.timezone.utc).isoformat(),"tz":n.tzname(),"offset":n.utcoffset(),"time.tzname":time.tzname,"timezone":time.timezone,"altzone":time.altzone,"daylight":time.daylight,"iso week":n.isocalendar()[:2],"day of year":n.timetuple().tm_yday,"epoch":time.time(),"monotonic":time.monotonic(),"perf_counter":time.perf_counter(),"process time":time.process_time(),"clock res":time.get_clock_info("monotonic").resolution})
def cpu():
pc=psutil.cpu_percent(interval=1,percpu=True);fr=psutil.cpu_freq(percpu=True) or []
info={}
for l in rd("/proc/cpuinfo").splitlines():
if ":" in l:
k,v=l.split(":",1);info.setdefault(k.strip(),v.strip())
cores="\n".join(f" core {i:>2}: {p:5.1f}%"+(f" @ {fr[i].current:.0f} mhz ({fr[i].min:.0f}-{fr[i].max:.0f})" if i<len(fr) else "") for i,p in enumerate(pc))
return kv({"model":info.get("model name","unknown"),"vendor":info.get("vendor_id"),"family":info.get("cpu family"),"model id":info.get("model"),"stepping":info.get("stepping"),"microcode":info.get("microcode"),"cache":info.get("cache size"),"bogomips":info.get("bogomips"),"physical":psutil.cpu_count(False),"logical":psutil.cpu_count(),"total %":round(sum(pc)/len(pc),1),"loadavg":os.getloadavg(),"times":psutil.cpu_times()._asdict(),"times %":psutil.cpu_times_percent()._asdict(),"stats":psutil.cpu_stats()._asdict(),"flags":info.get("flags"),"per core":"\n"+cores})
def mem():
v=psutil.virtual_memory()._asdict();s=psutil.swap_memory()._asdict()
f=lambda d:{k:(x if k in("percent","sin","sout") else h(x)) for k,x in d.items()}
mi={}
for l in rd("/proc/meminfo").splitlines():
k,_,x=l.partition(":");mi[k]=x.strip()
ex={k:mi[k] for k in("Dirty","Writeback","Mapped","AnonPages","PageTables","KernelStack","Hugepagesize","HugePages_Total","CommitLimit","Committed_AS","VmallocUsed") if k in mi}
return "ram\n"+kv(f(v))+"\nswap\n"+kv(f(s))+"\nextra\n"+kv(ex)
def disks():
o=[]
for n,p in enumerate(psutil.disk_partitions()):
try:u=psutil.disk_usage(p.mountpoint);o.append(f"part{n} {p.fstype} [{p.opts}] {h(u.used)}/{h(u.total)} ({u.percent}%) free {h(u.free)}")
except Exception:pass
for k,v in psutil.disk_io_counters(perdisk=True).items():
o.append(f"io {k}: read {h(v.read_bytes)} ({v.read_count}) write {h(v.write_bytes)} ({v.write_count}) read time {v.read_time}ms write time {v.write_time}ms busy {getattr(v,'busy_time','-')}ms")
return "\n".join(o)
def net():
st=psutil.net_if_stats();io=psutil.net_io_counters(pernic=True);o=[]
for k,a in psutil.net_if_addrs().items():
s=st.get(k);i=io.get(k)
o.append(f"{k}: up={s.isup if s else '?'} speed={s.speed if s else '?'} mb duplex={s.duplex.name if s else '?'} mtu={s.mtu if s else '?'}"+(f" rx={h(i.bytes_recv)} ({i.packets_recv} pkts) tx={h(i.bytes_sent)} ({i.packets_sent} pkts) err={i.errin}/{i.errout} drop={i.dropin}/{i.dropout}" if i else ""))
o.append(" families: "+", ".join(sorted({x.family.name for x in a})))
t=io.get
tot=psutil.net_io_counters()
o.append(f"total: rx={h(tot.bytes_recv)} tx={h(tot.bytes_sent)} err={tot.errin}/{tot.errout} drop={tot.dropin}/{tot.dropout}")
try:
c=psutil.net_connections();z={};k={}
for x in c:
z[x.status]=z.get(x.status,0)+1;k[x.type.name]=k.get(x.type.name,0)+1
o.append(f"connections: {len(c)} {z} {k}")
except Exception:pass
return "\n".join(o)
def sensors():
o=[]
for k,v in (getattr(psutil,"sensors_temperatures",lambda:{})() or {}).items():o+=[f"temp {k}/{x.label or '-'}: {x.current} c (high {x.high} crit {x.critical})" for x in v]
for k,v in (getattr(psutil,"sensors_fans",lambda:{})() or {}).items():o+=[f"fan {k}/{x.label or '-'}: {x.current} rpm" for x in v]
b=getattr(psutil,"sensors_battery",lambda:None)()
if b:o.append(f"battery: {b.percent:.0f}% plugged={b.power_plugged} left={b.secsleft}")
return "\n".join(o) or "no sensors"
def procs():
z={};t=0;n=0;top=[]
for p in psutil.process_iter(["status","num_threads","memory_percent","cpu_percent"]):
i=p.info;z[i["status"]]=z.get(i["status"],0)+1;t+=i["num_threads"] or 0;n+=1;top.append((i["memory_percent"] or 0,i["cpu_percent"] or 0))
top.sort(reverse=True)
return kv({"processes":n,"threads":t,"by status":z,"top mem %":[round(a,2) for a,_ in top[:5]],"mem % sum":round(sum(a for a,_ in top),1)})
def me():
p=psutil.Process();m=p.memory_info()
return kv({"pid":p.pid,"ppid":p.ppid(),"threads":p.num_threads(),"rss":h(m.rss),"vms":h(m.vms),"shared":h(m.shared),"fds":p.num_fds(),"ctx switches":p.num_ctx_switches()._asdict(),"cpu times":p.cpu_times()._asdict(),"io":p.io_counters()._asdict() if hasattr(p,"io_counters") else "-","started":dt.datetime.fromtimestamp(p.create_time()).isoformat(),"nice":p.nice(),"status":p.status()})
def build():
o=[]
for t,f in(("system",system),("time",clock),("cpu",cpu),("memory",mem),("disks",disks),("network",net),("sensors",sensors),("processes",procs),("bot process",me)):
try:b=f()
except Exception as e:b=f"n/a: {e!r}"
o.append(f"===== {t} =====\n{b}")
return "\n\n".join(o)
class status(commands.Cog):
def __init__(s,bot):s.bot=bot
@commands.command()
async def status(s,ctx):
d=await run_blocking(build);await ctx.send(f"```\n{d}\n```")
async def setup(bot):await bot.add_cog(status(bot))
View File
Whitespace-only changes.
+21
View File
@@ -0,0 +1,21 @@
import discord
from discord.ext import commands
from core.text import split
NOTE="-# slow down, this rep was rate limited, if you still spam the rate limite will increase"
class Ctx(commands.Context):
limited=False
async def send(s,content=None,**kw):
kw.setdefault("mention_author",False)
text=None if content is None else str(content)
if s.limited:
text=f"{text}\n{NOTE}" if text else NOTE
parts=split(text,1900) if len(text)>2000 else [text]
else:parts=split(text) if text is not None and len(text)>2000 else [text]
m=None
for i,p in enumerate(parts):
k=kw if i==len(parts)-1 else {"mention_author":False}
try:m=await s.message.reply(p,**k)
except discord.HTTPException:
k.pop("mention_author",None);m=await s.channel.send(p,**k)
return m
reply=send
+28
View File
@@ -0,0 +1,28 @@
import asyncio,time,aiohttp
from core.text import split
class Hook:
def __init__(s,url):
s.url=url;s.q=asyncio.Queue();s.task=None;s.http=None
async def start(s):
s.http=aiohttp.ClientSession();s.task=asyncio.create_task(s.run())
def put(s,text):
line=f"`{time.strftime('%d/%m %H:%M:%S')}` {text}"
print(line)
if s.url:
for c in split(line,1900):s.q.put_nowait(c)
async def run(s):
while True:
item=await s.q.get()
for i in range(6):
try:
async with s.http.post(s.url,json={"content":item,"allowed_mentions":{"parse":[]}}) as r:
if r.status==429:
await asyncio.sleep(float((await r.json()).get("retry_after",1))+.1);continue
if r.status<500:break
await asyncio.sleep(1+i)
except Exception:await asyncio.sleep(1+i)
s.q.task_done();await asyncio.sleep(.25)
async def close(s):
try:await asyncio.wait_for(s.q.join(),15)
except asyncio.TimeoutError:pass
s.task.cancel();await s.http.close()
+26
View File
@@ -0,0 +1,26 @@
import asyncio,time
BASE,MAX,STEP,CALM,CAP=0.5,2.0,0.25,5.0,20
class State:
__slots__=("slot","interval","last","tasks")
def __init__(s):s.slot=0.0;s.interval=BASE;s.last=0.0;s.tasks=set()
class Limiter:
def __init__(s):s.users={}
def admit(s,uid):
now=time.monotonic();st=s.users.get(uid)
if st is None:st=s.users[uid]=State()
if len(st.tasks)>=CAP:return st,None,True
if now-st.last>CALM:st.interval=BASE
wait=max(0.0,st.slot-now)
if wait>0:st.interval=min(MAX,st.interval+STEP)
st.slot=max(now,st.slot)+st.interval;st.last=now
return st,wait,False
def flush(s,st):
n=len(st.tasks)
for t in list(st.tasks):t.cancel()
st.tasks.clear();st.slot=0.0;st.interval=BASE;return n
def track(s,uid,st,task):
st.tasks.add(task)
def done(t):
st.tasks.discard(t)
if not st.tasks and time.monotonic()-st.last>CALM*2 and s.users.get(uid) is st:s.users.pop(uid,None)
task.add_done_callback(done)
+8
View File
@@ -0,0 +1,8 @@
from pathlib import Path
ROOT=Path(__file__).resolve().parent.parent
async def load_all(bot):
for d in ("cmds","events"):
for f in sorted((ROOT/d).glob("*.py")):
if f.name.startswith("_"):continue
try:await bot.load_extension(f"{d}.{f.stem}")
except Exception as e:bot.hook.put(f"module load failed: {d}.{f.stem}: {e!r}")
+10
View File
@@ -0,0 +1,10 @@
import asyncio,random,time
async def presence(bot,interval=45):
await bot.wait_until_ready()
since=int(time.time()*1000)
while True:
payload={"op":3,"d":{"status":"idle","since":since,"afk":True,"activities":[]}}
try:
if bot.ws:await bot.ws.send_as_json(payload)
except Exception as e:bot.hook.put(f"presence update failed: {e!r}")
await asyncio.sleep(interval+random.uniform(-5,5))
+10
View File
@@ -0,0 +1,10 @@
def split(t,n=1990):
out=[];op=False
while t:
pre="```\n" if op else "";room=n-len(pre)-4
if len(t)<=room:out.append(pre+t);break
cut=t.rfind("\n",0,room);cut=cut if cut>room//2 else room
chunk=t[:cut];t=t[cut:].lstrip("\n")
o=op^(chunk.count("```")%2==1)
out.append(pre+chunk+("\n```" if o else ""));op=o
return out or [""]
+22
View File
@@ -0,0 +1,22 @@
import asyncio
import functools
from concurrent.futures import ThreadPoolExecutor
_pool = ThreadPoolExecutor(max_workers=32, thread_name_prefix="dfu")
async def run_blocking(func, /, *args, **kwargs):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(_pool, functools.partial(func, *args, **kwargs))
def threaded(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
return await run_blocking(func, *args, **kwargs)
return wrapper
def spawn(coro, *, name=None):
task = asyncio.create_task(coro, name=name)
def _done(t):
if not t.cancelled() and t.exception():
print(f"task {name or ''} error: {t.exception()!r}")
task.add_done_callback(_done)
return task
def shutdown():
_pool.shutdown(wait=False, cancel_futures=True)
+35
View File
@@ -0,0 +1,35 @@
import asyncio,json,os
from pathlib import Path
from core.threads import run_blocking
def _read(p):
if not p.exists():p.write_text("[]")
return p.stat().st_mtime_ns,frozenset(int(x) for x in json.loads(p.read_text()))
def _write(p,ids):
t=p.with_suffix(".tmp");t.write_text(json.dumps(sorted(ids),indent=2));os.replace(t,p);return p.stat().st_mtime_ns
class Whitelist:
def __init__(s,path,hook):
s.path=Path(path);s.hook=hook;s.ids=frozenset();s.m=None;s.lock=asyncio.Lock();s.task=None
def __contains__(s,i):return i in s.ids
async def load(s):
try:m,ids=await run_blocking(_read,s.path)
except Exception as e:
s.hook.put(f"whitelist unreadable: {e!r}, keeping previous list")
try:s.m=(await run_blocking(s.path.stat)).st_mtime_ns
except OSError:pass
return
if s.m is not None and ids!=s.ids:s.hook.put(f"whitelist reloaded ({len(ids)} ids)")
s.ids=ids;s.m=m
async def watch(s):
while True:
await asyncio.sleep(1)
try:m=(await run_blocking(s.path.stat)).st_mtime_ns
except OSError:m=None
if m!=s.m:await s.load()
async def start(s):
await s.load();s.task=asyncio.create_task(s.watch())
async def mutate(s,i,add):
async with s.lock:
n=set(s.ids)
if add:n.add(i)
else:n.discard(i)
s.m=await run_blocking(_write,s.path,n);s.ids=frozenset(n)
+42
View File
@@ -0,0 +1,42 @@
import asyncio,signal
from pathlib import Path
import discord,dotenv
from discord.ext import commands
from discord.http import Route
from core import threads
from core.hook import Hook
from core.loader import load_all
from core.presence import presence
from core.whitelist import Whitelist
dotenv.load_dotenv()
env=dotenv.find_dotenv()
token=dotenv.get_key(env,"TOKEN");log=dotenv.get_key(env,"LOG")
bot=commands.Bot(command_prefix=";",self_bot=True,help_command=None)
def banned(e):
return isinstance(e,discord.LoginFailure) or (isinstance(e,discord.ConnectionClosed) and e.code==4004) or (isinstance(e,discord.HTTPException) and e.status in(401,403))
async def guard():
await bot.wait_until_ready()
if bot.user.id not in bot.wl:
await bot.wl.mutate(bot.user.id,True);bot.hook.put(f"owner {bot.user.id} added to whitelist")
bot.hook.put(f"started as {bot.user} ({bot.user.id}), {len(bot.guilds)} guilds, whitelist {len(bot.wl.ids)}")
while True:
await asyncio.sleep(60)
try:await bot.http.request(Route("GET","/users/@me"))
except Exception as e:
if banned(e):return f"account banned, disabled or token revoked: {e!r}"
async def main():
hook=Hook(log);await hook.start();bot.hook=hook;bot.wl=Whitelist(Path(__file__).resolve().parent/"whitelist.json",hook);await bot.wl.start()
stop=asyncio.Event();loop=asyncio.get_running_loop()
for s in(signal.SIGINT,signal.SIGTERM):loop.add_signal_handler(s,stop.set)
await load_all(bot)
run=asyncio.create_task(bot.start(token));g=asyncio.create_task(guard());w=asyncio.create_task(stop.wait());p=asyncio.create_task(presence(bot))
done,_=await asyncio.wait({run,g,w},return_when=asyncio.FIRST_COMPLETED)
if run in done:
e=run.exception() if not run.cancelled() else None
r=f"account banned or invalid token: {e!r}" if banned(e) else f"unexpected stop: {e!r}"
elif g in done:r=f"guard stopped: {g.exception()!r}" if g.exception() else g.result()
else:r="stopped by signal"
hook.put(r)
for t in(g,w,p,bot.wl.task):t.cancel()
await bot.close();await hook.close();threads.shutdown()
if __name__=="__main__":asyncio.run(main())
+38
View File
@@ -0,0 +1,38 @@
import asyncio,discord
from discord.ext import commands
from core.ctx import Ctx
from core.limiter import Limiter
async def setup(bot):
lim=Limiter()
async def run(ctx,st,wait,uid):
try:
if wait>0:await asyncio.sleep(wait)
st.tasks.discard(asyncio.current_task())
await bot.invoke(ctx)
except asyncio.CancelledError:pass
except Exception as e:bot.hook.put(f"dispatch failed for {uid}: {e!r}"[:1900])
async def handler(m):
if m.author.bot or not m.content.startswith(await bot.get_prefix(m)):return
uid=m.author.id
if uid!=bot.user.id and uid not in bot.wl:return
ctx=await bot.get_context(m,cls=Ctx)
if ctx.command is None:return
st,wait,full=lim.admit(uid)
if full:
n=lim.flush(st)
bot.hook.put(f"{m.author} ({uid}) flooded, {n} pending commands dropped")
try:await m.add_reaction("\u274c")
except Exception:pass
try:await m.reply(f"too many pending commands, {n} dropped",mention_author=False)
except Exception:pass
return
ctx.limited=wait>0
where=f"#{getattr(m.channel,'name',None) or 'dm'} ({m.guild.name if m.guild else 'dm'})"
bot.hook.put(f"{m.author} ({uid}) ran `{m.content[:200]}` in {where}")
lim.track(uid,st,asyncio.create_task(run(ctx,st,wait,uid)))
async def on_command_error(ctx,e):
if isinstance(e,(commands.CommandNotFound,commands.CheckFailure)):return
bot.hook.put(f"error in `{ctx.command}` by {ctx.author.id}: `{e!r}`"[:1900])
try:await ctx.send(f"error: `{e}`"[:1900])
except Exception:pass
bot.on_message=handler;bot.on_command_error=on_command_error
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
python-dotenv
aiohttp
Pillow
psutil
git+https://github.com/dolfies/discord.py-self.git
pilmoji