Merge pull request #21 from StudyLions/staging
Top.gg module and general bugfixes
This commit is contained in:
@@ -31,7 +31,7 @@ This feature allows the users to use their coins to schedule a time to study at.
|
|||||||
Not attending prevents everyone in the room from getting the bonus.
|
Not attending prevents everyone in the room from getting the bonus.
|
||||||
- **Study and Work Statistics**
|
- **Study and Work Statistics**
|
||||||
Users can view their daily, weekly, monthly and all-time stats, as well as their study streak.
|
Users can view their daily, weekly, monthly and all-time stats, as well as their study streak.
|
||||||
- `Coming Soon` **Pomodoro Timers**
|
- **Pomodoro Timers**
|
||||||
The bot will show the timer in the title of the study room and play a sound at the start and end of each session.
|
The bot will show the timer in the title of the study room and play a sound at the start and end of each session.
|
||||||
- **Private Study Rooms**
|
- **Private Study Rooms**
|
||||||
Allows the members to create their own private study rooms and invite their friends to join!
|
Allows the members to create their own private study rooms and invite their friends to join!
|
||||||
|
|||||||
88
bot/LionContext.py
Normal file
88
bot/LionContext.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import types
|
||||||
|
|
||||||
|
from cmdClient import Context
|
||||||
|
from cmdClient.logger import log
|
||||||
|
|
||||||
|
|
||||||
|
class LionContext(Context):
|
||||||
|
"""
|
||||||
|
Subclass to allow easy attachment of custom hooks and structure to contexts.
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def util(cls, util_func):
|
||||||
|
"""
|
||||||
|
Decorator to make a utility function available as a Context instance method.
|
||||||
|
Extends the default Context method to add logging and to return the utility function.
|
||||||
|
"""
|
||||||
|
super().util(util_func)
|
||||||
|
log(f"Attached context utility function: {util_func.__name__}")
|
||||||
|
return util_func
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrappable_util(cls, util_func):
|
||||||
|
"""
|
||||||
|
Decorator to add a Wrappable utility function as a Context instance method.
|
||||||
|
"""
|
||||||
|
wrappable = Wrappable(util_func)
|
||||||
|
super().util(wrappable)
|
||||||
|
log(f"Attached wrappable context utility function: {util_func.__name__}")
|
||||||
|
return wrappable
|
||||||
|
|
||||||
|
|
||||||
|
class Wrappable:
|
||||||
|
__slots__ = ('_func', 'wrappers')
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
self._func = func
|
||||||
|
self.wrappers = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __name__(self):
|
||||||
|
return self._func.__name__
|
||||||
|
|
||||||
|
def add_wrapper(self, func, name=None):
|
||||||
|
self.wrappers = self.wrappers or {}
|
||||||
|
name = name or func.__name__
|
||||||
|
self.wrappers[name] = func
|
||||||
|
log(
|
||||||
|
f"Added wrapper '{name}' to Wrappable '{self._func.__name__}'.",
|
||||||
|
context="Wrapping"
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove_wrapper(self, name):
|
||||||
|
if not self.wrappers or name not in self.wrappers:
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot remove non-existent wrapper '{name}' from Wrappable '{self._func.__name__}'"
|
||||||
|
)
|
||||||
|
self.wrappers.pop(name)
|
||||||
|
log(
|
||||||
|
f"Removed wrapper '{name}' from Wrappable '{self._func.__name__}'.",
|
||||||
|
context="Wrapping"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if self.wrappers:
|
||||||
|
return self._wrapped(iter(self.wrappers.values()))(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return self._func(*args, **kwargs)
|
||||||
|
|
||||||
|
def _wrapped(self, iter_wraps):
|
||||||
|
next_wrap = next(iter_wraps, None)
|
||||||
|
if next_wrap:
|
||||||
|
def _func(*args, **kwargs):
|
||||||
|
return next_wrap(self._wrapped(iter_wraps), *args, **kwargs)
|
||||||
|
else:
|
||||||
|
_func = self._func
|
||||||
|
return _func
|
||||||
|
|
||||||
|
def __get__(self, instance, cls=None):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return types.MethodType(self, instance)
|
||||||
|
|
||||||
|
|
||||||
|
# Override the original Context.reply with a wrappable utility
|
||||||
|
reply = LionContext.wrappable_util(Context.reply)
|
||||||
@@ -105,6 +105,9 @@ class LionModule(Module):
|
|||||||
await ctx.reply("I need permission to send embeds in this channel before I can run any commands!")
|
await ctx.reply("I need permission to send embeds in this channel before I can run any commands!")
|
||||||
raise SafeCancellation(details='I cannot send embeds in this channel.')
|
raise SafeCancellation(details='I cannot send embeds in this channel.')
|
||||||
|
|
||||||
|
# Ensure Lion exists and cached data is up to date
|
||||||
|
ctx.alion.update_saved_data(ctx.author)
|
||||||
|
|
||||||
# Start typing
|
# Start typing
|
||||||
await ctx.ch.trigger_typing()
|
await ctx.ch.trigger_typing()
|
||||||
|
|
||||||
|
|||||||
Submodule bot/cmdClient updated: 6eb4269034...a6ece4cb02
@@ -1,2 +1,2 @@
|
|||||||
CONFIG_FILE = "config/bot.conf"
|
CONFIG_FILE = "config/bot.conf"
|
||||||
DATA_VERSION = 8
|
DATA_VERSION = 9
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ meta = RowTable(
|
|||||||
|
|
||||||
user_config = RowTable(
|
user_config = RowTable(
|
||||||
'user_config',
|
'user_config',
|
||||||
('userid', 'timezone'),
|
('userid', 'timezone', 'topgg_vote_reminder', 'avatar_hash'),
|
||||||
'userid',
|
'userid',
|
||||||
cache=TTLCache(5000, ttl=60*5)
|
cache=TTLCache(5000, ttl=60*5)
|
||||||
)
|
)
|
||||||
@@ -33,7 +33,8 @@ guild_config = RowTable(
|
|||||||
'video_studyban', 'video_grace_period',
|
'video_studyban', 'video_grace_period',
|
||||||
'greeting_channel', 'greeting_message', 'returning_message',
|
'greeting_channel', 'greeting_message', 'returning_message',
|
||||||
'starting_funds', 'persist_roles',
|
'starting_funds', 'persist_roles',
|
||||||
'pomodoro_channel'),
|
'pomodoro_channel',
|
||||||
|
'name'),
|
||||||
'guildid',
|
'guildid',
|
||||||
cache=TTLCache(2500, ttl=60*5)
|
cache=TTLCache(2500, ttl=60*5)
|
||||||
)
|
)
|
||||||
@@ -51,6 +52,7 @@ lions = RowTable(
|
|||||||
'revision_mute_count',
|
'revision_mute_count',
|
||||||
'last_study_badgeid',
|
'last_study_badgeid',
|
||||||
'video_warned',
|
'video_warned',
|
||||||
|
'display_name',
|
||||||
'_timestamp'
|
'_timestamp'
|
||||||
),
|
),
|
||||||
('guildid', 'userid'),
|
('guildid', 'userid'),
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import pytz
|
import pytz
|
||||||
|
import discord
|
||||||
|
from functools import reduce
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from meta import client
|
from meta import client
|
||||||
@@ -20,6 +22,9 @@ class Lion:
|
|||||||
# Lion cache. Currently lions don't expire
|
# Lion cache. Currently lions don't expire
|
||||||
_lions = {} # (guildid, userid) -> Lion
|
_lions = {} # (guildid, userid) -> Lion
|
||||||
|
|
||||||
|
# Extra methods supplying an economy bonus
|
||||||
|
_economy_bonuses = []
|
||||||
|
|
||||||
def __init__(self, guildid, userid):
|
def __init__(self, guildid, userid):
|
||||||
self.guildid = guildid
|
self.guildid = guildid
|
||||||
self.userid = userid
|
self.userid = userid
|
||||||
@@ -44,6 +49,8 @@ class Lion:
|
|||||||
# TODO: Debug log
|
# TODO: Debug log
|
||||||
lion = tb.lions.fetch(key)
|
lion = tb.lions.fetch(key)
|
||||||
if not lion:
|
if not lion:
|
||||||
|
tb.user_config.fetch_or_create(userid)
|
||||||
|
tb.guild_config.fetch_or_create(guildid)
|
||||||
tb.lions.create_row(
|
tb.lions.create_row(
|
||||||
guildid=guildid,
|
guildid=guildid,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
@@ -71,10 +78,24 @@ class Lion:
|
|||||||
@property
|
@property
|
||||||
def data(self):
|
def data(self):
|
||||||
"""
|
"""
|
||||||
The Row corresponding to this user.
|
The Row corresponding to this member.
|
||||||
"""
|
"""
|
||||||
return tb.lions.fetch(self.key)
|
return tb.lions.fetch(self.key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_data(self):
|
||||||
|
"""
|
||||||
|
The Row corresponding to this user.
|
||||||
|
"""
|
||||||
|
return tb.user_config.fetch_or_create(self.userid)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def guild_data(self):
|
||||||
|
"""
|
||||||
|
The Row corresponding to this guild.
|
||||||
|
"""
|
||||||
|
return tb.guild_config.fetch_or_create(self.guildid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def settings(self):
|
def settings(self):
|
||||||
"""
|
"""
|
||||||
@@ -120,6 +141,24 @@ class Lion:
|
|||||||
|
|
||||||
return int(coins)
|
return int(coins)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def economy_bonus(self):
|
||||||
|
"""
|
||||||
|
Economy multiplier
|
||||||
|
"""
|
||||||
|
return reduce(
|
||||||
|
lambda x, y: x * y,
|
||||||
|
[func(self) for func in self._economy_bonuses]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_economy_bonus(cls, func):
|
||||||
|
cls._economy_bonuses.append(func)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister_economy_bonus(cls, func):
|
||||||
|
cls._economy_bonuses.remove(func)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self):
|
def session(self):
|
||||||
"""
|
"""
|
||||||
@@ -207,6 +246,33 @@ class Lion:
|
|||||||
|
|
||||||
return remaining
|
return remaining
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
Returns the best local name possible.
|
||||||
|
"""
|
||||||
|
if self.member:
|
||||||
|
name = self.member.display_name
|
||||||
|
elif self.data.display_name:
|
||||||
|
name = self.data.display_name
|
||||||
|
else:
|
||||||
|
name = str(self.userid)
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def update_saved_data(self, member: discord.Member):
|
||||||
|
"""
|
||||||
|
Update the stored discord data from the givem member.
|
||||||
|
Intended to be used when we get member data from events that may not be available in cache.
|
||||||
|
"""
|
||||||
|
if self.guild_data.name != member.guild.name:
|
||||||
|
self.guild_data.name = member.guild.name
|
||||||
|
if self.user_data.avatar_hash != member.avatar:
|
||||||
|
self.user_data.avatar_hash = member.avatar
|
||||||
|
if self.data.display_name != member.display_name:
|
||||||
|
self.data.display_name = member.display_name
|
||||||
|
|
||||||
def localize(self, naive_utc_dt):
|
def localize(self, naive_utc_dt):
|
||||||
"""
|
"""
|
||||||
Localise the provided naive UTC datetime into the user's timezone.
|
Localise the provided naive UTC datetime into the user's timezone.
|
||||||
@@ -214,11 +280,11 @@ class Lion:
|
|||||||
timezone = self.settings.timezone.value
|
timezone = self.settings.timezone.value
|
||||||
return naive_utc_dt.replace(tzinfo=pytz.UTC).astimezone(timezone)
|
return naive_utc_dt.replace(tzinfo=pytz.UTC).astimezone(timezone)
|
||||||
|
|
||||||
def addCoins(self, amount, flush=True):
|
def addCoins(self, amount, flush=True, ignorebonus=False):
|
||||||
"""
|
"""
|
||||||
Add coins to the user, optionally store the transaction in pending.
|
Add coins to the user, optionally store the transaction in pending.
|
||||||
"""
|
"""
|
||||||
self._pending_coins += amount
|
self._pending_coins += amount * (1 if ignorebonus else self.economy_bonus)
|
||||||
self._pending[self.key] = self
|
self._pending[self.key] = self
|
||||||
if flush:
|
if flush:
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ def _format_selectkeys(keys):
|
|||||||
"""
|
"""
|
||||||
if not keys:
|
if not keys:
|
||||||
return "*"
|
return "*"
|
||||||
|
elif type(keys) is str:
|
||||||
|
return keys
|
||||||
else:
|
else:
|
||||||
return ", ".join(keys)
|
return ", ".join(keys)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from cmdClient.cmdClient import cmdClient
|
|||||||
|
|
||||||
from .config import conf
|
from .config import conf
|
||||||
from .sharding import shard_number, shard_count
|
from .sharding import shard_number, shard_count
|
||||||
|
from LionContext import LionContext
|
||||||
|
|
||||||
# Initialise client
|
# Initialise client
|
||||||
owners = [int(owner) for owner in conf.bot.getlist('owners')]
|
owners = [int(owner) for owner in conf.bot.getlist('owners')]
|
||||||
@@ -14,6 +14,7 @@ client = cmdClient(
|
|||||||
owners=owners,
|
owners=owners,
|
||||||
intents=intents,
|
intents=intents,
|
||||||
shard_id=shard_number,
|
shard_id=shard_number,
|
||||||
shard_count=shard_count
|
shard_count=shard_count,
|
||||||
|
baseContext=LionContext
|
||||||
)
|
)
|
||||||
client.conf = conf
|
client.conf = conf
|
||||||
|
|||||||
@@ -1,8 +1,38 @@
|
|||||||
|
from discord import PartialEmoji
|
||||||
import configparser as cfgp
|
import configparser as cfgp
|
||||||
|
|
||||||
from .args import args
|
from .args import args
|
||||||
|
|
||||||
|
|
||||||
|
class configEmoji(PartialEmoji):
|
||||||
|
__slots__ = ('fallback',)
|
||||||
|
|
||||||
|
def __init__(self, *args, fallback=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fallback = fallback
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, emojistr: str):
|
||||||
|
"""
|
||||||
|
Parses emoji strings of one of the following forms
|
||||||
|
`<a:name:id> or fallback`
|
||||||
|
`<:name:id> or fallback`
|
||||||
|
`<a:name:id>`
|
||||||
|
`<:name:id>`
|
||||||
|
"""
|
||||||
|
splits = emojistr.rsplit(' or ', maxsplit=1)
|
||||||
|
|
||||||
|
fallback = splits[1] if len(splits) > 1 else None
|
||||||
|
emojistr = splits[0].strip('<> ')
|
||||||
|
animated, name, id = emojistr.split(':')
|
||||||
|
return cls(
|
||||||
|
name=name,
|
||||||
|
fallback=PartialEmoji(name=fallback),
|
||||||
|
animated=bool(animated),
|
||||||
|
id=int(id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Conf:
|
class Conf:
|
||||||
def __init__(self, configfile, section_name="DEFAULT"):
|
def __init__(self, configfile, section_name="DEFAULT"):
|
||||||
self.configfile = configfile
|
self.configfile = configfile
|
||||||
@@ -11,6 +41,7 @@ class Conf:
|
|||||||
converters={
|
converters={
|
||||||
"intlist": self._getintlist,
|
"intlist": self._getintlist,
|
||||||
"list": self._getlist,
|
"list": self._getlist,
|
||||||
|
"emoji": configEmoji.from_str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.config.read(configfile)
|
self.config.read(configfile)
|
||||||
@@ -20,6 +51,7 @@ class Conf:
|
|||||||
self.default = self.config["DEFAULT"]
|
self.default = self.config["DEFAULT"]
|
||||||
self.section = self.config[self.section_name]
|
self.section = self.config[self.section_name]
|
||||||
self.bot = self.section
|
self.bot = self.section
|
||||||
|
self.emojis = self.config['EMOJIS'] if 'EMOJIS' in self.config else self.section
|
||||||
|
|
||||||
# Config file recursion, read in configuration files specified in every "ALSO_READ" key.
|
# Config file recursion, read in configuration files specified in every "ALSO_READ" key.
|
||||||
more_to_read = self.section.getlist("ALSO_READ", [])
|
more_to_read = self.section.getlist("ALSO_READ", [])
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from .stats import *
|
|||||||
from .user_config import *
|
from .user_config import *
|
||||||
from .workout import *
|
from .workout import *
|
||||||
from .todo import *
|
from .todo import *
|
||||||
|
from .topgg import *
|
||||||
from .reminders import *
|
from .reminders import *
|
||||||
from .renting import *
|
from .renting import *
|
||||||
from .moderation import *
|
from .moderation import *
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class TimeSlot:
|
|||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
timestamp=self.start_time
|
timestamp=self.start_time
|
||||||
).set_footer(
|
).set_footer(
|
||||||
text="About to start!\nJoin the session with {}rooms book".format(client.prefix)
|
text="About to start!\nJoin the session with {}schedule book".format(client.prefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.members:
|
if self.members:
|
||||||
@@ -111,7 +111,7 @@ class TimeSlot:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
embed.description = "No members booked for this session!"
|
embed.description = "No members scheduled this session!"
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ class TimeSlot:
|
|||||||
description="Finishing <t:{}:R>.".format(timestamp + 3600),
|
description="Finishing <t:{}:R>.".format(timestamp + 3600),
|
||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
timestamp=self.start_time
|
timestamp=self.start_time
|
||||||
).set_footer(text="Join the next session using {}rooms book".format(client.prefix))
|
).set_footer(text="Join the next session using {}schedule book".format(client.prefix))
|
||||||
|
|
||||||
if self.members:
|
if self.members:
|
||||||
classifications = {
|
classifications = {
|
||||||
@@ -158,7 +158,7 @@ class TimeSlot:
|
|||||||
if value:
|
if value:
|
||||||
embed.add_field(name=field, value='\n'.join(value))
|
embed.add_field(name=field, value='\n'.join(value))
|
||||||
else:
|
else:
|
||||||
embed.description = "No members booked for this session!"
|
embed.description = "No members scheduled this session!"
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ class TimeSlot:
|
|||||||
if value:
|
if value:
|
||||||
embed.add_field(name=field, value='\n'.join(value))
|
embed.add_field(name=field, value='\n'.join(value))
|
||||||
else:
|
else:
|
||||||
embed.description = "No members booked this session!"
|
embed.description = "No members scheduled this session!"
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
@@ -316,13 +316,13 @@ class TimeSlot:
|
|||||||
if self.data and not self.channel:
|
if self.data and not self.channel:
|
||||||
try:
|
try:
|
||||||
self.channel = await self.guild.create_voice_channel(
|
self.channel = await self.guild.create_voice_channel(
|
||||||
"Upcoming Accountability Study Room",
|
"Upcoming Scheduled Session",
|
||||||
overwrites=overwrites,
|
overwrites=overwrites,
|
||||||
category=self.category
|
category=self.category
|
||||||
)
|
)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
GuildSettings(self.guild.id).event_log.log(
|
GuildSettings(self.guild.id).event_log.log(
|
||||||
"Failed to create the accountability voice channel. Skipping this session.",
|
"Failed to create the scheduled session voice channel. Skipping this session.",
|
||||||
colour=discord.Colour.red()
|
colour=discord.Colour.red()
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -337,7 +337,7 @@ class TimeSlot:
|
|||||||
)
|
)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
GuildSettings(self.guild.id).event_log.log(
|
GuildSettings(self.guild.id).event_log.log(
|
||||||
"Failed to post the status message in the accountability lobby {}.\n"
|
"Failed to post the status message in the scheduled session lobby {}.\n"
|
||||||
"Skipping this session.".format(self.lobby.mention),
|
"Skipping this session.".format(self.lobby.mention),
|
||||||
colour=discord.Colour.red()
|
colour=discord.Colour.red()
|
||||||
)
|
)
|
||||||
@@ -351,7 +351,7 @@ class TimeSlot:
|
|||||||
Ghost pings the session members in the lobby channel.
|
Ghost pings the session members in the lobby channel.
|
||||||
"""
|
"""
|
||||||
if self.members:
|
if self.members:
|
||||||
content = content or "Your accountability session has opened! Please join!"
|
content = content or "Your scheduled session has started! Please join!"
|
||||||
out = "{}\n\n{}".format(
|
out = "{}\n\n{}".format(
|
||||||
content,
|
content,
|
||||||
' '.join('<@{}>'.format(memid) for memid, mem in self.members.items() if not mem.has_attended)
|
' '.join('<@{}>'.format(memid) for memid, mem in self.members.items() if not mem.has_attended)
|
||||||
@@ -366,7 +366,7 @@ class TimeSlot:
|
|||||||
"""
|
"""
|
||||||
if self.channel:
|
if self.channel:
|
||||||
try:
|
try:
|
||||||
await self.channel.edit(name="Accountability Study Room")
|
await self.channel.edit(name="Scheduled Session Room")
|
||||||
await self.channel.set_permissions(self.guild.default_role, view_channel=True, connect=False)
|
await self.channel.set_permissions(self.guild.default_role, view_channel=True, connect=False)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
@@ -388,7 +388,7 @@ class TimeSlot:
|
|||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Your accountability session has started!",
|
title="The scheduled session you booked has started!",
|
||||||
description="Please join {}.".format(self.channel.mention),
|
description="Please join {}.".format(self.channel.mention),
|
||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ from .tracker import AccountabilityGuild as AG
|
|||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
class accountability_category(settings.Channel, settings.GuildSetting):
|
class accountability_category(settings.Channel, settings.GuildSetting):
|
||||||
category = "Accountability Rooms"
|
category = "Scheduled Sessions"
|
||||||
|
|
||||||
attr_name = "accountability_category"
|
attr_name = "accountability_category"
|
||||||
_data_column = "accountability_category"
|
_data_column = "accountability_category"
|
||||||
|
|
||||||
display_name = "accountability_category"
|
display_name = "session_category"
|
||||||
desc = "Category in which to make the accountability rooms."
|
desc = "Category in which to make the scheduled session rooms."
|
||||||
|
|
||||||
_default = None
|
_default = None
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"\"Accountability\" category channel.\n"
|
"\"Schedule session\" category channel.\n"
|
||||||
"The accountability voice channels will be created here."
|
"Scheduled sessions will be held in voice channels created under this category."
|
||||||
)
|
)
|
||||||
_accepts = "A category channel."
|
_accepts = "A category channel."
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ class accountability_category(settings.Channel, settings.GuildSetting):
|
|||||||
# TODO Move this somewhere better
|
# TODO Move this somewhere better
|
||||||
if self.id not in AG.cache:
|
if self.id not in AG.cache:
|
||||||
AG(self.id)
|
AG(self.id)
|
||||||
return "The accountability category has been changed to **{}**.".format(self.value.name)
|
return "The session category has been changed to **{}**.".format(self.value.name)
|
||||||
else:
|
else:
|
||||||
return "The accountability system has been started in **{}**.".format(self.value.name)
|
return "The scheduled session system has been started in **{}**.".format(self.value.name)
|
||||||
else:
|
else:
|
||||||
if self.id in AG.cache:
|
if self.id in AG.cache:
|
||||||
aguild = AG.cache.pop(self.id)
|
aguild = AG.cache.pop(self.id)
|
||||||
@@ -43,26 +43,26 @@ class accountability_category(settings.Channel, settings.GuildSetting):
|
|||||||
asyncio.create_task(aguild.current_slot.cancel())
|
asyncio.create_task(aguild.current_slot.cancel())
|
||||||
if aguild.upcoming_slot:
|
if aguild.upcoming_slot:
|
||||||
asyncio.create_task(aguild.upcoming_slot.cancel())
|
asyncio.create_task(aguild.upcoming_slot.cancel())
|
||||||
return "The accountability system has been shut down."
|
return "The scheduled session system has been shut down."
|
||||||
else:
|
else:
|
||||||
return "The accountability category has been unset."
|
return "The scheduled session category has been unset."
|
||||||
|
|
||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
class accountability_lobby(settings.Channel, settings.GuildSetting):
|
class accountability_lobby(settings.Channel, settings.GuildSetting):
|
||||||
category = "Accountability Rooms"
|
category = "Scheduled Sessions"
|
||||||
|
|
||||||
attr_name = "accountability_lobby"
|
attr_name = "accountability_lobby"
|
||||||
_data_column = attr_name
|
_data_column = attr_name
|
||||||
|
|
||||||
display_name = attr_name
|
display_name = "session_lobby"
|
||||||
desc = "Category in which to post accountability session status updates."
|
desc = "Category in which to post scheduled session notifications updates."
|
||||||
|
|
||||||
_default = None
|
_default = None
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Accountability session updates will be posted here, and members will be notified in this channel.\n"
|
"Scheduled session updates will be posted here, and members will be notified in this channel.\n"
|
||||||
"The channel will be automatically created in the accountability category if it does not exist.\n"
|
"The channel will be automatically created in the configured `session_category` if it does not exist.\n"
|
||||||
"Members do not need to be able to write in the channel."
|
"Members do not need to be able to write in the channel."
|
||||||
)
|
)
|
||||||
_accepts = "Any text channel."
|
_accepts = "Any text channel."
|
||||||
@@ -76,65 +76,65 @@ class accountability_lobby(settings.Channel, settings.GuildSetting):
|
|||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
class accountability_price(settings.Integer, GuildSetting):
|
class accountability_price(settings.Integer, GuildSetting):
|
||||||
category = "Accountability Rooms"
|
category = "Scheduled Sessions"
|
||||||
|
|
||||||
attr_name = "accountability_price"
|
attr_name = "accountability_price"
|
||||||
_data_column = attr_name
|
_data_column = attr_name
|
||||||
|
|
||||||
display_name = attr_name
|
display_name = "session_price"
|
||||||
desc = "Cost of booking an accountability time slot."
|
desc = "Cost of booking a scheduled session."
|
||||||
|
|
||||||
_default = 100
|
_default = 100
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"The price of booking each one hour accountability room slot."
|
"The price of booking each one hour scheduled session slot."
|
||||||
)
|
)
|
||||||
_accepts = "An integer number of coins."
|
_accepts = "An integer number of coins."
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
return "Accountability slots now cost `{}` coins.".format(self.value)
|
return "Scheduled session slots now cost `{}` coins.".format(self.value)
|
||||||
|
|
||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
class accountability_bonus(settings.Integer, GuildSetting):
|
class accountability_bonus(settings.Integer, GuildSetting):
|
||||||
category = "Accountability Rooms"
|
category = "Scheduled Sessions"
|
||||||
|
|
||||||
attr_name = "accountability_bonus"
|
attr_name = "accountability_bonus"
|
||||||
_data_column = attr_name
|
_data_column = attr_name
|
||||||
|
|
||||||
display_name = attr_name
|
display_name = "session_bonus"
|
||||||
desc = "Bonus given when all accountability members attend a time slot."
|
desc = "Bonus given when everyone attends a scheduled session slot."
|
||||||
|
|
||||||
_default = 1000
|
_default = 1000
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"The extra bonus given when all the members who have booked an accountability time slot attend."
|
"The extra bonus given to each scheduled session member when everyone who booked attended the session."
|
||||||
)
|
)
|
||||||
_accepts = "An integer number of coins."
|
_accepts = "An integer number of coins."
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
return "Accountability members will now get `{}` coins if everyone joins.".format(self.value)
|
return "Scheduled session members will now get `{}` coins if everyone joins.".format(self.value)
|
||||||
|
|
||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
class accountability_reward(settings.Integer, GuildSetting):
|
class accountability_reward(settings.Integer, GuildSetting):
|
||||||
category = "Accountability Rooms"
|
category = "Scheduled Sessions"
|
||||||
|
|
||||||
attr_name = "accountability_reward"
|
attr_name = "accountability_reward"
|
||||||
_data_column = attr_name
|
_data_column = attr_name
|
||||||
|
|
||||||
display_name = attr_name
|
display_name = "session_reward"
|
||||||
desc = "Reward given for attending a booked accountability slot."
|
desc = "The individual reward given when a member attends their booked scheduled session."
|
||||||
|
|
||||||
_default = 200
|
_default = 200
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Amount given to a member who books an accountability slot and attends it."
|
"Reward given to a member who attends a booked scheduled session."
|
||||||
)
|
)
|
||||||
_accepts = "An integer number of coins."
|
_accepts = "An integer number of coins."
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
return "Accountability members will now get `{}` coins at the end of their slot.".format(self.value)
|
return "Members will now get `{}` coins when they attend their scheduled session.".format(self.value)
|
||||||
|
|||||||
@@ -62,28 +62,29 @@ def ensure_exclusive(ctx):
|
|||||||
|
|
||||||
|
|
||||||
@module.cmd(
|
@module.cmd(
|
||||||
name="rooms",
|
name="schedule",
|
||||||
desc="Schedule an accountability study session.",
|
desc="View your schedule, and get rewarded for attending scheduled sessions!",
|
||||||
group="Productivity"
|
group="Productivity",
|
||||||
|
aliases=('rooms', 'sessions')
|
||||||
)
|
)
|
||||||
@in_guild()
|
@in_guild()
|
||||||
async def cmd_rooms(ctx):
|
async def cmd_rooms(ctx):
|
||||||
"""
|
"""
|
||||||
Usage``:
|
Usage``:
|
||||||
{prefix}rooms
|
{prefix}schedule
|
||||||
{prefix}rooms book
|
{prefix}schedule book
|
||||||
{prefix}rooms cancel
|
{prefix}schedule cancel
|
||||||
Description:
|
Description:
|
||||||
View your accountability profile with `{prefix}rooms`.
|
View your schedule with `{prefix}schedule`.
|
||||||
Use `{prefix}rooms book` to book an accountability session!
|
Use `{prefix}schedule book` to schedule a session at a selected time..
|
||||||
Use `{prefix}rooms cancel` to cancel a booked session.
|
Use `{prefix}schedule cancel` to cancel a scheduled session.
|
||||||
"""
|
"""
|
||||||
lower = ctx.args.lower()
|
lower = ctx.args.lower()
|
||||||
splits = lower.split()
|
splits = lower.split()
|
||||||
command = splits[0] if splits else None
|
command = splits[0] if splits else None
|
||||||
|
|
||||||
if not ctx.guild_settings.accountability_category.value:
|
if not ctx.guild_settings.accountability_category.value:
|
||||||
return await ctx.error_reply("The accountability system isn't set up!")
|
return await ctx.error_reply("The scheduled session system isn't set up!")
|
||||||
|
|
||||||
# First grab the sessions the member is booked in
|
# First grab the sessions the member is booked in
|
||||||
joined_rows = accountability_member_info.select_where(
|
joined_rows = accountability_member_info.select_where(
|
||||||
@@ -94,7 +95,7 @@ async def cmd_rooms(ctx):
|
|||||||
|
|
||||||
if command == 'cancel':
|
if command == 'cancel':
|
||||||
if not joined_rows:
|
if not joined_rows:
|
||||||
return await ctx.error_reply("You have no bookings to cancel!")
|
return await ctx.error_reply("You have no scheduled sessions to cancel!")
|
||||||
|
|
||||||
# Show unbooking menu
|
# Show unbooking menu
|
||||||
lines = [
|
lines = [
|
||||||
@@ -102,9 +103,9 @@ async def cmd_rooms(ctx):
|
|||||||
for i, row in enumerate(joined_rows)
|
for i, row in enumerate(joined_rows)
|
||||||
]
|
]
|
||||||
out_msg = await ctx.reply(
|
out_msg = await ctx.reply(
|
||||||
content="Please reply with the number(s) of the rooms you want to cancel. E.g. `1, 3, 5` or `1-3, 7-8`.",
|
content="Please reply with the number(s) of the sessions you want to cancel. E.g. `1, 3, 5` or `1-3, 7-8`.",
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Please choose the bookings you want to cancel.",
|
title="Please choose the sessions you want to cancel.",
|
||||||
description='\n'.join(lines),
|
description='\n'.join(lines),
|
||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
@@ -116,7 +117,7 @@ async def cmd_rooms(ctx):
|
|||||||
|
|
||||||
await ctx.cancellable(
|
await ctx.cancellable(
|
||||||
out_msg,
|
out_msg,
|
||||||
cancel_message="Cancel menu closed, no accountability sessions were cancelled.",
|
cancel_message="Cancel menu closed, no scheduled sessions were cancelled.",
|
||||||
timeout=70
|
timeout=70
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ async def cmd_rooms(ctx):
|
|||||||
await out_msg.edit(
|
await out_msg.edit(
|
||||||
content=None,
|
content=None,
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
description="Cancel menu timed out, no accountability sessions were cancelled.",
|
description="Cancel menu timed out, no scheduled sessions were cancelled.",
|
||||||
colour=discord.Colour.red()
|
colour=discord.Colour.red()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -156,7 +157,7 @@ async def cmd_rooms(ctx):
|
|||||||
for index in parse_ranges(message.content) if index < len(joined_rows)
|
for index in parse_ranges(message.content) if index < len(joined_rows)
|
||||||
]
|
]
|
||||||
if not to_cancel:
|
if not to_cancel:
|
||||||
return await ctx.error_reply("No valid bookings selected for cancellation.")
|
return await ctx.error_reply("No valid sessions selected for cancellation.")
|
||||||
elif any(row['start_at'] < utc_now() for row in to_cancel):
|
elif any(row['start_at'] < utc_now() for row in to_cancel):
|
||||||
return await ctx.error_reply("You can't cancel a running session!")
|
return await ctx.error_reply("You can't cancel a running session!")
|
||||||
|
|
||||||
@@ -189,7 +190,7 @@ async def cmd_rooms(ctx):
|
|||||||
|
|
||||||
remaining = [row for row in joined_rows if row['slotid'] not in slotids]
|
remaining = [row for row in joined_rows if row['slotid'] not in slotids]
|
||||||
if not remaining:
|
if not remaining:
|
||||||
await ctx.embed_reply("Cancelled all your upcoming accountability sessions!")
|
await ctx.embed_reply("Cancelled all your upcoming scheduled sessions!")
|
||||||
else:
|
else:
|
||||||
next_booked_time = min(row['start_at'] for row in remaining)
|
next_booked_time = min(row['start_at'] for row in remaining)
|
||||||
if len(to_cancel) > 1:
|
if len(to_cancel) > 1:
|
||||||
@@ -245,9 +246,11 @@ async def cmd_rooms(ctx):
|
|||||||
# TODO: Nicer embed
|
# TODO: Nicer embed
|
||||||
# TODO: Don't allow multi bookings if the member has a bad attendance rate
|
# TODO: Don't allow multi bookings if the member has a bad attendance rate
|
||||||
out_msg = await ctx.reply(
|
out_msg = await ctx.reply(
|
||||||
content="Please reply with the number(s) of the rooms you want to join. E.g. `1, 3, 5` or `1-3, 7-8`.",
|
content=(
|
||||||
|
"Please reply with the number(s) of the sessions you want to book. E.g. `1, 3, 5` or `1-3, 7-8`."
|
||||||
|
),
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Please choose the sessions you want to book.",
|
title="Please choose the sessions you want to schedule.",
|
||||||
description='\n'.join(lines),
|
description='\n'.join(lines),
|
||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
@@ -354,10 +357,10 @@ async def cmd_rooms(ctx):
|
|||||||
|
|
||||||
# Ack purchase
|
# Ack purchase
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="You have booked the following session{}!".format('s' if len(to_book) > 1 else ''),
|
title="You have scheduled the following session{}!".format('s' if len(to_book) > 1 else ''),
|
||||||
description=(
|
description=(
|
||||||
"*Please attend all your booked sessions!*\n"
|
"*Please attend all your scheduled sessions!*\n"
|
||||||
"*If you can't attend, cancel with* `{}rooms cancel`\n\n{}"
|
"*If you can't attend, cancel with* `{}schedule cancel`\n\n{}"
|
||||||
).format(
|
).format(
|
||||||
ctx.best_prefix,
|
ctx.best_prefix,
|
||||||
'\n'.join(time_format(time) for time in to_book),
|
'\n'.join(time_format(time) for time in to_book),
|
||||||
@@ -365,7 +368,7 @@ async def cmd_rooms(ctx):
|
|||||||
colour=discord.Colour.orange()
|
colour=discord.Colour.orange()
|
||||||
).set_footer(
|
).set_footer(
|
||||||
text=(
|
text=(
|
||||||
"Use {prefix}rooms to see your current bookings.\n"
|
"Use {prefix}schedule to see your current schedule.\n"
|
||||||
).format(prefix=ctx.best_prefix)
|
).format(prefix=ctx.best_prefix)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
@@ -400,10 +403,10 @@ async def cmd_rooms(ctx):
|
|||||||
if not (history or joined_rows):
|
if not (history or joined_rows):
|
||||||
# First-timer information
|
# First-timer information
|
||||||
about = (
|
about = (
|
||||||
"You haven't joined any accountability sessions yet!\n"
|
"You haven't scheduled any study sessions yet!\n"
|
||||||
"Book a session by typing **`{}rooms book`** and selecting "
|
"Schedule a session by typing **`{}schedule book`** and selecting "
|
||||||
"the hours you intend to study, "
|
"the hours you intend to study, "
|
||||||
"then attend by joining the accountability voice channel when the session starts!\n"
|
"then attend by joining the session voice channel when it starts!\n"
|
||||||
"Only if everyone attends will they get the bonus of `{}` LionCoins!\n"
|
"Only if everyone attends will they get the bonus of `{}` LionCoins!\n"
|
||||||
"Let's all do our best and keep each other accountable 🔥"
|
"Let's all do our best and keep each other accountable 🔥"
|
||||||
).format(
|
).format(
|
||||||
@@ -492,7 +495,7 @@ async def cmd_rooms(ctx):
|
|||||||
total_count,
|
total_count,
|
||||||
(attended_count * 100) / total_count,
|
(attended_count * 100) / total_count,
|
||||||
),
|
),
|
||||||
"Time": "**{:02}:{:02}** spent in accountability rooms.".format(
|
"Time": "**{:02}:{:02}** in scheduled sessions.".format(
|
||||||
total_duration // 3600,
|
total_duration // 3600,
|
||||||
(total_duration % 3600) // 60
|
(total_duration % 3600) // 60
|
||||||
),
|
),
|
||||||
@@ -525,11 +528,24 @@ async def cmd_rooms(ctx):
|
|||||||
),
|
),
|
||||||
_extra="GROUP BY start_at, slotid, guildid ORDER BY start_at ASC"
|
_extra="GROUP BY start_at, slotid, guildid ORDER BY start_at ASC"
|
||||||
)
|
)
|
||||||
attendees = {row['start_at']: (row['num'], client.get_guild(row['guildid'])) for row in rows}
|
attendees = {
|
||||||
|
row['start_at']: (row['num'], row['guildid']) for row in rows
|
||||||
|
}
|
||||||
attendee_pad = max((len(str(num)) for num, _ in attendees.values()), default=1)
|
attendee_pad = max((len(str(num)) for num, _ in attendees.values()), default=1)
|
||||||
|
|
||||||
# TODO: Allow cancel to accept multiselect keys as args
|
# TODO: Allow cancel to accept multiselect keys as args
|
||||||
show_guild = any(guild != ctx.guild for _, guild in attendees.values())
|
show_guild = any(guildid != ctx.guild.id for _, guildid in attendees.values())
|
||||||
|
guild_map = {}
|
||||||
|
if show_guild:
|
||||||
|
for _, guildid in attendees.values():
|
||||||
|
if guildid not in guild_map:
|
||||||
|
guild = ctx.client.get_guild(guildid)
|
||||||
|
if not guild:
|
||||||
|
try:
|
||||||
|
guild = await ctx.client.fetch_guild(guildid)
|
||||||
|
except discord.HTTPException:
|
||||||
|
guild = None
|
||||||
|
guild_map[guildid] = guild
|
||||||
|
|
||||||
booked_list = '\n'.join(
|
booked_list = '\n'.join(
|
||||||
"`{:>{}}` attendees | {} {}".format(
|
"`{:>{}}` attendees | {} {}".format(
|
||||||
@@ -537,24 +553,24 @@ async def cmd_rooms(ctx):
|
|||||||
attendee_pad,
|
attendee_pad,
|
||||||
time_format(start),
|
time_format(start),
|
||||||
"" if not show_guild else (
|
"" if not show_guild else (
|
||||||
"on this server" if guild == ctx.guild else "in **{}**".format(
|
"on this server" if guildid == ctx.guild.id else "in **{}**".format(
|
||||||
guild.name if guild else guild.id
|
guild_map[guildid] or "Unknown"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) for start, (num, guild) in attendees.items()
|
) for start, (num, guildid) in attendees.items()
|
||||||
)
|
)
|
||||||
booked_field = (
|
booked_field = (
|
||||||
"{}\n\n"
|
"{}\n\n"
|
||||||
"*If you can't make your booking, please cancel using `{}rooms cancel`!*"
|
"*If you can't make your session, please cancel using `{}schedule cancel`!*"
|
||||||
).format(booked_list, ctx.best_prefix)
|
).format(booked_list, ctx.best_prefix)
|
||||||
|
|
||||||
# Temporary footer for acclimatisation
|
# Temporary footer for acclimatisation
|
||||||
# footer = "All times are displayed in your own timezone!"
|
# footer = "All times are displayed in your own timezone!"
|
||||||
footer = "Book another session using {}rooms book".format(ctx.best_prefix)
|
footer = "Book another session using {}schedule book".format(ctx.best_prefix)
|
||||||
else:
|
else:
|
||||||
booked_field = (
|
booked_field = (
|
||||||
"Your schedule is empty!\n"
|
"Your schedule is empty!\n"
|
||||||
"Book another session using `{}rooms book`."
|
"Book another session using `{}schedule book`."
|
||||||
).format(ctx.best_prefix)
|
).format(ctx.best_prefix)
|
||||||
footer = "Please keep your DMs open for notifications!"
|
footer = "Please keep your DMs open for notifications!"
|
||||||
|
|
||||||
@@ -563,7 +579,7 @@ async def cmd_rooms(ctx):
|
|||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
description=desc,
|
description=desc,
|
||||||
).set_author(
|
).set_author(
|
||||||
name="Accountability profile for {}".format(ctx.author.name),
|
name="Schedule statistics for {}".format(ctx.author.name),
|
||||||
icon_url=ctx.author.avatar_url
|
icon_url=ctx.author.avatar_url
|
||||||
).set_footer(
|
).set_footer(
|
||||||
text=footer,
|
text=footer,
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ async def open_next(start_time):
|
|||||||
if not slot.category:
|
if not slot.category:
|
||||||
# Log and unload guild
|
# Log and unload guild
|
||||||
aguild.guild_settings.event_log.log(
|
aguild.guild_settings.event_log.log(
|
||||||
"The accountability category couldn't be found!\n"
|
"The scheduled session category couldn't be found!\n"
|
||||||
"Shutting down the accountability system in this server.\n"
|
"Shutting down the scheduled session system in this server.\n"
|
||||||
"To re-activate, please reconfigure `config accountability_category`."
|
"To re-activate, please reconfigure `config session_category`."
|
||||||
)
|
)
|
||||||
AccountabilityGuild.cache.pop(aguild.guildid, None)
|
AccountabilityGuild.cache.pop(aguild.guildid, None)
|
||||||
await slot.cancel()
|
await slot.cancel()
|
||||||
@@ -106,16 +106,16 @@ async def open_next(start_time):
|
|||||||
# Create a new lobby
|
# Create a new lobby
|
||||||
try:
|
try:
|
||||||
channel = await guild.create_text_channel(
|
channel = await guild.create_text_channel(
|
||||||
name="accountability-lobby",
|
name="session-lobby",
|
||||||
category=slot.category,
|
category=slot.category,
|
||||||
reason="Automatic creation of accountability lobby."
|
reason="Automatic creation of scheduled session lobby."
|
||||||
)
|
)
|
||||||
aguild.guild_settings.accountability_lobby.value = channel
|
aguild.guild_settings.accountability_lobby.value = channel
|
||||||
slot.lobby = channel
|
slot.lobby = channel
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
# Event log failure and skip session
|
# Event log failure and skip session
|
||||||
aguild.guild_settings.event_log.log(
|
aguild.guild_settings.event_log.log(
|
||||||
"Failed to create the accountability lobby text channel.\n"
|
"Failed to create the scheduled session lobby text channel.\n"
|
||||||
"Please set the lobby channel manually with `config`."
|
"Please set the lobby channel manually with `config`."
|
||||||
)
|
)
|
||||||
await slot.cancel()
|
await slot.cancel()
|
||||||
@@ -123,7 +123,7 @@ async def open_next(start_time):
|
|||||||
|
|
||||||
# Event log creation
|
# Event log creation
|
||||||
aguild.guild_settings.event_log.log(
|
aguild.guild_settings.event_log.log(
|
||||||
"Automatically created an accountability lobby channel {}.".format(channel.mention)
|
"Automatically created a scheduled session lobby channel {}.".format(channel.mention)
|
||||||
)
|
)
|
||||||
|
|
||||||
results = await slot.open()
|
results = await slot.open()
|
||||||
@@ -221,7 +221,7 @@ async def turnover():
|
|||||||
movement_tasks = (
|
movement_tasks = (
|
||||||
mem.member.edit(
|
mem.member.edit(
|
||||||
voice_channel=slot.channel,
|
voice_channel=slot.channel,
|
||||||
reason="Moving to booked accountability session."
|
reason="Moving to scheduled session."
|
||||||
)
|
)
|
||||||
for slot in current_slots
|
for slot in current_slots
|
||||||
for mem in slot.members.values()
|
for mem in slot.members.values()
|
||||||
@@ -317,7 +317,7 @@ async def _accountability_loop():
|
|||||||
except Exception:
|
except Exception:
|
||||||
# Unknown exception. Catch it so the loop doesn't die.
|
# Unknown exception. Catch it so the loop doesn't die.
|
||||||
client.log(
|
client.log(
|
||||||
"Error while opening new accountability rooms! "
|
"Error while opening new scheduled sessions! "
|
||||||
"Exception traceback follows.\n{}".format(
|
"Exception traceback follows.\n{}".format(
|
||||||
traceback.format_exc()
|
traceback.format_exc()
|
||||||
),
|
),
|
||||||
@@ -332,7 +332,7 @@ async def _accountability_loop():
|
|||||||
except Exception:
|
except Exception:
|
||||||
# Unknown exception. Catch it so the loop doesn't die.
|
# Unknown exception. Catch it so the loop doesn't die.
|
||||||
client.log(
|
client.log(
|
||||||
"Error while starting accountability rooms! "
|
"Error while starting scheduled sessions! "
|
||||||
"Exception traceback follows.\n{}".format(
|
"Exception traceback follows.\n{}".format(
|
||||||
traceback.format_exc()
|
traceback.format_exc()
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ async def cmd_send(ctx):
|
|||||||
return await ctx.embed_reply("We are still waiting for {} to open an account.".format(target.mention))
|
return await ctx.embed_reply("We are still waiting for {} to open an account.".format(target.mention))
|
||||||
|
|
||||||
# Finally, send the amount and the ack message
|
# Finally, send the amount and the ack message
|
||||||
target_lion.addCoins(amount)
|
target_lion.addCoins(amount, ignorebonus=True)
|
||||||
source_lion.addCoins(-amount)
|
source_lion.addCoins(-amount)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ async def cmd_set(ctx):
|
|||||||
# Postgres `coins` column is `integer`, sanity check postgres int limits - which are smalled than python int range
|
# Postgres `coins` column is `integer`, sanity check postgres int limits - which are smalled than python int range
|
||||||
target_coins_to_set = target_lion.coins + amount
|
target_coins_to_set = target_lion.coins + amount
|
||||||
if target_coins_to_set >= 0 and target_coins_to_set <= POSTGRES_INT_MAX:
|
if target_coins_to_set >= 0 and target_coins_to_set <= POSTGRES_INT_MAX:
|
||||||
target_lion.addCoins(amount)
|
target_lion.addCoins(amount, ignorebonus=True)
|
||||||
elif target_coins_to_set < 0:
|
elif target_coins_to_set < 0:
|
||||||
target_coins_to_set = -target_lion.coins # Coins cannot go -ve, cap to 0
|
target_coins_to_set = -target_lion.coins # Coins cannot go -ve, cap to 0
|
||||||
target_lion.addCoins(target_coins_to_set)
|
target_lion.addCoins(target_coins_to_set, ignorebonus=True)
|
||||||
target_coins_to_set = 0
|
target_coins_to_set = 0
|
||||||
else:
|
else:
|
||||||
return await ctx.embed_reply("Member coins cannot be more than {}".format(POSTGRES_INT_MAX))
|
return await ctx.embed_reply("Member coins cannot be more than {}".format(POSTGRES_INT_MAX))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ cat_pages = {
|
|||||||
'Administration': ('Meta', 'Guild Roles', 'New Members'),
|
'Administration': ('Meta', 'Guild Roles', 'New Members'),
|
||||||
'Moderation': ('Moderation', 'Video Channels'),
|
'Moderation': ('Moderation', 'Video Channels'),
|
||||||
'Productivity': ('Study Tracking', 'TODO List', 'Workout'),
|
'Productivity': ('Study Tracking', 'TODO List', 'Workout'),
|
||||||
'Study Rooms': ('Rented Rooms', 'Accountability Rooms'),
|
'Study Rooms': ('Rented Rooms', 'Scheduled Sessions'),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Descriptions of each configuration category
|
# Descriptions of each configuration category
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ class greeting_channel(stypes.Channel, GuildSetting):
|
|||||||
attr_name = 'greeting_channel'
|
attr_name = 'greeting_channel'
|
||||||
_data_column = 'greeting_channel'
|
_data_column = 'greeting_channel'
|
||||||
|
|
||||||
display_name = "greeting_channel"
|
display_name = "welcome_channel"
|
||||||
desc = "Channel to send the greeting message in"
|
desc = "Channel to send the welcome message in"
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Channel to post the `greeting_message` in when a new user joins the server. "
|
"Channel to post the `welcome_message` in when a new user joins the server. "
|
||||||
"Accepts `DM` to indicate the greeting should be direct messaged to the new member."
|
"Accepts `DM` to indicate the welcome should be sent via direct message."
|
||||||
)
|
)
|
||||||
_accepts = (
|
_accepts = (
|
||||||
"Text Channel name/id/mention, or `DM`, or `None` to disable."
|
"Text Channel name/id/mention, or `DM`, or `None` to disable."
|
||||||
@@ -78,11 +78,11 @@ class greeting_channel(stypes.Channel, GuildSetting):
|
|||||||
def success_response(self):
|
def success_response(self):
|
||||||
value = self.value
|
value = self.value
|
||||||
if not value:
|
if not value:
|
||||||
return "Greeting messages are disabled."
|
return "Welcome messages are disabled."
|
||||||
elif value == self.DMCHANNEL:
|
elif value == self.DMCHANNEL:
|
||||||
return "Greeting messages will be sent via direct message."
|
return "Welcome messages will be sent via direct message."
|
||||||
else:
|
else:
|
||||||
return "Greeting messages will be posted in {}".format(self.formatted)
|
return "Welcome messages will be posted in {}".format(self.formatted)
|
||||||
|
|
||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
@@ -92,11 +92,11 @@ class greeting_message(stypes.Message, GuildSetting):
|
|||||||
attr_name = 'greeting_message'
|
attr_name = 'greeting_message'
|
||||||
_data_column = 'greeting_message'
|
_data_column = 'greeting_message'
|
||||||
|
|
||||||
display_name = 'greeting_message'
|
display_name = 'welcome_message'
|
||||||
desc = "Greeting message sent to welcome new members."
|
desc = "Welcome message sent to welcome new members."
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Message to send to the configured `greeting_channel` when a member joins the server for the first time."
|
"Message to send to the configured `welcome_channel` when a member joins the server for the first time."
|
||||||
)
|
)
|
||||||
|
|
||||||
_default = r"""
|
_default = r"""
|
||||||
@@ -133,7 +133,7 @@ class greeting_message(stypes.Message, GuildSetting):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def success_response(self):
|
def success_response(self):
|
||||||
return "The greeting message has been set!"
|
return "The welcome message has been set!"
|
||||||
|
|
||||||
|
|
||||||
@GuildSettings.attach_setting
|
@GuildSettings.attach_setting
|
||||||
@@ -144,10 +144,10 @@ class returning_message(stypes.Message, GuildSetting):
|
|||||||
_data_column = 'returning_message'
|
_data_column = 'returning_message'
|
||||||
|
|
||||||
display_name = 'returning_message'
|
display_name = 'returning_message'
|
||||||
desc = "Greeting message sent to returning members."
|
desc = "Welcome message sent to returning members."
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Message to send to the configured `greeting_channel` when a member returns to the server."
|
"Message to send to the configured `welcome_channel` when a member returns to the server."
|
||||||
)
|
)
|
||||||
|
|
||||||
_default = r"""
|
_default = r"""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from codecs import ignore_errors
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import datetime
|
import datetime
|
||||||
@@ -500,7 +501,7 @@ class ReactionRoleMessage:
|
|||||||
if price and refund:
|
if price and refund:
|
||||||
# Give the user the refund
|
# Give the user the refund
|
||||||
lion = Lion.fetch(self.guild.id, member.id)
|
lion = Lion.fetch(self.guild.id, member.id)
|
||||||
lion.addCoins(price)
|
lion.addCoins(price, ignorebonus=True)
|
||||||
|
|
||||||
# Notify the user
|
# Notify the user
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ group_hints = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standard_group_order = (
|
standard_group_order = (
|
||||||
('🆕 Pomodoro', 'Productivity', 'Statistics', 'Economy', 'Personal Settings', 'Meta')
|
('🆕 Pomodoro', 'Productivity', 'Statistics', 'Economy', 'Personal Settings', 'Meta'),
|
||||||
)
|
)
|
||||||
|
|
||||||
mod_group_order = (
|
mod_group_order = (
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ ticket_info = RowTable(
|
|||||||
'expiry',
|
'expiry',
|
||||||
'pardoned_by', 'pardoned_at', 'pardoned_reason'),
|
'pardoned_by', 'pardoned_at', 'pardoned_reason'),
|
||||||
'ticketid',
|
'ticketid',
|
||||||
|
cache_size=20000
|
||||||
)
|
)
|
||||||
|
|
||||||
tickets = Table('tickets')
|
tickets = Table('tickets')
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class video_grace_period(settings.Duration, GuildSetting):
|
|||||||
"before they will be kicked from the channel, and warned or studybanned (if enabled)."
|
"before they will be kicked from the channel, and warned or studybanned (if enabled)."
|
||||||
)
|
)
|
||||||
|
|
||||||
_default = 45
|
_default = 90
|
||||||
_default_multiplier = 1
|
_default_multiplier = 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from data import RowTable
|
from data.interfaces import RowTable
|
||||||
|
|
||||||
|
|
||||||
reminders = RowTable(
|
reminders = RowTable(
|
||||||
'reminders',
|
'reminders',
|
||||||
('reminderid', 'userid', 'remind_at', 'content', 'message_link', 'interval', 'created_at'),
|
('reminderid', 'userid', 'remind_at', 'content', 'message_link', 'interval', 'created_at', 'title', 'footer'),
|
||||||
'reminderid'
|
'reminderid'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -150,11 +150,13 @@ class Reminder:
|
|||||||
|
|
||||||
# Build the message embed
|
# Build the message embed
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="You asked me to remind you!",
|
title="You asked me to remind you!" if self.data.title is None else self.data.title,
|
||||||
colour=discord.Colour.orange(),
|
colour=discord.Colour.orange(),
|
||||||
description=self.data.content,
|
description=self.data.content,
|
||||||
timestamp=datetime.datetime.utcnow()
|
timestamp=datetime.datetime.utcnow()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.data.message_link:
|
||||||
embed.add_field(name="Context?", value="[Click here]({})".format(self.data.message_link))
|
embed.add_field(name="Context?", value="[Click here]({})".format(self.data.message_link))
|
||||||
|
|
||||||
if self.data.interval:
|
if self.data.interval:
|
||||||
@@ -165,6 +167,9 @@ class Reminder:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.data.footer:
|
||||||
|
embed.set_footer(text=self.data.footer)
|
||||||
|
|
||||||
# Update the reminder data, and reschedule if required
|
# Update the reminder data, and reschedule if required
|
||||||
if self.data.interval:
|
if self.data.interval:
|
||||||
next_time = self.data.remind_at + datetime.timedelta(seconds=self.data.interval)
|
next_time = self.data.remind_at + datetime.timedelta(seconds=self.data.interval)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class rent_member_limit(settings.Integer, GuildSetting):
|
|||||||
display_name = "rent_member_limit"
|
display_name = "rent_member_limit"
|
||||||
desc = "Maximum number of people that can be added to a rented room."
|
desc = "Maximum number of people that can be added to a rented room."
|
||||||
|
|
||||||
_default = 10
|
_default = 24
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Maximum number of people a member can add to a rented private voice channel."
|
"Maximum number of people a member can add to a rented private voice channel."
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ async def cmd_setprofile(ctx, flags):
|
|||||||
return await ctx.error_reply(
|
return await ctx.error_reply(
|
||||||
f"Sorry, you can have a maximum of `{MAX_TAGS}` tags!"
|
f"Sorry, you can have a maximum of `{MAX_TAGS}` tags!"
|
||||||
)
|
)
|
||||||
|
if tagid == 0:
|
||||||
|
return await ctx.error_reply("Tags start at `1`!")
|
||||||
|
|
||||||
# Retrieve the user's current taglist
|
# Retrieve the user's current taglist
|
||||||
rows = profile_tags.select_where(
|
rows = profile_tags.select_where(
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ class Timer:
|
|||||||
Remove the timer.
|
Remove the timer.
|
||||||
"""
|
"""
|
||||||
# Remove timer from cache
|
# Remove timer from cache
|
||||||
self.timers.pop(self.channelid)
|
self.timers.pop(self.channelid, None)
|
||||||
|
|
||||||
# Cancel the loop
|
# Cancel the loop
|
||||||
if self._run_task:
|
if self._run_task:
|
||||||
@@ -372,6 +372,11 @@ class Timer:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Destroy the timer if our voice channel no longer exists
|
||||||
|
if not self.channel:
|
||||||
|
await self.destroy()
|
||||||
|
break
|
||||||
|
|
||||||
if self._state.end < utc_now():
|
if self._state.end < utc_now():
|
||||||
asyncio.create_task(self.notify_change_stage(self._state, self.current_stage))
|
asyncio.create_task(self.notify_change_stage(self._state, self.current_stage))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Session:
|
|||||||
raise ValueError("A session for this member already exists!")
|
raise ValueError("A session for this member already exists!")
|
||||||
|
|
||||||
# If the user is study capped, schedule the session start for the next day
|
# If the user is study capped, schedule the session start for the next day
|
||||||
if (lion := Lion.fetch(guildid, userid)).remaining_study_today <= 0:
|
if (lion := Lion.fetch(guildid, userid)).remaining_study_today <= 10:
|
||||||
if pending := cls.members_pending[guildid].pop(userid, None):
|
if pending := cls.members_pending[guildid].pop(userid, None):
|
||||||
pending.cancel()
|
pending.cancel()
|
||||||
task = asyncio.create_task(cls._delayed_start(guildid, userid, member, state))
|
task = asyncio.create_task(cls._delayed_start(guildid, userid, member, state))
|
||||||
@@ -181,12 +181,13 @@ class Session:
|
|||||||
self._expiry_task.cancel()
|
self._expiry_task.cancel()
|
||||||
|
|
||||||
# Wait for the maximum session length
|
# Wait for the maximum session length
|
||||||
|
self._expiry_task = asyncio.create_task(asyncio.sleep(self.lion.remaining_study_today))
|
||||||
try:
|
try:
|
||||||
self._expiry_task = await asyncio.sleep(self.lion.remaining_study_today)
|
await self._expiry_task
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if self.lion.remaining_study_today <= 0:
|
if self.lion.remaining_study_today <= 10:
|
||||||
# End the session
|
# End the session
|
||||||
# Note that the user will not automatically start a new session when the day starts
|
# Note that the user will not automatically start a new session when the day starts
|
||||||
# TODO: Notify user? Disconnect them?
|
# TODO: Notify user? Disconnect them?
|
||||||
@@ -253,7 +254,7 @@ async def session_voice_tracker(client, member, before, after):
|
|||||||
return
|
return
|
||||||
|
|
||||||
guild = member.guild
|
guild = member.guild
|
||||||
Lion.fetch(guild.id, member.id)
|
Lion.fetch(guild.id, member.id).update_saved_data(member)
|
||||||
session = Session.get(guild.id, member.id)
|
session = Session.get(guild.id, member.id)
|
||||||
|
|
||||||
if before.channel == after.channel:
|
if before.channel == after.channel:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class task_limit(settings.Integer, GuildSetting):
|
|||||||
display_name = "task_limit"
|
display_name = "task_limit"
|
||||||
desc = "Maximum number of tasks each user may have."
|
desc = "Maximum number of tasks each user may have."
|
||||||
|
|
||||||
_default = 30
|
_default = 99
|
||||||
|
|
||||||
long_desc = (
|
long_desc = (
|
||||||
"Maximum number of tasks each user may have in the todo system."
|
"Maximum number of tasks each user may have in the todo system."
|
||||||
|
|||||||
6
bot/modules/topgg/__init__.py
Normal file
6
bot/modules/topgg/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .module import module
|
||||||
|
|
||||||
|
from . import webhook
|
||||||
|
from . import commands
|
||||||
|
from . import data
|
||||||
|
from . import settings
|
||||||
78
bot/modules/topgg/commands.py
Normal file
78
bot/modules/topgg/commands.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import discord
|
||||||
|
from .module import module
|
||||||
|
from bot.cmdClient.checks import in_guild, is_owner
|
||||||
|
from settings.user_settings import UserSettings
|
||||||
|
from LionContext import LionContext
|
||||||
|
|
||||||
|
from .webhook import on_dbl_vote
|
||||||
|
from .utils import lion_loveemote
|
||||||
|
|
||||||
|
|
||||||
|
@module.cmd(
|
||||||
|
"forcevote",
|
||||||
|
desc="Simulate a Topgg Vote from the given user.",
|
||||||
|
group="Bot Admin",
|
||||||
|
)
|
||||||
|
@is_owner()
|
||||||
|
async def cmd_forcevote(ctx: LionContext):
|
||||||
|
"""
|
||||||
|
Usage``:
|
||||||
|
{prefix}forcevote
|
||||||
|
Description:
|
||||||
|
Simulate Top.gg vote without actually a confirmation from Topgg site.
|
||||||
|
|
||||||
|
Can be used for force a vote for testing or if topgg has an error or production time bot error.
|
||||||
|
"""
|
||||||
|
target = ctx.author
|
||||||
|
|
||||||
|
# Identify the target
|
||||||
|
if ctx.args:
|
||||||
|
if not ctx.msg.mentions:
|
||||||
|
return await ctx.error_reply("Please mention a user to simulate a vote!")
|
||||||
|
target = ctx.msg.mentions[0]
|
||||||
|
|
||||||
|
await on_dbl_vote({"user": target.id, "type": "test"})
|
||||||
|
return await ctx.reply('Topgg vote simulation successful on {}'.format(target), suggest_vote=False)
|
||||||
|
|
||||||
|
|
||||||
|
@module.cmd(
|
||||||
|
"vote",
|
||||||
|
desc="[Vote](https://top.gg/bot/889078613817831495/vote) for me to get 25% more LCs!",
|
||||||
|
group="Economy",
|
||||||
|
aliases=('topgg', 'topggvote', 'upvote')
|
||||||
|
)
|
||||||
|
@in_guild()
|
||||||
|
async def cmd_vote(ctx: LionContext):
|
||||||
|
"""
|
||||||
|
Usage``:
|
||||||
|
{prefix}vote
|
||||||
|
Description:
|
||||||
|
Get Top.gg bot's link for +25% Economy boost.
|
||||||
|
"""
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Claim your boost!",
|
||||||
|
description=(
|
||||||
|
"Please click [here](https://top.gg/bot/889078613817831495/vote) to vote and support our bot!\n\n"
|
||||||
|
"Thank you! {}.".format(lion_loveemote)
|
||||||
|
),
|
||||||
|
colour=discord.Colour.orange()
|
||||||
|
).set_thumbnail(
|
||||||
|
url="https://cdn.discordapp.com/attachments/908283085999706153/933012309532614666/lion-love.png"
|
||||||
|
)
|
||||||
|
return await ctx.reply(embed=embed, suggest_vote=False)
|
||||||
|
|
||||||
|
|
||||||
|
@module.cmd(
|
||||||
|
"vote_reminder",
|
||||||
|
group="Personal Settings",
|
||||||
|
desc="Turn on/off boost reminders."
|
||||||
|
)
|
||||||
|
async def cmd_remind_vote(ctx: LionContext):
|
||||||
|
"""
|
||||||
|
Usage:
|
||||||
|
`{prefix}vote_reminder on`
|
||||||
|
`{prefix}vote_reminder off`
|
||||||
|
|
||||||
|
Enable or disable DM boost reminders.
|
||||||
|
"""
|
||||||
|
await UserSettings.settings.vote_remainder.command(ctx, ctx.author.id)
|
||||||
8
bot/modules/topgg/data.py
Normal file
8
bot/modules/topgg/data.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from data.interfaces import RowTable
|
||||||
|
|
||||||
|
topggvotes = RowTable(
|
||||||
|
'topgg',
|
||||||
|
('voteid', 'userid', 'boostedTimestamp'),
|
||||||
|
'voteid'
|
||||||
|
)
|
||||||
|
|
||||||
70
bot/modules/topgg/module.py
Normal file
70
bot/modules/topgg/module.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from LionModule import LionModule
|
||||||
|
from LionContext import LionContext
|
||||||
|
from core.lion import Lion
|
||||||
|
|
||||||
|
from .utils import get_last_voted_timestamp, lion_loveemote, lion_yayemote
|
||||||
|
from .webhook import init_webhook
|
||||||
|
|
||||||
|
module = LionModule("Topgg")
|
||||||
|
|
||||||
|
upvote_info = "You have a boost available {}, to support our project and earn **25% more LionCoins** type `{}vote` {}"
|
||||||
|
|
||||||
|
|
||||||
|
@module.launch_task
|
||||||
|
async def attach_topgg_webhook(client):
|
||||||
|
if client.shard_id == 0:
|
||||||
|
init_webhook()
|
||||||
|
client.log("Attached top.gg voiting webhook.", context="TOPGG")
|
||||||
|
|
||||||
|
@module.launch_task
|
||||||
|
async def register_hook(client):
|
||||||
|
LionContext.reply.add_wrapper(topgg_reply_wrapper)
|
||||||
|
Lion.register_economy_bonus(economy_bonus)
|
||||||
|
|
||||||
|
client.log("Loaded top.gg hooks.", context="TOPGG")
|
||||||
|
|
||||||
|
|
||||||
|
@module.unload_task
|
||||||
|
async def unregister_hook(client):
|
||||||
|
Lion.unregister_economy_bonus(economy_bonus)
|
||||||
|
LionContext.reply.remove_wrapper(topgg_reply_wrapper.__name__)
|
||||||
|
|
||||||
|
client.log("Unloaded top.gg hooks.", context="TOPGG")
|
||||||
|
|
||||||
|
|
||||||
|
async def topgg_reply_wrapper(func, *args, suggest_vote=True, **kwargs):
|
||||||
|
ctx = args[0]
|
||||||
|
|
||||||
|
if not suggest_vote:
|
||||||
|
pass
|
||||||
|
elif ctx.cmd and ctx.cmd.name == 'config':
|
||||||
|
pass
|
||||||
|
elif ctx.cmd and ctx.cmd.name == 'help' and ctx.args and ctx.args.split(maxsplit=1)[0].lower() == 'vote':
|
||||||
|
pass
|
||||||
|
elif not get_last_voted_timestamp(args[0].author.id):
|
||||||
|
upvote_info_formatted = upvote_info.format(lion_yayemote, args[0].best_prefix, lion_loveemote)
|
||||||
|
|
||||||
|
if 'embed' in kwargs:
|
||||||
|
# Add message as an extra embed field
|
||||||
|
kwargs['embed'].add_field(
|
||||||
|
name="\u200b",
|
||||||
|
value=(
|
||||||
|
upvote_info_formatted
|
||||||
|
),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Add message to content
|
||||||
|
if 'content' in kwargs and kwargs['content'] and len(kwargs['content']) + len(upvote_info_formatted) < 1998:
|
||||||
|
kwargs['content'] += '\n\n' + upvote_info_formatted
|
||||||
|
elif len(args) > 1 and len(args[1]) + len(upvote_info_formatted) < 1998:
|
||||||
|
args = list(args)
|
||||||
|
args[1] += '\n\n' + upvote_info_formatted
|
||||||
|
else:
|
||||||
|
kwargs['content'] = upvote_info_formatted
|
||||||
|
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def economy_bonus(lion):
|
||||||
|
return 1.25 if get_last_voted_timestamp(lion.userid) else 1
|
||||||
50
bot/modules/topgg/settings.py
Normal file
50
bot/modules/topgg/settings.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from settings.user_settings import UserSettings, UserSetting
|
||||||
|
from settings.setting_types import Boolean
|
||||||
|
|
||||||
|
from modules.reminders.reminder import Reminder
|
||||||
|
from modules.reminders.data import reminders
|
||||||
|
|
||||||
|
from .utils import create_remainder, remainder_content, topgg_upvote_link
|
||||||
|
|
||||||
|
|
||||||
|
@UserSettings.attach_setting
|
||||||
|
class topgg_vote_remainder(Boolean, UserSetting):
|
||||||
|
attr_name = 'vote_remainder'
|
||||||
|
_data_column = 'topgg_vote_reminder'
|
||||||
|
|
||||||
|
_default = True
|
||||||
|
|
||||||
|
display_name = 'vote_reminder'
|
||||||
|
desc = r"Toggle automatic reminders to support me for a 25% LionCoin boost."
|
||||||
|
long_desc = (
|
||||||
|
"Did you know that you can [vote for me]({vote_link}) to help me help other people reach their goals? "
|
||||||
|
"And you get a **25% boost** to all LionCoin income you make across all servers!\n"
|
||||||
|
"Enable this setting if you want me to let you know when you can vote again!"
|
||||||
|
).format(vote_link=topgg_upvote_link)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success_response(self):
|
||||||
|
if self.value:
|
||||||
|
# Check if reminder is already running
|
||||||
|
create_remainder(self.id)
|
||||||
|
|
||||||
|
return (
|
||||||
|
"Thank you for supporting me! I will remind in your DMs when you can vote next! "
|
||||||
|
"(Please make sure your DMs are open, otherwise I can't reach you!)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Check if reminder is already running and get its id
|
||||||
|
r = reminders.select_one_where(
|
||||||
|
userid=self.id,
|
||||||
|
select_columns='reminderid',
|
||||||
|
content=remainder_content,
|
||||||
|
_extra="ORDER BY remind_at DESC LIMIT 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cancel and delete Remainder if already running
|
||||||
|
if r:
|
||||||
|
Reminder.delete(r['reminderid'])
|
||||||
|
|
||||||
|
return (
|
||||||
|
"I will no longer send you voting reminders."
|
||||||
|
)
|
||||||
97
bot/modules/topgg/utils.py
Normal file
97
bot/modules/topgg/utils.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import discord
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from meta import sharding
|
||||||
|
from meta import conf
|
||||||
|
from meta.client import client
|
||||||
|
from utils.lib import utc_now
|
||||||
|
from settings.setting_types import Integer
|
||||||
|
|
||||||
|
from modules.reminders.reminder import Reminder
|
||||||
|
from modules.reminders.data import reminders
|
||||||
|
|
||||||
|
from . import data as db
|
||||||
|
from data.conditions import GEQ
|
||||||
|
|
||||||
|
topgg_upvote_link = 'https://top.gg/bot/889078613817831495/vote'
|
||||||
|
remainder_content = (
|
||||||
|
"You can now vote again on top.gg!\n"
|
||||||
|
"Click [here]({}) to vote, thank you for the support!"
|
||||||
|
).format(topgg_upvote_link)
|
||||||
|
|
||||||
|
lion_loveemote = conf.emojis.getemoji('lionlove')
|
||||||
|
lion_yayemote = conf.emojis.getemoji('lionyay')
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_voted_timestamp(userid: Integer):
|
||||||
|
"""
|
||||||
|
Will return None if user has not voted in [-12.5hrs till now]
|
||||||
|
else will return a Tuple containing timestamp of when exactly she voted
|
||||||
|
"""
|
||||||
|
return db.topggvotes.select_one_where(
|
||||||
|
userid=userid,
|
||||||
|
select_columns="boostedTimestamp",
|
||||||
|
boostedTimestamp=GEQ(utc_now() - datetime.timedelta(hours=12.5)),
|
||||||
|
_extra="ORDER BY boostedTimestamp DESC LIMIT 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_remainder(userid):
|
||||||
|
"""
|
||||||
|
Checks if a remainder is already running (immaterial of remind_at time)
|
||||||
|
If no remainder exists creates a new remainder and schedules it
|
||||||
|
"""
|
||||||
|
if not reminders.select_one_where(
|
||||||
|
userid=userid,
|
||||||
|
content=remainder_content,
|
||||||
|
_extra="ORDER BY remind_at DESC LIMIT 1"
|
||||||
|
):
|
||||||
|
last_vote_time = get_last_voted_timestamp(userid)
|
||||||
|
|
||||||
|
# if no, Create reminder
|
||||||
|
reminder = Reminder.create(
|
||||||
|
userid=userid,
|
||||||
|
# TODO using content as a selector is not a good method
|
||||||
|
content=remainder_content,
|
||||||
|
message_link=None,
|
||||||
|
interval=None,
|
||||||
|
title="Your boost is now available! {}".format(lion_yayemote),
|
||||||
|
footer="Use `{}vote_reminder off` to stop receiving reminders.".format(client.prefix),
|
||||||
|
remind_at=(
|
||||||
|
last_vote_time[0] + datetime.timedelta(hours=12.5)
|
||||||
|
if last_vote_time else
|
||||||
|
utc_now() + datetime.timedelta(minutes=5)
|
||||||
|
)
|
||||||
|
# remind_at=datetime.datetime.utcnow() + datetime.timedelta(minutes=2)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Schedule reminder
|
||||||
|
if sharding.shard_number == 0:
|
||||||
|
reminder.schedule()
|
||||||
|
|
||||||
|
|
||||||
|
async def send_user_dm(userid):
|
||||||
|
# Send the message, if possible
|
||||||
|
if not (user := client.get_user(userid)):
|
||||||
|
try:
|
||||||
|
user = await client.fetch_user(userid)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
if user:
|
||||||
|
try:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Thank you for supporting our bot on Top.gg! {}".format(lion_yayemote),
|
||||||
|
description=(
|
||||||
|
"By voting every 12 hours you will allow us to reach and help "
|
||||||
|
"even more students all over the world.\n"
|
||||||
|
"Thank you for supporting us, enjoy your LionCoins boost!"
|
||||||
|
),
|
||||||
|
colour=discord.Colour.orange()
|
||||||
|
).set_image(
|
||||||
|
url="https://cdn.discordapp.com/attachments/908283085999706153/932737228440993822/lion-yay.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
await user.send(embed=embed)
|
||||||
|
except discord.HTTPException:
|
||||||
|
# Nothing we can really do here. Maybe tell the user about their reminder next time?
|
||||||
|
pass
|
||||||
40
bot/modules/topgg/webhook.py
Normal file
40
bot/modules/topgg/webhook.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from meta.client import client
|
||||||
|
from settings.user_settings import UserSettings
|
||||||
|
from utils.lib import utc_now
|
||||||
|
from meta.config import conf
|
||||||
|
|
||||||
|
import topgg
|
||||||
|
from .utils import db, send_user_dm, create_remainder
|
||||||
|
|
||||||
|
|
||||||
|
@client.event
|
||||||
|
async def on_dbl_vote(data):
|
||||||
|
"""An event that is called whenever someone votes for the bot on Top.gg."""
|
||||||
|
client.log(f"Received a vote: \n{data}", context='Topgg')
|
||||||
|
|
||||||
|
db.topggvotes.insert(
|
||||||
|
userid=data['user'],
|
||||||
|
boostedTimestamp=utc_now()
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_user_dm(data['user'])
|
||||||
|
|
||||||
|
if UserSettings.settings.vote_remainder.value:
|
||||||
|
create_remainder(data['user'])
|
||||||
|
|
||||||
|
if data["type"] == "test":
|
||||||
|
return client.dispatch("dbl_test", data)
|
||||||
|
|
||||||
|
|
||||||
|
@client.event
|
||||||
|
async def on_dbl_test(data):
|
||||||
|
"""An event that is called whenever someone tests the webhook system for your bot on Top.gg."""
|
||||||
|
client.log(f"Received a test vote:\n{data}", context='Topgg')
|
||||||
|
|
||||||
|
|
||||||
|
def init_webhook():
|
||||||
|
client.topgg_webhook = topgg.WebhookManager(client).dbl_webhook(
|
||||||
|
conf.bot.get("topgg_route"),
|
||||||
|
conf.bot.get("topgg_password")
|
||||||
|
)
|
||||||
|
client.topgg_webhook.run(conf.bot.get("topgg_port"))
|
||||||
@@ -130,8 +130,8 @@ class Integer(SettingType):
|
|||||||
accepts = "An integer."
|
accepts = "An integer."
|
||||||
|
|
||||||
# Set limits on the possible integers
|
# Set limits on the possible integers
|
||||||
_min = -4096
|
_min = -2147483647
|
||||||
_max = 4096
|
_max = 2147483647
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _data_from_value(cls, id: int, value: Optional[bool], **kwargs):
|
def _data_from_value(cls, id: int, value: Optional[bool], **kwargs):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from cmdClient import Context
|
from LionContext import LionContext as Context
|
||||||
from cmdClient.lib import SafeCancellation
|
from cmdClient.lib import SafeCancellation
|
||||||
|
|
||||||
from data import tables
|
from data import tables
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from cmdClient import Context
|
from LionContext import LionContext as Context
|
||||||
from cmdClient.lib import UserCancelled, ResponseTimedOut
|
from cmdClient.lib import UserCancelled, ResponseTimedOut
|
||||||
|
|
||||||
from .lib import paginate_list
|
from .lib import paginate_list
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from cmdClient import Context
|
from LionContext import LionContext as Context
|
||||||
from cmdClient.lib import InvalidContext, UserCancelled, ResponseTimedOut, SafeCancellation
|
from cmdClient.lib import InvalidContext, UserCancelled, ResponseTimedOut, SafeCancellation
|
||||||
from . import interactive
|
from . import interactive as _interactive
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@Context.util
|
||||||
|
|||||||
@@ -14,3 +14,11 @@ data_appid = LionBot
|
|||||||
shard_count = 1
|
shard_count = 1
|
||||||
|
|
||||||
lion_sync_period = 60
|
lion_sync_period = 60
|
||||||
|
|
||||||
|
topgg_password =
|
||||||
|
topgg_route =
|
||||||
|
topgg_port =
|
||||||
|
|
||||||
|
[EMOJIS]
|
||||||
|
lionyay =
|
||||||
|
lionlove =
|
||||||
|
|||||||
87
data/migration/v8-v9/migration.sql
Normal file
87
data/migration/v8-v9/migration.sql
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
ALTER TABLE user_config
|
||||||
|
ADD COLUMN topgg_vote_reminder BOOLEAN;
|
||||||
|
|
||||||
|
ALTER TABLE reminders
|
||||||
|
ADD COLUMN title TEXT,
|
||||||
|
ADD COLUMN footer TEXT;
|
||||||
|
|
||||||
|
-- Topgg Data {{{
|
||||||
|
CREATE TABLE IF NOT EXISTS topgg(
|
||||||
|
voteid SERIAL PRIMARY KEY,
|
||||||
|
userid BIGINT NOT NULL,
|
||||||
|
boostedTimestamp TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
CREATE INDEX topgg_userid_timestamp ON topgg (userid, boostedTimestamp);
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
DROP FUNCTION close_study_session(_guildid BIGINT, _userid BIGINT);
|
||||||
|
|
||||||
|
CREATE FUNCTION close_study_session(_guildid BIGINT, _userid BIGINT)
|
||||||
|
RETURNS SETOF members
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
WITH
|
||||||
|
current_sesh AS (
|
||||||
|
DELETE FROM current_sessions
|
||||||
|
WHERE guildid=_guildid AND userid=_userid
|
||||||
|
RETURNING
|
||||||
|
*,
|
||||||
|
EXTRACT(EPOCH FROM (NOW() - start_time)) AS total_duration,
|
||||||
|
stream_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - stream_start)), 0) AS total_stream_duration,
|
||||||
|
video_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - video_start)), 0) AS total_video_duration,
|
||||||
|
live_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - live_start)), 0) AS total_live_duration
|
||||||
|
), bonus_userid AS (
|
||||||
|
SELECT COUNT(boostedTimestamp),
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM Topgg
|
||||||
|
WHERE Topgg.userid=_userid AND EXTRACT(EPOCH FROM (NOW() - boostedTimestamp)) < 12.5*60*60
|
||||||
|
) THEN
|
||||||
|
(array_agg(
|
||||||
|
CASE WHEN boostedTimestamp <= current_sesh.start_time THEN
|
||||||
|
1.25
|
||||||
|
ELSE
|
||||||
|
(((current_sesh.total_duration - EXTRACT(EPOCH FROM (boostedTimestamp - current_sesh.start_time)))/current_sesh.total_duration)*0.25)+1
|
||||||
|
END))[1]
|
||||||
|
ELSE
|
||||||
|
1
|
||||||
|
END
|
||||||
|
AS bonus
|
||||||
|
FROM Topgg, current_sesh
|
||||||
|
WHERE Topgg.userid=_userid AND EXTRACT(EPOCH FROM (NOW() - boostedTimestamp)) < 12.5*60*60
|
||||||
|
ORDER BY (array_agg(boostedTimestamp))[1] DESC LIMIT 1
|
||||||
|
), saved_sesh AS (
|
||||||
|
INSERT INTO session_history (
|
||||||
|
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
||||||
|
duration, stream_duration, video_duration, live_duration,
|
||||||
|
coins_earned
|
||||||
|
) SELECT
|
||||||
|
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
||||||
|
total_duration, total_stream_duration, total_video_duration, total_live_duration,
|
||||||
|
((total_duration * hourly_coins + live_duration * hourly_live_coins) * bonus_userid.bonus )/ 3600
|
||||||
|
FROM current_sesh, bonus_userid
|
||||||
|
RETURNING *
|
||||||
|
)
|
||||||
|
UPDATE members
|
||||||
|
SET
|
||||||
|
tracked_time=(tracked_time + saved_sesh.duration),
|
||||||
|
coins=(coins + saved_sesh.coins_earned)
|
||||||
|
FROM saved_sesh
|
||||||
|
WHERE members.guildid=saved_sesh.guildid AND members.userid=saved_sesh.userid
|
||||||
|
RETURNING members.*;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE PLPGSQL;
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE user_config
|
||||||
|
ADD COLUMN avatar_hash TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE guild_config
|
||||||
|
ADD COLUMN name TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE members
|
||||||
|
ADD COLUMN display_name TEXT;
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO VersionHistory (version, author) VALUES (9, 'v8-v9 migration');
|
||||||
@@ -4,7 +4,7 @@ CREATE TABLE VersionHistory(
|
|||||||
time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
author TEXT
|
author TEXT
|
||||||
);
|
);
|
||||||
INSERT INTO VersionHistory (version, author) VALUES (8, 'Initial Creation');
|
INSERT INTO VersionHistory (version, author) VALUES (9, 'Initial Creation');
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION update_timestamp_column()
|
CREATE OR REPLACE FUNCTION update_timestamp_column()
|
||||||
@@ -41,7 +41,9 @@ CREATE TABLE global_guild_blacklist(
|
|||||||
-- User configuration data {{{
|
-- User configuration data {{{
|
||||||
CREATE TABLE user_config(
|
CREATE TABLE user_config(
|
||||||
userid BIGINT PRIMARY KEY,
|
userid BIGINT PRIMARY KEY,
|
||||||
timezone TEXT
|
timezone TEXT,
|
||||||
|
topgg_vote_reminder,
|
||||||
|
avatar_hash TEXT
|
||||||
);
|
);
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
@@ -79,7 +81,8 @@ CREATE TABLE guild_config(
|
|||||||
starting_funds INTEGER,
|
starting_funds INTEGER,
|
||||||
persist_roles BOOLEAN,
|
persist_roles BOOLEAN,
|
||||||
daily_study_cap INTEGER,
|
daily_study_cap INTEGER,
|
||||||
pomodoro_channel BIGINT
|
pomodoro_channel BIGINT,
|
||||||
|
name TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE ignored_members(
|
CREATE TABLE ignored_members(
|
||||||
@@ -166,7 +169,9 @@ CREATE TABLE reminders(
|
|||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
message_link TEXT,
|
message_link TEXT,
|
||||||
interval INTEGER,
|
interval INTEGER,
|
||||||
created_at TIMESTAMP DEFAULT (now() at time zone 'utc')
|
created_at TIMESTAMP DEFAULT (now() at time zone 'utc'),
|
||||||
|
title TEXT,
|
||||||
|
footer TEXT
|
||||||
);
|
);
|
||||||
CREATE INDEX reminder_users ON reminders (userid);
|
CREATE INDEX reminder_users ON reminders (userid);
|
||||||
-- }}}
|
-- }}}
|
||||||
@@ -402,6 +407,7 @@ CREATE TABLE members(
|
|||||||
last_workout_start TIMESTAMP,
|
last_workout_start TIMESTAMP,
|
||||||
last_study_badgeid INTEGER REFERENCES study_badges ON DELETE SET NULL,
|
last_study_badgeid INTEGER REFERENCES study_badges ON DELETE SET NULL,
|
||||||
video_warned BOOLEAN DEFAULT FALSE,
|
video_warned BOOLEAN DEFAULT FALSE,
|
||||||
|
display_name TEXT,
|
||||||
_timestamp TIMESTAMP DEFAULT (now() at time zone 'utc'),
|
_timestamp TIMESTAMP DEFAULT (now() at time zone 'utc'),
|
||||||
PRIMARY KEY(guildid, userid)
|
PRIMARY KEY(guildid, userid)
|
||||||
);
|
);
|
||||||
@@ -417,7 +423,7 @@ CREATE TYPE SessionChannelType AS ENUM (
|
|||||||
'STANDARD',
|
'STANDARD',
|
||||||
'ACCOUNTABILITY',
|
'ACCOUNTABILITY',
|
||||||
'RENTED',
|
'RENTED',
|
||||||
'EXTERNAL',
|
'EXTERNAL'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -512,6 +518,25 @@ AS $$
|
|||||||
stream_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - stream_start)), 0) AS total_stream_duration,
|
stream_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - stream_start)), 0) AS total_stream_duration,
|
||||||
video_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - video_start)), 0) AS total_video_duration,
|
video_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - video_start)), 0) AS total_video_duration,
|
||||||
live_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - live_start)), 0) AS total_live_duration
|
live_duration + COALESCE(EXTRACT(EPOCH FROM (NOW() - live_start)), 0) AS total_live_duration
|
||||||
|
), bonus_userid AS (
|
||||||
|
SELECT COUNT(boostedTimestamp),
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM Topgg
|
||||||
|
WHERE Topgg.userid=_userid AND EXTRACT(EPOCH FROM (NOW() - boostedTimestamp)) < 12.5*60*60
|
||||||
|
) THEN
|
||||||
|
(array_agg(
|
||||||
|
CASE WHEN boostedTimestamp <= current_sesh.start_time THEN
|
||||||
|
1.25
|
||||||
|
ELSE
|
||||||
|
(((current_sesh.total_duration - EXTRACT(EPOCH FROM (boostedTimestamp - current_sesh.start_time)))/current_sesh.total_duration)*0.25)+1
|
||||||
|
END))[1]
|
||||||
|
ELSE
|
||||||
|
1
|
||||||
|
END
|
||||||
|
AS bonus
|
||||||
|
FROM Topgg, current_sesh
|
||||||
|
WHERE Topgg.userid=_userid AND EXTRACT(EPOCH FROM (NOW() - boostedTimestamp)) < 12.5*60*60
|
||||||
|
ORDER BY (array_agg(boostedTimestamp))[1] DESC LIMIT 1
|
||||||
), saved_sesh AS (
|
), saved_sesh AS (
|
||||||
INSERT INTO session_history (
|
INSERT INTO session_history (
|
||||||
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
||||||
@@ -520,8 +545,8 @@ AS $$
|
|||||||
) SELECT
|
) SELECT
|
||||||
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
guildid, userid, channelid, rating, tag, channel_type, start_time,
|
||||||
total_duration, total_stream_duration, total_video_duration, total_live_duration,
|
total_duration, total_stream_duration, total_video_duration, total_live_duration,
|
||||||
(total_duration * hourly_coins + live_duration * hourly_live_coins) / 3600
|
((total_duration * hourly_coins + live_duration * hourly_live_coins) * bonus_userid.bonus )/ 3600
|
||||||
FROM current_sesh
|
FROM current_sesh, bonus_userid
|
||||||
RETURNING *
|
RETURNING *
|
||||||
)
|
)
|
||||||
UPDATE members
|
UPDATE members
|
||||||
@@ -766,4 +791,13 @@ create TABLE timers(
|
|||||||
CREATE INDEX timers_guilds ON timers (guildid);
|
CREATE INDEX timers_guilds ON timers (guildid);
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
-- Topgg Data {{{
|
||||||
|
create TABLE topgg(
|
||||||
|
voteid SERIAL PRIMARY KEY,
|
||||||
|
userid BIGINT NOT NULL,
|
||||||
|
boostedTimestamp TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
CREATE INDEX topgg_userid_timestamp ON topgg (userid, boostedTimestamp);
|
||||||
|
-- }}}
|
||||||
|
|
||||||
-- vim: set fdm=marker:
|
-- vim: set fdm=marker:
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ discord.py==1.7.3
|
|||||||
iso8601==0.1.16
|
iso8601==0.1.16
|
||||||
psycopg2==2.9.1
|
psycopg2==2.9.1
|
||||||
pytz==2021.1
|
pytz==2021.1
|
||||||
|
topggpy
|
||||||
|
|||||||
Reference in New Issue
Block a user