diff --git a/bot/LionContext.py b/bot/LionContext.py new file mode 100644 index 00000000..cf068d9d --- /dev/null +++ b/bot/LionContext.py @@ -0,0 +1,48 @@ +import datetime + +import discord +from cmdClient import Context +from cmdClient.logger import log + +reply_callbacks: list = [] # TODO Extend to all cmdClient.Context.Utils to give flexibility to modules + +class LionContext(Context): + """ + Subclass to allow easy attachment of custom hooks and structure to contexts. + """ + + def __init__(self, client, **kwargs): + super().__init__(client, **kwargs) + + @classmethod + def util(self, util_func): + """ + Decorator to make a utility function available as a Context instance method + """ + log('added util_function: ' + util_func.__name__) + + def util_fun_wrapper(*args, **kwargs): + [args, kwargs] = self.util_pre(util_func, *args, **kwargs) + return util_func(*args, **kwargs) + + util_fun_wrapper.__name__ = util_func.__name__ # Hack + + super().util(util_fun_wrapper) + + @classmethod + def util_pre(self, util_func, *args, **kwargs): + + if util_func.__name__ == 'reply': + for cb in reply_callbacks: + [args, kwargs] = cb(util_func, *args, **kwargs) # Nesting handlers. Note: args and kwargs are mutable + + return [args, kwargs] + + +def register_reply_callback(func): + reply_callbacks.append(func) + +def unregister_reply_callback(func): + reply_callbacks.remove(func) + + diff --git a/bot/core/data.py b/bot/core/data.py index 9b7f0bdd..41836a3c 100644 --- a/bot/core/data.py +++ b/bot/core/data.py @@ -14,7 +14,7 @@ meta = RowTable( user_config = RowTable( 'user_config', - ('userid', 'timezone'), + ('userid', 'timezone', 'remaind_upvote'), 'userid', cache=TTLCache(5000, ttl=60*5) ) diff --git a/bot/core/lion.py b/bot/core/lion.py index 4b3baea9..c06fff48 100644 --- a/bot/core/lion.py +++ b/bot/core/lion.py @@ -5,6 +5,8 @@ from meta import client from data import tables as tb from settings import UserSettings, GuildSettings +# Give modules the ability to intercept addCoin() calls +_lion_add_coins_callbacks: list = [] class Lion: """ @@ -214,10 +216,13 @@ class Lion: timezone = self.settings.timezone.value 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. """ + for cb in _lion_add_coins_callbacks: + [self, amount, flush, ignorebonus] = cb(self, amount, flush, ignorebonus) + self._pending_coins += amount self._pending[self.key] = self if flush: @@ -251,3 +256,12 @@ class Lion: for lion in lions: lion._pending_coins -= int(lion._pending_coins) cls._pending.pop(lion.key, None) + + +# TODO Expand this callback system to other functions +# Note: callbacks MUST return [self, amount, flush, ignorebonus] modified/unmodified +def register_addcoins_callback(func): + _lion_add_coins_callbacks.append(func) + +def unregister_addcoins_callback(func): + _lion_add_coins_callbacks.remove(func) diff --git a/bot/data/formatters.py b/bot/data/formatters.py index 4bdccbc3..1509ed08 100644 --- a/bot/data/formatters.py +++ b/bot/data/formatters.py @@ -69,6 +69,8 @@ def _format_selectkeys(keys): """ if not keys: return "*" + elif type(keys) is str: + return keys else: return ", ".join(keys) diff --git a/bot/meta/client.py b/bot/meta/client.py index 50414aa8..a3f02877 100644 --- a/bot/meta/client.py +++ b/bot/meta/client.py @@ -3,7 +3,7 @@ from cmdClient.cmdClient import cmdClient from .config import conf from .sharding import shard_number, shard_count - +from LionContext import LionContext # Initialise client owners = [int(owner) for owner in conf.bot.getlist('owners')] @@ -14,6 +14,7 @@ client = cmdClient( owners=owners, intents=intents, shard_id=shard_number, - shard_count=shard_count + shard_count=shard_count, + baseContext=LionContext ) client.conf = conf diff --git a/bot/modules/__init__.py b/bot/modules/__init__.py index b1763cae..20630a1b 100644 --- a/bot/modules/__init__.py +++ b/bot/modules/__init__.py @@ -7,6 +7,7 @@ from .stats import * from .user_config import * from .workout import * from .todo import * +from .topgg import * from .reminders import * from .renting import * from .moderation import * diff --git a/bot/modules/economy/send_cmd.py b/bot/modules/economy/send_cmd.py index 5086ee67..db997f22 100644 --- a/bot/modules/economy/send_cmd.py +++ b/bot/modules/economy/send_cmd.py @@ -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)) # Finally, send the amount and the ack message - target_lion.addCoins(amount) + target_lion.addCoins(amount, ignorebonus=True) source_lion.addCoins(-amount) embed = discord.Embed( diff --git a/bot/modules/guild_admin/economy/set_coins.py b/bot/modules/guild_admin/economy/set_coins.py index c0744a13..cbbf85d9 100644 --- a/bot/modules/guild_admin/economy/set_coins.py +++ b/bot/modules/guild_admin/economy/set_coins.py @@ -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 target_coins_to_set = target_lion.coins + amount 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: 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 else: return await ctx.embed_reply("Member coins cannot be more than {}".format(POSTGRES_INT_MAX)) diff --git a/bot/modules/guild_admin/reaction_roles/tracker.py b/bot/modules/guild_admin/reaction_roles/tracker.py index 17a64960..bfc43ba5 100644 --- a/bot/modules/guild_admin/reaction_roles/tracker.py +++ b/bot/modules/guild_admin/reaction_roles/tracker.py @@ -1,4 +1,5 @@ import asyncio +from codecs import ignore_errors import logging import traceback import datetime @@ -500,7 +501,7 @@ class ReactionRoleMessage: if price and refund: # Give the user the refund lion = Lion.fetch(self.guild.id, member.id) - lion.addCoins(price) + lion.addCoins(price, ignorebonus=True) # Notify the user embed = discord.Embed( diff --git a/bot/modules/reminders/data.py b/bot/modules/reminders/data.py index 3c57c6bb..ee5b1a41 100644 --- a/bot/modules/reminders/data.py +++ b/bot/modules/reminders/data.py @@ -1,8 +1,8 @@ -from data import RowTable +from data.interfaces import RowTable reminders = RowTable( 'reminders', - ('reminderid', 'userid', 'remind_at', 'content', 'message_link', 'interval', 'created_at'), + ('reminderid', 'userid', 'remind_at', 'content', 'message_link', 'interval', 'created_at', 'title', 'footer'), 'reminderid' ) diff --git a/bot/modules/reminders/reminder.py b/bot/modules/reminders/reminder.py index 67956a1d..15772b6a 100644 --- a/bot/modules/reminders/reminder.py +++ b/bot/modules/reminders/reminder.py @@ -150,12 +150,14 @@ class Reminder: # Build the message 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(), description=self.data.content, timestamp=datetime.datetime.utcnow() ) - embed.add_field(name="Context?", value="[Click here]({})".format(self.data.message_link)) + + if self.data.message_link: + embed.add_field(name="Context?", value="[Click here]({})".format(self.data.message_link)) if self.data.interval: embed.add_field( @@ -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 if self.data.interval: next_time = self.data.remind_at + datetime.timedelta(seconds=self.data.interval) diff --git a/bot/modules/topgg/__init__.py b/bot/modules/topgg/__init__.py new file mode 100644 index 00000000..bf762868 --- /dev/null +++ b/bot/modules/topgg/__init__.py @@ -0,0 +1,6 @@ +from .module import module + +from . import webhook +from . import commands +from . import data +from . import settings \ No newline at end of file diff --git a/bot/modules/topgg/commands.py b/bot/modules/topgg/commands.py new file mode 100644 index 00000000..9bb63cd8 --- /dev/null +++ b/bot/modules/topgg/commands.py @@ -0,0 +1,76 @@ +import discord +from .module import module +from wards import guild_admin +from bot.cmdClient.checks.global_perms import in_guild +from settings.user_settings import UserSettings + +from .webhook import on_dbl_vote +from .utils import * + +@module.cmd( + "forcevote", + desc="Simulate Topgg Vote.", + group="Guild Admin", + aliases=('debugvote', 'topggvote') +) +@guild_admin() +async def cmd_forcevote(ctx): + """ + Usage``: + {prefix}forcevote + Description: + Simulate Topgg 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)) + + +@module.cmd( + "vote", + desc="Get top.gg boost for 25% more LCs.", + group="Economy", + aliases=('topgg', 'topggvote', 'upvote') +) +@in_guild() +async def cmd_vote(ctx): + """ + Usage``: + {prefix}vote + Description: + Get Top.gg bot's link for +25% Economy boost. + """ + target = ctx.author + + embed=discord.Embed( + title="Claim your boost!", + description='Please click [here](https://top.gg/bot/889078613817831495/vote) vote and support our bot!\n\nThank 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) + + +@module.cmd( + "vote_reminder", + group="Personal Settings", + desc="Turn on/off boost reminders." +) +async def cmd_remind_vote(ctx): + """ + 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) diff --git a/bot/modules/topgg/data.py b/bot/modules/topgg/data.py new file mode 100644 index 00000000..b12a4120 --- /dev/null +++ b/bot/modules/topgg/data.py @@ -0,0 +1,8 @@ +from data.interfaces import RowTable + +topggvotes = RowTable( + 'topgg', + ('voteid', 'userid', 'boostedTimestamp'), + 'voteid' +) + diff --git a/bot/modules/topgg/module.py b/bot/modules/topgg/module.py new file mode 100644 index 00000000..517058f9 --- /dev/null +++ b/bot/modules/topgg/module.py @@ -0,0 +1,68 @@ +from multiprocessing import context +from LionModule import LionModule +from LionContext import register_reply_callback, unregister_reply_callback +from bot.data.conditions import NOT +from meta.client import client +from core.lion import register_addcoins_callback, unregister_addcoins_callback + +from .utils import * +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 register_hook(client): + init_webhook() + register_reply_callback(reply) + register_addcoins_callback(cb_addCoins) + + client.log("Registered LionContext reply util hook.", context="Topgg" ) + +@module.unload_task +async def unregister_hook(client): + unregister_reply_callback(reply) + unregister_addcoins_callback(cb_addCoins) + + client.log("Unregistered LionContext reply util hook.", context="Topgg" ) + + +def reply(util_func, *args, **kwargs): + # *args will have LionContext + # **kwargs should have the actual reply() call's extra arguments + + if not get_last_voted_timestamp(args[0].author.id): + args = list(args) + + upvote_info_formatted = upvote_info.format(lion_yayemote, args[0].best_prefix, lion_loveemote) + + if 'embed' in kwargs: + kwargs['embed'].add_field( + name="\u200b", + value=( + upvote_info_formatted + ), + inline=False + ) + elif 'content' in args and args['content']: + args['content'] += '\n\n' + upvote_info_formatted + elif len(args) > 1: + args[1] += '\n\n' + upvote_info_formatted + else: + args['content'] = '\n\n' + upvote_info_formatted + + args = tuple(args) + + return [args, kwargs] + + +def cb_addCoins(self, amount, flush, ignorebonus): + + client.log('cb_addCoins hook with amount={} ignorebonux={}'.format(amount, ignorebonus), context='Topgg') + + if not ignorebonus and amount > 0 and get_last_voted_timestamp(self.userid): + amount *= 1.25 + client.log('cb_addCoins with bonus={}'.format(amount), context='Topgg') + + return [self, amount, flush, ignorebonus] \ No newline at end of file diff --git a/bot/modules/topgg/settings.py b/bot/modules/topgg/settings.py new file mode 100644 index 00000000..2686bc02 --- /dev/null +++ b/bot/modules/topgg/settings.py @@ -0,0 +1,45 @@ +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 * + +@UserSettings.attach_setting +class topgg_vote_remainder(Boolean, UserSetting): + attr_name = 'vote_remainder' + _data_column = 'remaind_upvote' + + _default = True + + display_name = 'Upvote Reminder' + desc = "Turn on/off DM Reminders to Upvote me." + long_desc = ("Enable or disable DM boost reminders.",) + + @property + def success_response(self): + if self.value: + # Check if reminder is already running + create_remainder(self.id) + + return ( + " I will send you boost reminders.\n\n" + "`Please make sure your DMs are open.`" + ) + 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 won't send you boost reminders." + ) \ No newline at end of file diff --git a/bot/modules/topgg/utils.py b/bot/modules/topgg/utils.py new file mode 100644 index 00000000..233c61b1 --- /dev/null +++ b/bot/modules/topgg/utils.py @@ -0,0 +1,79 @@ +from email.mime import image +import discord +import datetime +from meta.client import client +from bot.settings.setting_types import Integer +from meta import sharding + +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!\nClick [here]({}) to vote, thank you for the support!".format(topgg_upvote_link) + +lion_loveemote = '<:lionloveemote:933003977656795136>' +lion_yayemote = '<:lionyayemote:933003929229352990>' + +# 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 +def get_last_voted_timestamp(userid: Integer): + return db.topggvotes.select_one_where( + userid=userid, + select_columns="boostedTimestamp", + boostedTimestamp=GEQ(datetime.datetime.utcnow() - datetime.timedelta(hours=12.5)), + _extra="ORDER BY boostedTimestamp DESC LIMIT 1" + ) + +# Checks if a remainder is already running (immaterial of remind_at time) +# If no remainder exists creates a new remainder and schedules it +def create_remainder(userid): + 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 datetime.datetime.utcnow() + 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 diff --git a/bot/modules/topgg/webhook.py b/bot/modules/topgg/webhook.py new file mode 100644 index 00000000..eb0eb1c5 --- /dev/null +++ b/bot/modules/topgg/webhook.py @@ -0,0 +1,36 @@ +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 * + +@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")) diff --git a/bot/utils/interactive.py b/bot/utils/interactive.py index 986361d5..a232604e 100644 --- a/bot/utils/interactive.py +++ b/bot/utils/interactive.py @@ -1,8 +1,10 @@ import asyncio import discord -from cmdClient import Context +from LionContext import LionContext from cmdClient.lib import UserCancelled, ResponseTimedOut +import datetime +from cmdClient import lib from .lib import paginate_list # TODO: Interactive locks @@ -19,7 +21,7 @@ async def discord_shield(coro): pass -@Context.util +@LionContext.util async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout=300): """ Add a cancellation reaction to the given message. @@ -62,7 +64,7 @@ async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout= return task -@Context.util +@LionContext.util async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=None): """ Listen for a one of a particular set of input strings, @@ -114,7 +116,7 @@ async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=Non return message -@Context.util +@LionContext.util async def selector(ctx, header, select_from, timeout=120, max_len=20): """ Interactive routine to prompt the `ctx.author` to select an item from a list. @@ -214,7 +216,7 @@ async def selector(ctx, header, select_from, timeout=120, max_len=20): return result -@Context.util +@LionContext.util async def pager(ctx, pages, locked=True, start_at=0, add_cancel=False, **kwargs): """ Shows the user each page from the provided list `pages` one at a time, @@ -371,7 +373,7 @@ async def _pager(ctx, out_msg, pages, locked, start_at, add_cancel, **kwargs): pass -@Context.util +@LionContext.util async def input(ctx, msg="", timeout=120): """ Listen for a response in the current channel, from ctx.author. @@ -413,7 +415,7 @@ async def input(ctx, msg="", timeout=120): return result -@Context.util +@LionContext.util async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False): """ Ask ctx.author a yes/no question. @@ -459,3 +461,41 @@ async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False): if result in ["n", "no"]: return 0 return 1 + +# this reply() will be overide baseContext's reply with LionContext's, whcih can +# hook pre_execution of any util. +# Using this system, Module now have much power to change Context's utils +@LionContext.util +async def reply(ctx, content=None, allow_everyone=False, **kwargs): + """ + Helper function to reply in the current channel. + """ + if not allow_everyone: + if content: + content = lib.sterilise_content(content) + + message = await ctx.ch.send(content=content, **kwargs) + ctx.sent_messages.append(message) + return message + + +# this reply() will be overide baseContext's reply +@LionContext.util +async def error_reply(ctx, error_str): + """ + Notify the user of a user level error. + Typically, this will occur in a red embed, posted in the command channel. + """ + embed = discord.Embed( + colour=discord.Colour.red(), + description=error_str, + timestamp=datetime.datetime.utcnow() + ) + try: + message = await ctx.ch.send(embed=embed) + ctx.sent_messages.append(message) + return message + except discord.Forbidden: + message = await ctx.reply(error_str) + ctx.sent_messages.append(message) + return message \ No newline at end of file diff --git a/config/example-bot.conf b/config/example-bot.conf index d2ec5dd1..cdaaf501 100644 --- a/config/example-bot.conf +++ b/config/example-bot.conf @@ -14,3 +14,7 @@ data_appid = LionBot shard_count = 1 lion_sync_period = 60 + +topgg_password = +topgg_route = +topgg_port = \ No newline at end of file diff --git a/data/migration/v8-v9/migration.sql b/data/migration/v8-v9/migration.sql new file mode 100644 index 00000000..5a46d5e1 --- /dev/null +++ b/data/migration/v8-v9/migration.sql @@ -0,0 +1,76 @@ +ALTER TABLE user_config + ADD COLUMN remaind_upvote BOOLEAN DEFAULT TRUE + +ALTER TABLE reminders + ADD COLUMN title TEXT DEFAULT NULL, + ADD COLUMN footer TEXT DEFAULT NULL + +-- Topgg Data {{{ +CREATE TABLE IF NOT EXISTS topgg( + voteid SERIAL PRIMARY KEY, + userid BIGINT NOT NULL, + boostedTimestamp TIMESTAMPTZ NOT NULL +); +CREATE INDEX topgg_member ON topgg (userid); +-- }}} + +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; +-- }}} + +INSERT INTO VersionHistory (version, author) VALUES (8, 'v8-v9 migration'); \ No newline at end of file diff --git a/data/schema.sql b/data/schema.sql index 51432086..4ee4378d 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -41,7 +41,8 @@ CREATE TABLE global_guild_blacklist( -- User configuration data {{{ CREATE TABLE user_config( userid BIGINT PRIMARY KEY, - timezone TEXT + timezone TEXT, + remaind_upvote BOOLEAN DEFAULT TRUE ); -- }}} @@ -166,7 +167,9 @@ CREATE TABLE reminders( content TEXT NOT NULL, message_link TEXT, interval INTEGER, - created_at TIMESTAMP DEFAULT (now() at time zone 'utc') + created_at TIMESTAMP DEFAULT (now() at time zone 'utc'), + title TEXT DEFAULT NULL, + footer TEXT DEFAULT NULL ); CREATE INDEX reminder_users ON reminders (userid); -- }}} @@ -512,6 +515,25 @@ AS $$ 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, @@ -520,8 +542,8 @@ AS $$ ) 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) / 3600 - FROM current_sesh + ((total_duration * hourly_coins + live_duration * hourly_live_coins) * bonus_userid.bonus )/ 3600 + FROM current_sesh, bonus_userid RETURNING * ) UPDATE members @@ -766,4 +788,13 @@ create TABLE timers( 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_member ON topgg (userid); +-- }}} + -- vim: set fdm=marker: