bot.py
· 6.1 KiB · Python
Raw
from interactions import slash_command, SlashContext, Client, slash_option, OptionType, auto_defer, Embed, check, Task, DateTrigger, listen
from requests import get, post
from datetime import datetime, timedelta, date
from re import findall
from os import environ
bot = Client(token=environ["BOT_TOKEN"])
is_recurring = True
owner_username = "alyssile"
officer_role_suffix = "Officer"
# add IDs for guilds you want the bot to register commands in
guilds = [353071720342487040, 1038346577267392582]
role_housel = 1149914848428294315
role_weekly = 1149914607822061599
role_tribal = 1149914662197010522
role_grandc = 1149914810486632488
role_jumboc = 1149929534129459290
role_fashio = 1212403886263115786
# role_testing = 873495029883236392 # TODO
async def check_officer(ctx: SlashContext):
return ctx.author.username == owner_username or any(role.name[-len(officer_role_suffix):] == officer_role_suffix for role in ctx.author.roles)
#@slash_command(name="xivtimers", description="Show current timers", scopes=guilds)
#@slash_option(name="clear", description="Clears the previous message from the channel", required=False, opt_type=OptionType.BOOLEAN)
#@slash_option(name="recur", description="Sets up the command to refresh the embed automatically each time one of the timers expires", required=False, opt_type=OptionType.BOOLEAN)
#@check(check=check_officer)
#@auto_defer()
#async def xivtimers_cmd(ctx: SlashContext, clear: bool=False, recur: bool=False):
# global is_recurring
# if is_recurring:
# await ctx.send(f"There's already a recurring channel update task running... check with {owner_username}", delete_after=10, ephemeral=True)
# return
# await xivtimers(ctx.channel_id, clear, recur)
async def xivtimers(channel_id, clear: bool=False, recur: bool=False, mention: str=""):
global is_recurring
channel = bot.get_channel(channel_id)
now = datetime.now()
data = get("https://www.xenoveritas.org/static/ffxiv/timers.json").json()
embed = Embed(title="Timers", color="#eb144c")
timers = {}
maintenance = False
#print('data["timers"]', data["timers"])
for timer in data["timers"]:
if not any(label in timer for label in ["start", "end", "end_label"]):
print(timer)
continue
if timer.get('end',0) and (now-timedelta(hours=6)).timestamp() > int(timer.get('end',0)/1000):
continue
end_label = timer.get("endLabel", f"<t:{int(timer.get('end',0)/1000)}:R>")
ts = datetime.fromtimestamp(int(timer.get('start',0)/1000))
if "<a href=" in timer["name"]:
name = timer["name"].split('">')[1].split("<")[0]
else:
name = timer["name"]
if "all worlds maintenance" in name.lower() and now > ts and now < datetime.fromtimestamp(int(timer.get('end',0)/1000)):
maintenance = True
timers["Maintenance complete"] = datetime.fromtimestamp(int(timer.get('end',0)/1000))
if now < ts:
timers[name] = ts
embed.add_field(name=name, value=f"Start{'s' if now.timestamp() < int(timer.get('start',0)/1000) else 'ed'} <t:{int(timer.get('start',0)/1000)}:R>, {'runs' if 'endLabel' in timer else 'ends'} {end_label}")
if maintenance:
embed.title = "Timers (Maintenance)"
def generate_field(name: str, hour: int, role: int=None, day: int=None):
nonlocal timers, now
if (day is not None and now.weekday() == day) and now.hour >= hour:
_now = now + timedelta(1)
elif now.hour >= hour:
_now = now + timedelta(1)
else:
_now = now
reset = datetime.combine(_now, datetime.min.time())+timedelta(hours=hour)
if day is not None:
reset += timedelta((day-_now.weekday())%7)
timers[f"<@&{role}>"] = reset
return {"name": name, "value": f"<t:{int(reset.timestamp())}:R>", "inline": True}
# embed.add_field(name="Patch 6.5", value=f"<t:{int(datetime(2023,10,3,19,00).timestamp())}:R>")
if not maintenance:
housing_start_date = datetime(2022,5,27,1,0)
if now.hour >= 1:
_now = now+timedelta(1)
else:
_now = now
mod = (_now - housing_start_date).days % 9
house_reset = datetime.combine(_now, datetime.min.time())+timedelta(abs(mod-5 if mod <= 5 else mod-9))+timedelta(hours=1)
timers[f"<@&{role_housel}>"] = house_reset
embed.add_field(name="Housing " + ("Results ending" if mod > 5 else "Lottery ending"), value=f"<t:{int(house_reset.timestamp())}:R>", inline=True)
embed.add_field(**generate_field("Weekly Reset", 19, day=1, role=role_weekly))
embed.add_field(**generate_field("Fashion Report Judging", 19, day=4, role=role_fashio))
embed.add_field(**generate_field("Tribal/Duty Reset", 1, role=role_tribal))
embed.add_field(**generate_field("Grand Company Reset", 6, role=role_grandc))
embed.add_field(**generate_field("Jumbo Cactpot", 19, day=5, role=role_jumboc))
#print("\n".join(f"{t:%Y/%m/%d %H:%M:%S}" for t in timers.values()))
async for message in channel.history(limit=2 if not is_recurring else 1):
print(message)
if message.author == bot.user.id:
await message.delete()
# timers[f"<@&{role_testing}>"] = datetime.now()+timedelta(seconds=20) # TODO
_mention = mention
mention, earliest = min(timers.items(), key=lambda x: x[1])
embed.add_field(name="Refreshing", value=f"<t:{int(earliest.timestamp())}:R>", inline=True)
await channel.send(content=_mention, embed=embed)
print(f"Setting up timer at {earliest:%Y/%m/%d %H:%M:%S} in {channel_id}")
@Task.create(DateTrigger(earliest))
async def recurring():
await xivtimers(channel_id, clear, recur, mention=mention)
recurring.start()
@listen()
async def on_startup():
print("Bot started")
await xivtimers(1063529561486270494, True, True)
await (bot.get_channel("1127730623818256384")).send("xivtimers restarted")
# channel = bot.get_channel("1127730623818256384")
# await channel.send("Bot restarted, please run `/xivtimers clear:True recur:True` in <#1063529561486270494> (<@133057442425602048>)")
bot.start()
| 1 | from interactions import slash_command, SlashContext, Client, slash_option, OptionType, auto_defer, Embed, check, Task, DateTrigger, listen |
| 2 | from requests import get, post |
| 3 | from datetime import datetime, timedelta, date |
| 4 | from re import findall |
| 5 | from os import environ |
| 6 | |
| 7 | bot = Client(token=environ["BOT_TOKEN"]) |
| 8 | |
| 9 | is_recurring = True |
| 10 | owner_username = "alyssile" |
| 11 | officer_role_suffix = "Officer" |
| 12 | # add IDs for guilds you want the bot to register commands in |
| 13 | guilds = [353071720342487040, 1038346577267392582] |
| 14 | role_housel = 1149914848428294315 |
| 15 | role_weekly = 1149914607822061599 |
| 16 | role_tribal = 1149914662197010522 |
| 17 | role_grandc = 1149914810486632488 |
| 18 | role_jumboc = 1149929534129459290 |
| 19 | role_fashio = 1212403886263115786 |
| 20 | # role_testing = 873495029883236392 # TODO |
| 21 | |
| 22 | async def check_officer(ctx: SlashContext): |
| 23 | return ctx.author.username == owner_username or any(role.name[-len(officer_role_suffix):] == officer_role_suffix for role in ctx.author.roles) |
| 24 | |
| 25 | #@slash_command(name="xivtimers", description="Show current timers", scopes=guilds) |
| 26 | #@slash_option(name="clear", description="Clears the previous message from the channel", required=False, opt_type=OptionType.BOOLEAN) |
| 27 | #@slash_option(name="recur", description="Sets up the command to refresh the embed automatically each time one of the timers expires", required=False, opt_type=OptionType.BOOLEAN) |
| 28 | #@check(check=check_officer) |
| 29 | #@auto_defer() |
| 30 | #async def xivtimers_cmd(ctx: SlashContext, clear: bool=False, recur: bool=False): |
| 31 | # global is_recurring |
| 32 | # if is_recurring: |
| 33 | # await ctx.send(f"There's already a recurring channel update task running... check with {owner_username}", delete_after=10, ephemeral=True) |
| 34 | # return |
| 35 | # await xivtimers(ctx.channel_id, clear, recur) |
| 36 | |
| 37 | async def xivtimers(channel_id, clear: bool=False, recur: bool=False, mention: str=""): |
| 38 | global is_recurring |
| 39 | channel = bot.get_channel(channel_id) |
| 40 | now = datetime.now() |
| 41 | data = get("https://www.xenoveritas.org/static/ffxiv/timers.json").json() |
| 42 | embed = Embed(title="Timers", color="#eb144c") |
| 43 | timers = {} |
| 44 | maintenance = False |
| 45 | #print('data["timers"]', data["timers"]) |
| 46 | for timer in data["timers"]: |
| 47 | if not any(label in timer for label in ["start", "end", "end_label"]): |
| 48 | print(timer) |
| 49 | continue |
| 50 | if timer.get('end',0) and (now-timedelta(hours=6)).timestamp() > int(timer.get('end',0)/1000): |
| 51 | continue |
| 52 | end_label = timer.get("endLabel", f"<t:{int(timer.get('end',0)/1000)}:R>") |
| 53 | ts = datetime.fromtimestamp(int(timer.get('start',0)/1000)) |
| 54 | if "<a href=" in timer["name"]: |
| 55 | name = timer["name"].split('">')[1].split("<")[0] |
| 56 | else: |
| 57 | name = timer["name"] |
| 58 | if "all worlds maintenance" in name.lower() and now > ts and now < datetime.fromtimestamp(int(timer.get('end',0)/1000)): |
| 59 | maintenance = True |
| 60 | timers["Maintenance complete"] = datetime.fromtimestamp(int(timer.get('end',0)/1000)) |
| 61 | if now < ts: |
| 62 | timers[name] = ts |
| 63 | embed.add_field(name=name, value=f"Start{'s' if now.timestamp() < int(timer.get('start',0)/1000) else 'ed'} <t:{int(timer.get('start',0)/1000)}:R>, {'runs' if 'endLabel' in timer else 'ends'} {end_label}") |
| 64 | |
| 65 | if maintenance: |
| 66 | embed.title = "Timers (Maintenance)" |
| 67 | |
| 68 | def generate_field(name: str, hour: int, role: int=None, day: int=None): |
| 69 | nonlocal timers, now |
| 70 | if (day is not None and now.weekday() == day) and now.hour >= hour: |
| 71 | _now = now + timedelta(1) |
| 72 | elif now.hour >= hour: |
| 73 | _now = now + timedelta(1) |
| 74 | else: |
| 75 | _now = now |
| 76 | reset = datetime.combine(_now, datetime.min.time())+timedelta(hours=hour) |
| 77 | if day is not None: |
| 78 | reset += timedelta((day-_now.weekday())%7) |
| 79 | timers[f"<@&{role}>"] = reset |
| 80 | return {"name": name, "value": f"<t:{int(reset.timestamp())}:R>", "inline": True} |
| 81 | |
| 82 | # embed.add_field(name="Patch 6.5", value=f"<t:{int(datetime(2023,10,3,19,00).timestamp())}:R>") |
| 83 | |
| 84 | if not maintenance: |
| 85 | housing_start_date = datetime(2022,5,27,1,0) |
| 86 | if now.hour >= 1: |
| 87 | _now = now+timedelta(1) |
| 88 | else: |
| 89 | _now = now |
| 90 | mod = (_now - housing_start_date).days % 9 |
| 91 | house_reset = datetime.combine(_now, datetime.min.time())+timedelta(abs(mod-5 if mod <= 5 else mod-9))+timedelta(hours=1) |
| 92 | timers[f"<@&{role_housel}>"] = house_reset |
| 93 | embed.add_field(name="Housing " + ("Results ending" if mod > 5 else "Lottery ending"), value=f"<t:{int(house_reset.timestamp())}:R>", inline=True) |
| 94 | |
| 95 | embed.add_field(**generate_field("Weekly Reset", 19, day=1, role=role_weekly)) |
| 96 | embed.add_field(**generate_field("Fashion Report Judging", 19, day=4, role=role_fashio)) |
| 97 | embed.add_field(**generate_field("Tribal/Duty Reset", 1, role=role_tribal)) |
| 98 | embed.add_field(**generate_field("Grand Company Reset", 6, role=role_grandc)) |
| 99 | embed.add_field(**generate_field("Jumbo Cactpot", 19, day=5, role=role_jumboc)) |
| 100 | |
| 101 | #print("\n".join(f"{t:%Y/%m/%d %H:%M:%S}" for t in timers.values())) |
| 102 | |
| 103 | async for message in channel.history(limit=2 if not is_recurring else 1): |
| 104 | print(message) |
| 105 | if message.author == bot.user.id: |
| 106 | await message.delete() |
| 107 | |
| 108 | # timers[f"<@&{role_testing}>"] = datetime.now()+timedelta(seconds=20) # TODO |
| 109 | |
| 110 | _mention = mention |
| 111 | mention, earliest = min(timers.items(), key=lambda x: x[1]) |
| 112 | embed.add_field(name="Refreshing", value=f"<t:{int(earliest.timestamp())}:R>", inline=True) |
| 113 | |
| 114 | await channel.send(content=_mention, embed=embed) |
| 115 | |
| 116 | print(f"Setting up timer at {earliest:%Y/%m/%d %H:%M:%S} in {channel_id}") |
| 117 | @Task.create(DateTrigger(earliest)) |
| 118 | async def recurring(): |
| 119 | await xivtimers(channel_id, clear, recur, mention=mention) |
| 120 | recurring.start() |
| 121 | |
| 122 | @listen() |
| 123 | async def on_startup(): |
| 124 | print("Bot started") |
| 125 | await xivtimers(1063529561486270494, True, True) |
| 126 | await (bot.get_channel("1127730623818256384")).send("xivtimers restarted") |
| 127 | # channel = bot.get_channel("1127730623818256384") |
| 128 | # await channel.send("Bot restarted, please run `/xivtimers clear:True recur:True` in <#1063529561486270494> (<@133057442425602048>)") |
| 129 | |
| 130 | bot.start() |
docker-compose.yml
· 204 B · YAML
Raw
services:
bot:
image: ghcr.io/alyssadev/python-discord-bot:latest
environment:
- BOT_TOKEN=
volumes:
- .:/usr/src/app
- /etc/localtime:/etc/localtime:ro
restart: always
| 1 | services: |
| 2 | bot: |
| 3 | image: ghcr.io/alyssadev/python-discord-bot:latest |
| 4 | environment: |
| 5 | - BOT_TOKEN= |
| 6 | volumes: |
| 7 | - .:/usr/src/app |
| 8 | - /etc/localtime:/etc/localtime:ro |
| 9 | restart: always |