Merge branch 'master' into cogguide_develop
This commit is contained in:
commit
8ed622b1a9
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@ -6,7 +6,7 @@
|
|||||||
# https://github.com/actions/labeler
|
# https://github.com/actions/labeler
|
||||||
|
|
||||||
name: Labeler
|
name: Labeler
|
||||||
on: [pull_request]
|
on: [pull_request_target]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
label:
|
||||||
|
@ -292,13 +292,13 @@ class CCRole(commands.Cog):
|
|||||||
# Thank you Cog-Creators
|
# Thank you Cog-Creators
|
||||||
|
|
||||||
cmd = ctx.invoked_with
|
cmd = ctx.invoked_with
|
||||||
cmd = cmd.lower() # Continues the proud case_insentivity tradition of ccrole
|
cmd = cmd.lower() # Continues the proud case-insensitivity tradition of ccrole
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
# message = ctx.message # Unneeded since switch to `on_message_without_command` from `on_command_error`
|
# message = ctx.message # Unneeded since switch to `on_message_without_command` from `on_command_error`
|
||||||
|
|
||||||
cmdlist = self.config.guild(guild).cmdlist
|
cmd_list = self.config.guild(guild).cmdlist
|
||||||
# cmd = message.content[len(prefix) :].split()[0].lower()
|
# cmd = message.content[len(prefix) :].split()[0].lower()
|
||||||
cmd = await cmdlist.get_raw(cmd, default=None)
|
cmd = await cmd_list.get_raw(cmd, default=None)
|
||||||
|
|
||||||
if cmd is not None:
|
if cmd is not None:
|
||||||
await self.eval_cc(cmd, message, ctx)
|
await self.eval_cc(cmd, message, ctx)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"Bobloy"
|
"Bobloy"
|
||||||
],
|
],
|
||||||
"min_bot_version": "3.4.0",
|
"min_bot_version": "3.4.0",
|
||||||
"description": "Create an offline chatbot that talks like your average member using Machine Learning",
|
"description": "Create an offline chatbot that talks like your average member using Machine Learning. See setup instructions at https://github.com/bobloy/Fox-V3/tree/master/chatter",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"install_msg": "Thank you for installing Chatter! Please make sure you check the install instructions at https://github.com/bobloy/Fox-V3/blob/master/chatter/README.md\nAfter that, get started ith `[p]load chatter` and `[p]help Chatter`",
|
"install_msg": "Thank you for installing Chatter! Please make sure you check the install instructions at https://github.com/bobloy/Fox-V3/blob/master/chatter/README.md\nAfter that, get started ith `[p]load chatter` and `[p]help Chatter`",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
from .fifo import FIFO
|
from .fifo import FIFO
|
||||||
|
|
||||||
|
# Applying fix from: https://github.com/Azure/azure-functions-python-worker/issues/640
|
||||||
|
# [Fix] Create a wrapper for importing imgres
|
||||||
|
from .date_trigger import *
|
||||||
|
from . import CustomDateTrigger
|
||||||
|
|
||||||
|
# [Fix] Register imgres into system modules
|
||||||
|
sys.modules["CustomDateTrigger"] = CustomDateTrigger
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
cog = FIFO(bot)
|
cog = FIFO(bot)
|
||||||
|
10
fifo/date_trigger.py
Normal file
10
fifo/date_trigger.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from apscheduler.triggers.date import DateTrigger
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDateTrigger(DateTrigger):
|
||||||
|
def get_next_fire_time(self, previous_fire_time, now):
|
||||||
|
next_run = super().get_next_fire_time(previous_fire_time, now)
|
||||||
|
return next_run if next_run is not None and next_run >= now else None
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {"version": 1, "run_date": self.run_date}
|
49
fifo/fifo.py
49
fifo/fifo.py
@ -4,6 +4,7 @@ from datetime import MAXYEAR, datetime, timedelta, tzinfo
|
|||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import pytz
|
||||||
from apscheduler.job import Job
|
from apscheduler.job import Job
|
||||||
from apscheduler.jobstores.base import JobLookupError
|
from apscheduler.jobstores.base import JobLookupError
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
@ -51,7 +52,7 @@ def _get_run_times(job: Job, now: datetime = None):
|
|||||||
|
|
||||||
if now is None:
|
if now is None:
|
||||||
now = datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, tzinfo=job.next_run_time.tzinfo)
|
now = datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999, tzinfo=job.next_run_time.tzinfo)
|
||||||
yield from _get_run_times(job, now)
|
yield from _get_run_times(job, now) # Recursion
|
||||||
raise StopIteration()
|
raise StopIteration()
|
||||||
|
|
||||||
next_run_time = job.next_run_time
|
next_run_time = job.next_run_time
|
||||||
@ -145,28 +146,39 @@ class FIFO(commands.Cog):
|
|||||||
await task.delete_self()
|
await task.delete_self()
|
||||||
|
|
||||||
async def _process_task(self, task: Task):
|
async def _process_task(self, task: Task):
|
||||||
job: Union[Job, None] = await self._get_job(task)
|
# None of this is necessar, we have `replace_existing` already
|
||||||
if job is not None:
|
# job: Union[Job, None] = await self._get_job(task)
|
||||||
job.reschedule(await task.get_combined_trigger())
|
# if job is not None:
|
||||||
return job
|
# combined_trigger_ = await task.get_combined_trigger()
|
||||||
|
# if combined_trigger_ is None:
|
||||||
|
# job.remove()
|
||||||
|
# else:
|
||||||
|
# job.reschedule(combined_trigger_)
|
||||||
|
# return job
|
||||||
return await self._add_job(task)
|
return await self._add_job(task)
|
||||||
|
|
||||||
async def _get_job(self, task: Task) -> Job:
|
async def _get_job(self, task: Task) -> Job:
|
||||||
return self.scheduler.get_job(_assemble_job_id(task.name, task.guild_id))
|
return self.scheduler.get_job(_assemble_job_id(task.name, task.guild_id))
|
||||||
|
|
||||||
async def _add_job(self, task: Task):
|
async def _add_job(self, task: Task):
|
||||||
|
combined_trigger_ = await task.get_combined_trigger()
|
||||||
|
if combined_trigger_ is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return self.scheduler.add_job(
|
return self.scheduler.add_job(
|
||||||
_execute_task,
|
_execute_task,
|
||||||
kwargs=task.__getstate__(),
|
kwargs=task.__getstate__(),
|
||||||
id=_assemble_job_id(task.name, task.guild_id),
|
id=_assemble_job_id(task.name, task.guild_id),
|
||||||
trigger=await task.get_combined_trigger(),
|
trigger=combined_trigger_,
|
||||||
name=task.name,
|
name=task.name,
|
||||||
|
replace_existing=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _resume_job(self, task: Task):
|
async def _resume_job(self, task: Task):
|
||||||
try:
|
job: Union[Job, None] = await self._get_job(task)
|
||||||
job = self.scheduler.resume_job(job_id=_assemble_job_id(task.name, task.guild_id))
|
if job is not None:
|
||||||
except JobLookupError:
|
job.resume()
|
||||||
|
else:
|
||||||
job = await self._process_task(task)
|
job = await self._process_task(task)
|
||||||
return job
|
return job
|
||||||
|
|
||||||
@ -221,6 +233,17 @@ class FIFO(commands.Cog):
|
|||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@fifo.command(name="wakeup")
|
||||||
|
async def fifo_wakeup(self, ctx: commands.Context):
|
||||||
|
"""Debug command to fix missed executions.
|
||||||
|
|
||||||
|
If you see a negative "Next run time" when adding a trigger, this may help resolve it.
|
||||||
|
Check the logs when using this command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.scheduler.wakeup()
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
@fifo.command(name="checktask", aliases=["checkjob", "check"])
|
@fifo.command(name="checktask", aliases=["checkjob", "check"])
|
||||||
async def fifo_checktask(self, ctx: commands.Context, task_name: str):
|
async def fifo_checktask(self, ctx: commands.Context, task_name: str):
|
||||||
"""Returns the next 10 scheduled executions of the task"""
|
"""Returns the next 10 scheduled executions of the task"""
|
||||||
@ -372,10 +395,14 @@ class FIFO(commands.Cog):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
embed.add_field(name="Server", value="Server not found", inline=False)
|
embed.add_field(name="Server", value="Server not found", inline=False)
|
||||||
|
triggers, expired_triggers = await task.get_triggers()
|
||||||
|
|
||||||
trigger_str = "\n".join(str(t) for t in await task.get_triggers())
|
trigger_str = "\n".join(str(t) for t in triggers)
|
||||||
|
expired_str = "\n".join(str(t) for t in expired_triggers)
|
||||||
if trigger_str:
|
if trigger_str:
|
||||||
embed.add_field(name="Triggers", value=trigger_str, inline=False)
|
embed.add_field(name="Triggers", value=trigger_str, inline=False)
|
||||||
|
if expired_str:
|
||||||
|
embed.add_field(name="Expired Triggers", value=expired_str, inline=False)
|
||||||
|
|
||||||
job = await self._get_job(task)
|
job = await self._get_job(task)
|
||||||
if job and job.next_run_time:
|
if job and job.next_run_time:
|
||||||
@ -546,7 +573,7 @@ class FIFO(commands.Cog):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
time_to_run = datetime.now() + time_from_now
|
time_to_run = datetime.now(pytz.utc) + time_from_now
|
||||||
|
|
||||||
result = await task.add_trigger("date", time_to_run, time_to_run.tzinfo)
|
result = await task.add_trigger("date", time_to_run, time_to_run.tzinfo)
|
||||||
if not result:
|
if not result:
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
"end_user_data_statement": "This cog does not store any End User Data",
|
"end_user_data_statement": "This cog does not store any End User Data",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"apscheduler",
|
"apscheduler",
|
||||||
"pytz"
|
"pytz",
|
||||||
|
"python-dateutil"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"bobloy",
|
"bobloy",
|
||||||
|
@ -39,7 +39,7 @@ class RedConfigJobStore(MemoryJobStore):
|
|||||||
# self._jobs = [
|
# self._jobs = [
|
||||||
# (await self._decode_job(job), timestamp) async for (job, timestamp) in AsyncIter(_jobs)
|
# (await self._decode_job(job), timestamp) async for (job, timestamp) in AsyncIter(_jobs)
|
||||||
# ]
|
# ]
|
||||||
async for job, timestamp in AsyncIter(_jobs):
|
async for job, timestamp in AsyncIter(_jobs, steps=5):
|
||||||
job = await self._decode_job(job)
|
job = await self._decode_job(job)
|
||||||
index = self._get_job_index(timestamp, job.id)
|
index = self._get_job_index(timestamp, job.id)
|
||||||
self._jobs.insert(index, (job, timestamp))
|
self._jobs.insert(index, (job, timestamp))
|
||||||
@ -109,83 +109,6 @@ class RedConfigJobStore(MemoryJobStore):
|
|||||||
|
|
||||||
return job
|
return job
|
||||||
|
|
||||||
# @run_in_event_loop
|
|
||||||
# def add_job(self, job: Job):
|
|
||||||
# if job.id in self._jobs_index:
|
|
||||||
# raise ConflictingIdError(job.id)
|
|
||||||
# # log.debug(f"Check job args: {job.args=}")
|
|
||||||
# timestamp = datetime_to_utc_timestamp(job.next_run_time)
|
|
||||||
# index = self._get_job_index(timestamp, job.id) # This is fine
|
|
||||||
# self._jobs.insert(index, (job, timestamp))
|
|
||||||
# self._jobs_index[job.id] = (job, timestamp)
|
|
||||||
# task = asyncio.create_task(self._async_add_job(job, index, timestamp))
|
|
||||||
# self._eventloop.run_until_complete(task)
|
|
||||||
# # log.debug(f"Added job: {self._jobs[index][0].args}")
|
|
||||||
#
|
|
||||||
# async def _async_add_job(self, job, index, timestamp):
|
|
||||||
# encoded_job = self._encode_job(job)
|
|
||||||
# job_tuple = tuple([encoded_job, timestamp])
|
|
||||||
# async with self.config.jobs() as jobs:
|
|
||||||
# jobs.insert(index, job_tuple)
|
|
||||||
# # await self.config.jobs_index.set_raw(job.id, value=job_tuple)
|
|
||||||
# return True
|
|
||||||
|
|
||||||
# @run_in_event_loop
|
|
||||||
# def update_job(self, job):
|
|
||||||
# old_tuple: Tuple[Union[Job, None], Union[datetime, None]] = self._jobs_index.get(
|
|
||||||
# job.id, (None, None)
|
|
||||||
# )
|
|
||||||
# old_job = old_tuple[0]
|
|
||||||
# old_timestamp = old_tuple[1]
|
|
||||||
# if old_job is None:
|
|
||||||
# raise JobLookupError(job.id)
|
|
||||||
#
|
|
||||||
# # If the next run time has not changed, simply replace the job in its present index.
|
|
||||||
# # Otherwise, reinsert the job to the list to preserve the ordering.
|
|
||||||
# old_index = self._get_job_index(old_timestamp, old_job.id)
|
|
||||||
# new_timestamp = datetime_to_utc_timestamp(job.next_run_time)
|
|
||||||
# task = asyncio.create_task(
|
|
||||||
# self._async_update_job(job, new_timestamp, old_index, old_job, old_timestamp)
|
|
||||||
# )
|
|
||||||
# self._eventloop.run_until_complete(task)
|
|
||||||
#
|
|
||||||
# async def _async_update_job(self, job, new_timestamp, old_index, old_job, old_timestamp):
|
|
||||||
# encoded_job = self._encode_job(job)
|
|
||||||
# if old_timestamp == new_timestamp:
|
|
||||||
# self._jobs[old_index] = (job, new_timestamp)
|
|
||||||
# async with self.config.jobs() as jobs:
|
|
||||||
# jobs[old_index] = (encoded_job, new_timestamp)
|
|
||||||
# else:
|
|
||||||
# del self._jobs[old_index]
|
|
||||||
# new_index = self._get_job_index(new_timestamp, job.id) # This is fine
|
|
||||||
# self._jobs.insert(new_index, (job, new_timestamp))
|
|
||||||
# async with self.config.jobs() as jobs:
|
|
||||||
# del jobs[old_index]
|
|
||||||
# jobs.insert(new_index, (encoded_job, new_timestamp))
|
|
||||||
# self._jobs_index[old_job.id] = (job, new_timestamp)
|
|
||||||
# # await self.config.jobs_index.set_raw(old_job.id, value=(encoded_job, new_timestamp))
|
|
||||||
#
|
|
||||||
# log.debug(f"Async Updated {job.id=}")
|
|
||||||
# # log.debug(f"Check job args: {job.kwargs=}")
|
|
||||||
|
|
||||||
# @run_in_event_loop
|
|
||||||
# def remove_job(self, job_id):
|
|
||||||
# """Copied instead of super for the asyncio args"""
|
|
||||||
# job, timestamp = self._jobs_index.get(job_id, (None, None))
|
|
||||||
# if job is None:
|
|
||||||
# raise JobLookupError(job_id)
|
|
||||||
#
|
|
||||||
# index = self._get_job_index(timestamp, job_id)
|
|
||||||
# del self._jobs[index]
|
|
||||||
# del self._jobs_index[job.id]
|
|
||||||
# task = asyncio.create_task(self._async_remove_job(index, job))
|
|
||||||
# self._eventloop.run_until_complete(task)
|
|
||||||
#
|
|
||||||
# async def _async_remove_job(self, index, job):
|
|
||||||
# async with self.config.jobs() as jobs:
|
|
||||||
# del jobs[index]
|
|
||||||
# # await self.config.jobs_index.clear_raw(job.id)
|
|
||||||
|
|
||||||
@run_in_event_loop
|
@run_in_event_loop
|
||||||
def remove_all_jobs(self):
|
def remove_all_jobs(self):
|
||||||
super().remove_all_jobs()
|
super().remove_all_jobs()
|
||||||
@ -201,4 +124,5 @@ class RedConfigJobStore(MemoryJobStore):
|
|||||||
|
|
||||||
async def async_shutdown(self):
|
async def async_shutdown(self):
|
||||||
await self.save_to_config()
|
await self.save_to_config()
|
||||||
super().remove_all_jobs()
|
self._jobs = []
|
||||||
|
self._jobs_index = {}
|
||||||
|
207
fifo/task.py
207
fifo/task.py
@ -1,18 +1,19 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Dict, List, Union
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import pytz
|
||||||
from apscheduler.triggers.base import BaseTrigger
|
from apscheduler.triggers.base import BaseTrigger
|
||||||
from apscheduler.triggers.combining import OrTrigger
|
from apscheduler.triggers.combining import OrTrigger
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
from apscheduler.triggers.date import DateTrigger
|
|
||||||
from apscheduler.triggers.interval import IntervalTrigger
|
from apscheduler.triggers.interval import IntervalTrigger
|
||||||
from discord.utils import time_snowflake
|
from discord.utils import time_snowflake
|
||||||
from pytz import timezone
|
|
||||||
from redbot.core import Config, commands
|
from redbot.core import Config, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
from fifo.date_trigger import CustomDateTrigger
|
||||||
|
|
||||||
log = logging.getLogger("red.fox_v3.fifo.task")
|
log = logging.getLogger("red.fox_v3.fifo.task")
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ def get_trigger(data):
|
|||||||
return IntervalTrigger(days=parsed_time.days, seconds=parsed_time.seconds)
|
return IntervalTrigger(days=parsed_time.days, seconds=parsed_time.seconds)
|
||||||
|
|
||||||
if data["type"] == "date":
|
if data["type"] == "date":
|
||||||
return DateTrigger(data["time_data"], timezone=data["tzinfo"])
|
return CustomDateTrigger(data["time_data"], timezone=data["tzinfo"])
|
||||||
|
|
||||||
if data["type"] == "cron":
|
if data["type"] == "cron":
|
||||||
return CronTrigger.from_crontab(data["time_data"], timezone=data["tzinfo"])
|
return CronTrigger.from_crontab(data["time_data"], timezone=data["tzinfo"])
|
||||||
@ -34,20 +35,126 @@ def get_trigger(data):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_expired_trigger(trigger: BaseTrigger):
|
||||||
|
return trigger.get_next_fire_time(None, datetime.now(pytz.utc)) is None
|
||||||
|
|
||||||
|
|
||||||
def parse_triggers(data: Union[Dict, None]):
|
def parse_triggers(data: Union[Dict, None]):
|
||||||
if data is None or not data.get("triggers", False): # No triggers
|
if data is None or not data.get("triggers", False): # No triggers
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if len(data["triggers"]) > 1: # Multiple triggers
|
if len(data["triggers"]) > 1: # Multiple triggers
|
||||||
return OrTrigger([get_trigger(t_data) for t_data in data["triggers"]])
|
triggers_list = [get_trigger(t_data) for t_data in data["triggers"]]
|
||||||
|
triggers_list = [t for t in triggers_list if not check_expired_trigger(t)]
|
||||||
|
if not triggers_list:
|
||||||
|
return None
|
||||||
|
return OrTrigger(triggers_list)
|
||||||
else:
|
else:
|
||||||
return get_trigger(data["triggers"][0])
|
trigger = get_trigger(data["triggers"][0])
|
||||||
|
if check_expired_trigger(trigger):
|
||||||
|
return None
|
||||||
|
return trigger
|
||||||
|
|
||||||
|
|
||||||
class FakeMessage:
|
# class FakeMessage:
|
||||||
def __init__(self, message: discord.Message):
|
# def __init__(self, message: discord.Message):
|
||||||
d = {k: getattr(message, k, None) for k in dir(message)}
|
# d = {k: getattr(message, k, None) for k in dir(message)}
|
||||||
self.__dict__.update(**d)
|
# self.__dict__.update(**d)
|
||||||
|
|
||||||
|
|
||||||
|
# Potential FakeMessage subclass of Message
|
||||||
|
# class DeleteSlots(type):
|
||||||
|
# @classmethod
|
||||||
|
# def __prepare__(metacls, name, bases):
|
||||||
|
# """Borrowed a bit from https://stackoverflow.com/q/56579348"""
|
||||||
|
# super_prepared = super().__prepare__(name, bases)
|
||||||
|
# print(super_prepared)
|
||||||
|
# return super_prepared
|
||||||
|
|
||||||
|
things_for_fakemessage_to_steal = [
|
||||||
|
"_state",
|
||||||
|
"id",
|
||||||
|
"webhook_id",
|
||||||
|
# "reactions",
|
||||||
|
# "attachments",
|
||||||
|
"embeds",
|
||||||
|
"application",
|
||||||
|
"activity",
|
||||||
|
"channel",
|
||||||
|
"_edited_time",
|
||||||
|
"type",
|
||||||
|
"pinned",
|
||||||
|
"flags",
|
||||||
|
"mention_everyone",
|
||||||
|
"tts",
|
||||||
|
"content",
|
||||||
|
"nonce",
|
||||||
|
"reference",
|
||||||
|
]
|
||||||
|
|
||||||
|
things_fakemessage_sets_by_default = {
|
||||||
|
"attachments": [],
|
||||||
|
"reactions": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeMessage(discord.Message):
|
||||||
|
def __init__(self, *args, message: discord.Message, **kwargs):
|
||||||
|
d = {k: getattr(message, k, None) for k in things_for_fakemessage_to_steal}
|
||||||
|
d.update(things_fakemessage_sets_by_default)
|
||||||
|
for k, v in d.items():
|
||||||
|
try:
|
||||||
|
# log.debug(f"{k=} {v=}")
|
||||||
|
setattr(self, k, v)
|
||||||
|
except TypeError:
|
||||||
|
# log.exception("This is fine")
|
||||||
|
pass
|
||||||
|
except AttributeError:
|
||||||
|
# log.exception("This is fine")
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.id = time_snowflake(datetime.utcnow(), high=False) # Pretend to be now
|
||||||
|
self.type = discord.MessageType.default
|
||||||
|
|
||||||
|
def process_the_rest(
|
||||||
|
self,
|
||||||
|
author: discord.Member,
|
||||||
|
channel: discord.TextChannel,
|
||||||
|
content,
|
||||||
|
):
|
||||||
|
# self.content = content
|
||||||
|
# log.debug(self.content)
|
||||||
|
|
||||||
|
# for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'flags'):
|
||||||
|
# try:
|
||||||
|
# getattr(self, '_handle_%s' % handler)(data[handler])
|
||||||
|
# except KeyError:
|
||||||
|
# continue
|
||||||
|
self.author = author
|
||||||
|
# self._handle_author(author._user._to_minimal_user_json())
|
||||||
|
# self._handle_member(author)
|
||||||
|
self._rebind_channel_reference(channel)
|
||||||
|
self._update(
|
||||||
|
{
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self._update(
|
||||||
|
{
|
||||||
|
"mention_roles": self.raw_role_mentions,
|
||||||
|
"mentions": self.raw_mentions,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# self._handle_content(content)
|
||||||
|
# log.debug(self.content)
|
||||||
|
|
||||||
|
self.mention_everyone = "@everyone" in self.content or "@here" in self.content
|
||||||
|
|
||||||
|
# self._handle_mention_roles(self.raw_role_mentions)
|
||||||
|
# self._handle_mentions(self.raw_mentions)
|
||||||
|
|
||||||
|
# self.__dict__.update(**d)
|
||||||
|
|
||||||
|
|
||||||
def neuter_message(message: FakeMessage):
|
def neuter_message(message: FakeMessage):
|
||||||
@ -66,11 +173,11 @@ def neuter_message(message: FakeMessage):
|
|||||||
|
|
||||||
|
|
||||||
class Task:
|
class Task:
|
||||||
default_task_data = {"triggers": [], "command_str": ""}
|
default_task_data = {"triggers": [], "command_str": "", "expired_triggers": []}
|
||||||
|
|
||||||
default_trigger = {
|
default_trigger = {
|
||||||
"type": "",
|
"type": "",
|
||||||
"time_data": None, # Used for Interval and Date Triggers
|
"time_data": None,
|
||||||
"tzinfo": None,
|
"tzinfo": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +194,10 @@ class Task:
|
|||||||
|
|
||||||
async def _encode_time_triggers(self):
|
async def _encode_time_triggers(self):
|
||||||
if not self.data or not self.data.get("triggers", None):
|
if not self.data or not self.data.get("triggers", None):
|
||||||
return []
|
return [], []
|
||||||
|
|
||||||
triggers = []
|
triggers = []
|
||||||
|
expired_triggers = []
|
||||||
for t in self.data["triggers"]:
|
for t in self.data["triggers"]:
|
||||||
if t["type"] == "interval": # Convert into timedelta
|
if t["type"] == "interval": # Convert into timedelta
|
||||||
td: timedelta = t["time_data"]
|
td: timedelta = t["time_data"]
|
||||||
@ -101,13 +209,15 @@ class Task:
|
|||||||
|
|
||||||
if t["type"] == "date": # Convert into datetime
|
if t["type"] == "date": # Convert into datetime
|
||||||
dt: datetime = t["time_data"]
|
dt: datetime = t["time_data"]
|
||||||
triggers.append(
|
data_to_append = {
|
||||||
{
|
|
||||||
"type": t["type"],
|
"type": t["type"],
|
||||||
"time_data": dt.isoformat(),
|
"time_data": dt.isoformat(),
|
||||||
"tzinfo": getattr(t["tzinfo"], "zone", None),
|
"tzinfo": getattr(t["tzinfo"], "zone", None),
|
||||||
}
|
}
|
||||||
)
|
if dt < datetime.now(pytz.utc):
|
||||||
|
expired_triggers.append(data_to_append)
|
||||||
|
else:
|
||||||
|
triggers.append(data_to_append)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if t["type"] == "cron":
|
if t["type"] == "cron":
|
||||||
@ -125,7 +235,7 @@ class Task:
|
|||||||
|
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
return triggers
|
return triggers, expired_triggers
|
||||||
|
|
||||||
async def _decode_time_triggers(self):
|
async def _decode_time_triggers(self):
|
||||||
if not self.data or not self.data.get("triggers", None):
|
if not self.data or not self.data.get("triggers", None):
|
||||||
@ -138,7 +248,7 @@ class Task:
|
|||||||
|
|
||||||
# First decode timezone if there is one
|
# First decode timezone if there is one
|
||||||
if t["tzinfo"] is not None:
|
if t["tzinfo"] is not None:
|
||||||
t["tzinfo"] = timezone(t["tzinfo"])
|
t["tzinfo"] = pytz.timezone(t["tzinfo"])
|
||||||
|
|
||||||
if t["type"] == "interval": # Convert into timedelta
|
if t["type"] == "interval": # Convert into timedelta
|
||||||
t["time_data"] = timedelta(**t["time_data"])
|
t["time_data"] = timedelta(**t["time_data"])
|
||||||
@ -174,14 +284,23 @@ class Task:
|
|||||||
await self._decode_time_triggers()
|
await self._decode_time_triggers()
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
async def get_triggers(self) -> List[Union[IntervalTrigger, DateTrigger]]:
|
async def get_triggers(self) -> Tuple[List[BaseTrigger], List[BaseTrigger]]:
|
||||||
if not self.data:
|
if not self.data:
|
||||||
await self.load_from_config()
|
await self.load_from_config()
|
||||||
|
|
||||||
if self.data is None or "triggers" not in self.data: # No triggers
|
if self.data is None or "triggers" not in self.data: # No triggers
|
||||||
return []
|
return [], []
|
||||||
|
|
||||||
return [get_trigger(t) for t in self.data["triggers"]]
|
trigs = []
|
||||||
|
expired_trigs = []
|
||||||
|
for t in self.data["triggers"]:
|
||||||
|
trig = get_trigger(t)
|
||||||
|
if check_expired_trigger(trig):
|
||||||
|
expired_trigs.append(t)
|
||||||
|
else:
|
||||||
|
trigs.append(t)
|
||||||
|
|
||||||
|
return trigs, expired_trigs
|
||||||
|
|
||||||
async def get_combined_trigger(self) -> Union[BaseTrigger, None]:
|
async def get_combined_trigger(self) -> Union[BaseTrigger, None]:
|
||||||
if not self.data:
|
if not self.data:
|
||||||
@ -201,7 +320,10 @@ class Task:
|
|||||||
data_to_save = self.default_task_data.copy()
|
data_to_save = self.default_task_data.copy()
|
||||||
if self.data:
|
if self.data:
|
||||||
data_to_save["command_str"] = self.get_command_str()
|
data_to_save["command_str"] = self.get_command_str()
|
||||||
data_to_save["triggers"] = await self._encode_time_triggers()
|
(
|
||||||
|
data_to_save["triggers"],
|
||||||
|
data_to_save["expired_triggers"],
|
||||||
|
) = await self._encode_time_triggers()
|
||||||
|
|
||||||
to_save = {
|
to_save = {
|
||||||
"guild_id": self.guild_id,
|
"guild_id": self.guild_id,
|
||||||
@ -217,7 +339,10 @@ class Task:
|
|||||||
return
|
return
|
||||||
|
|
||||||
data_to_save = self.data.copy()
|
data_to_save = self.data.copy()
|
||||||
data_to_save["triggers"] = await self._encode_time_triggers()
|
(
|
||||||
|
data_to_save["triggers"],
|
||||||
|
data_to_save["expired_triggers"],
|
||||||
|
) = await self._encode_time_triggers()
|
||||||
|
|
||||||
await self.config.guild_from_id(self.guild_id).tasks.set_raw(
|
await self.config.guild_from_id(self.guild_id).tasks.set_raw(
|
||||||
self.name, "data", value=data_to_save
|
self.name, "data", value=data_to_save
|
||||||
@ -240,19 +365,23 @@ class Task:
|
|||||||
f"Could not execute Task[{self.name}] due to missing channel: {self.channel_id}"
|
f"Could not execute Task[{self.name}] due to missing channel: {self.channel_id}"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
author: discord.User = guild.get_member(self.author_id)
|
author: discord.Member = guild.get_member(self.author_id)
|
||||||
if author is None:
|
if author is None:
|
||||||
log.warning(
|
log.warning(
|
||||||
f"Could not execute Task[{self.name}] due to missing author: {self.author_id}"
|
f"Could not execute Task[{self.name}] due to missing author: {self.author_id}"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
actual_message: discord.Message = channel.last_message
|
actual_message: Optional[discord.Message] = channel.last_message
|
||||||
# I'd like to present you my chain of increasingly desperate message fetching attempts
|
# I'd like to present you my chain of increasingly desperate message fetching attempts
|
||||||
if actual_message is None:
|
if actual_message is None:
|
||||||
# log.warning("No message found in channel cache yet, skipping execution")
|
# log.warning("No message found in channel cache yet, skipping execution")
|
||||||
# return
|
# return
|
||||||
|
if channel.last_message_id is not None:
|
||||||
|
try:
|
||||||
actual_message = await channel.fetch_message(channel.last_message_id)
|
actual_message = await channel.fetch_message(channel.last_message_id)
|
||||||
|
except discord.NotFound:
|
||||||
|
actual_message = None
|
||||||
if actual_message is None: # last_message_id was an invalid message I guess
|
if actual_message is None: # last_message_id was an invalid message I guess
|
||||||
actual_message = await channel.history(limit=1).flatten()
|
actual_message = await channel.history(limit=1).flatten()
|
||||||
if not actual_message: # Basically only happens if the channel has no messages
|
if not actual_message: # Basically only happens if the channel has no messages
|
||||||
@ -262,22 +391,27 @@ class Task:
|
|||||||
return False
|
return False
|
||||||
actual_message = actual_message[0]
|
actual_message = actual_message[0]
|
||||||
|
|
||||||
message = FakeMessage(actual_message)
|
# message._handle_author(author) # Option when message is subclass
|
||||||
# message = FakeMessage2
|
# message._state = self.bot._get_state()
|
||||||
message.author = author
|
# Time to set the relevant attributes
|
||||||
message.guild = guild # Just in case we got desperate, see above
|
# message.author = author
|
||||||
message.channel = channel
|
# Don't need guild with subclass, guild is just channel.guild
|
||||||
message.id = time_snowflake(datetime.utcnow(), high=False) # Pretend to be now
|
# message.guild = guild # Just in case we got desperate, see above
|
||||||
message = neuter_message(message)
|
# message.channel = channel
|
||||||
|
|
||||||
# absolutely weird that this takes a message object instead of guild
|
# absolutely weird that this takes a message object instead of guild
|
||||||
prefixes = await self.bot.get_prefix(message)
|
prefixes = await self.bot.get_prefix(actual_message)
|
||||||
if isinstance(prefixes, str):
|
if isinstance(prefixes, str):
|
||||||
prefix = prefixes
|
prefix = prefixes
|
||||||
else:
|
else:
|
||||||
prefix = prefixes[0]
|
prefix = prefixes[0]
|
||||||
|
|
||||||
message.content = f"{prefix}{self.get_command_str()}"
|
new_content = f"{prefix}{self.get_command_str()}"
|
||||||
|
# log.debug(f"{new_content=}")
|
||||||
|
|
||||||
|
message = FakeMessage(message=actual_message)
|
||||||
|
message = neuter_message(message)
|
||||||
|
message.process_the_rest(author=author, channel=channel, content=new_content)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not message.guild
|
not message.guild
|
||||||
@ -285,7 +419,10 @@ class Task:
|
|||||||
or not message.content
|
or not message.content
|
||||||
or message.content == prefix
|
or message.content == prefix
|
||||||
):
|
):
|
||||||
log.warning(f"Could not execute Task[{self.name}] due to message problem: {message}")
|
log.warning(
|
||||||
|
f"Could not execute Task[{self.name}] due to message problem: "
|
||||||
|
f"{message.guild=}, {message.author=}, {message.content=}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
new_ctx: commands.Context = await self.bot.get_context(message)
|
new_ctx: commands.Context = await self.bot.get_context(message)
|
||||||
|
@ -453,7 +453,7 @@ class InfoChannel(Cog):
|
|||||||
if channel_type is not None:
|
if channel_type is not None:
|
||||||
return await self.trigger_updates_for(guild, **{channel_type: True})
|
return await self.trigger_updates_for(guild, **{channel_type: True})
|
||||||
|
|
||||||
return await self.trigger_updates_for(guild, extra_roles=set(channel_role))
|
return await self.trigger_updates_for(guild, extra_roles={channel_role})
|
||||||
|
|
||||||
async def start_queue(self, guild_id, identifier):
|
async def start_queue(self, guild_id, identifier):
|
||||||
self._rate_limited_edits[guild_id][identifier] = asyncio.create_task(
|
self._rate_limited_edits[guild_id][identifier] = asyncio.create_task(
|
||||||
|
@ -71,6 +71,7 @@ W1, W2, W5, W6 = Random Werewolf
|
|||||||
N1 = Benign Neutral
|
N1 = Benign Neutral
|
||||||
|
|
||||||
0001-1112T11W112N2
|
0001-1112T11W112N2
|
||||||
|
which translates to
|
||||||
0,0,0,1,11,12,E1,R1,R1,R1,R2,P2
|
0,0,0,1,11,12,E1,R1,R1,R1,R2,P2
|
||||||
|
|
||||||
pre-letter = exact role position
|
pre-letter = exact role position
|
||||||
|
@ -72,6 +72,9 @@ class Role(WolfListener):
|
|||||||
self.blocked = False
|
self.blocked = False
|
||||||
self.properties = {} # Extra data for other roles (i.e. arsonist)
|
self.properties = {} # Extra data for other roles (i.e. arsonist)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__name__}({self.player.__repr__()})"
|
return f"{self.__class__.__name__}({self.player.__repr__()})"
|
||||||
|
|
||||||
@ -86,7 +89,7 @@ class Role(WolfListener):
|
|||||||
|
|
||||||
log.debug(f"Assigned {self} to {player}")
|
log.debug(f"Assigned {self} to {player}")
|
||||||
|
|
||||||
async def get_alignment(self, source=None):
|
async def get_alignment(self, source=None): # TODO: Rework to be "strength" tiers
|
||||||
"""
|
"""
|
||||||
Interaction for powerful access of alignment
|
Interaction for powerful access of alignment
|
||||||
(Village, Werewolf, Other)
|
(Village, Werewolf, Other)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from typing import Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.commands import Cog
|
from redbot.core.commands import Cog
|
||||||
from redbot.core.utils import AsyncIter
|
|
||||||
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
|
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
|
||||||
|
|
||||||
from werewolf.builder import (
|
from werewolf.builder import (
|
||||||
@ -50,15 +49,17 @@ class Werewolf(Cog):
|
|||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
log.debug("Unload called")
|
log.debug("Unload called")
|
||||||
for game in self.games.values():
|
for key in self.games.keys():
|
||||||
del game
|
del self.games[key]
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def buildgame(self, ctx: commands.Context):
|
async def buildgame(self, ctx: commands.Context):
|
||||||
"""
|
"""
|
||||||
Create game codes to run custom games.
|
Create game codes to run custom games.
|
||||||
|
|
||||||
Pick the roles or randomized roles you want to include in a game
|
Pick the roles or randomized roles you want to include in a game.
|
||||||
|
|
||||||
|
Note: The same role can be picked more than once.
|
||||||
"""
|
"""
|
||||||
gb = GameBuilder()
|
gb = GameBuilder()
|
||||||
code = await gb.build_game(ctx)
|
code = await gb.build_game(ctx)
|
||||||
@ -84,9 +85,6 @@ class Werewolf(Cog):
|
|||||||
Lists current guild settings
|
Lists current guild settings
|
||||||
"""
|
"""
|
||||||
valid, role, category, channel, log_channel = await self._get_settings(ctx)
|
valid, role, category, channel, log_channel = await self._get_settings(ctx)
|
||||||
# if not valid:
|
|
||||||
# await ctx.send("Failed to get settings")
|
|
||||||
# return None
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Current Guild Settings",
|
title="Current Guild Settings",
|
||||||
@ -393,7 +391,7 @@ class Werewolf(Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.maybe_send_embed("Role ID not found")
|
await ctx.maybe_send_embed("Role ID not found")
|
||||||
|
|
||||||
async def _get_game(self, ctx: commands.Context, game_code=None) -> Union[Game, None]:
|
async def _get_game(self, ctx: commands.Context, game_code=None) -> Optional[Game]:
|
||||||
guild: discord.Guild = getattr(ctx, "guild", None)
|
guild: discord.Guild = getattr(ctx, "guild", None)
|
||||||
|
|
||||||
if guild is None:
|
if guild is None:
|
||||||
@ -420,7 +418,7 @@ class Werewolf(Cog):
|
|||||||
|
|
||||||
return self.games[guild.id]
|
return self.games[guild.id]
|
||||||
|
|
||||||
async def _game_start(self, game):
|
async def _game_start(self, game: Game):
|
||||||
await game.start()
|
await game.start()
|
||||||
|
|
||||||
async def _get_settings(self, ctx):
|
async def _get_settings(self, ctx):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user