From 3e0c1be33e2e6200eeedabac5676dc3803f0bfb7 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Thu, 13 Jan 2022 14:03:23 +0530 Subject: [PATCH 01/20] Init Framework --- bot/modules/__init__.py | 1 + bot/modules/topgg/__init__.py | 3 +++ bot/modules/topgg/module.py | 3 +++ bot/modules/topgg/topgg.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 bot/modules/topgg/__init__.py create mode 100644 bot/modules/topgg/module.py create mode 100644 bot/modules/topgg/topgg.py 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/topgg/__init__.py b/bot/modules/topgg/__init__.py new file mode 100644 index 00000000..d9785b83 --- /dev/null +++ b/bot/modules/topgg/__init__.py @@ -0,0 +1,3 @@ +from .module import module + +from . import topgg diff --git a/bot/modules/topgg/module.py b/bot/modules/topgg/module.py new file mode 100644 index 00000000..783d98a9 --- /dev/null +++ b/bot/modules/topgg/module.py @@ -0,0 +1,3 @@ +from LionModule import LionModule + +module = LionModule("Topgg") diff --git a/bot/modules/topgg/topgg.py b/bot/modules/topgg/topgg.py new file mode 100644 index 00000000..ad873a84 --- /dev/null +++ b/bot/modules/topgg/topgg.py @@ -0,0 +1,29 @@ +from meta import client +import discord +import topgg + +# client.log("test") + +import topgg + +# This example uses topggpy's webhook system. +client.topgg_webhook = topgg.WebhookManager(client).dbl_webhook("/dblwebhook", "nopassword123") + +# The port must be a number between 1024 and 49151. +client.topgg_webhook.run(5000) # this method can be awaited as well + + +@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}") + if data["type"] == "test": + # this is roughly equivalent to + # `return await on_dbl_test(data)` in this case + 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}") \ No newline at end of file From a2fcdf075feb7d2529c4055bcd5555e5b834d46f Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Fri, 14 Jan 2022 18:49:51 +0530 Subject: [PATCH 02/20] [DB] Init Topgg DB Framework --- bot/modules/topgg/data.py | 8 ++++++++ data/schema.sql | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 bot/modules/topgg/data.py diff --git a/bot/modules/topgg/data.py b/bot/modules/topgg/data.py new file mode 100644 index 00000000..829ce6da --- /dev/null +++ b/bot/modules/topgg/data.py @@ -0,0 +1,8 @@ +from data import RowTable, Table + +topggvotes = RowTable( + 'topgg', + ('voteid', 'userid', 'boostedTimestamp'), + 'voteid' +) + diff --git a/data/schema.sql b/data/schema.sql index 51432086..c5dfca6f 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -144,6 +144,15 @@ CREATE TABLE tasklist( ); CREATE INDEX tasklist_users ON tasklist (userid); +-- Topgg Data {{{ +create TABLE topgg( + voteid SERIAL PRIMARY KEY, + userid BIGINT NOT NULL, + boostedTimestamp TIMESTAMPTZ NOT NULL +); +CREATE INDEX topgg_member ON topgg (guildid, userid); +-- }}} + CREATE TABLE tasklist_channels( guildid BIGINT NOT NULL, channelid BIGINT NOT NULL From 437adf87e4787a9d23b1c2be4dcb4cfc41217ce7 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Fri, 14 Jan 2022 18:52:51 +0530 Subject: [PATCH 03/20] [Context] Implement LionContext Need to append a Text to all bot replies to ask people to !vote - Implement LionContext - Implement Callback handler to enable Modules intercept cmdClient.Context.Utils() (attr calls) --- bot/LionContext.py | 48 +++++++++++++++++++++++++++++++++++ bot/meta/client.py | 5 ++-- bot/utils/interactive.py | 54 ++++++++++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 bot/LionContext.py 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/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/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 From 521dd340bc7f49dc24d4fdd6e8f5e402425a465c Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Fri, 14 Jan 2022 18:54:58 +0530 Subject: [PATCH 04/20] [Topgg] Update module - Implement Sending DM to user - Implement launch and unload tasks to register reply Ctx.Util calls - Implement reply callback handler - Implement !focevote cmd to simulate vote --- bot/modules/topgg/topgg.py | 113 ++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 9 deletions(-) diff --git a/bot/modules/topgg/topgg.py b/bot/modules/topgg/topgg.py index ad873a84..2ddf0ae1 100644 --- a/bot/modules/topgg/topgg.py +++ b/bot/modules/topgg/topgg.py @@ -1,10 +1,12 @@ from meta import client import discord import topgg +import datetime -# client.log("test") - -import topgg +from .module import module +from wards import guild_admin +from cmdClient.Context import Context +from . import data as db # This example uses topggpy's webhook system. client.topgg_webhook = topgg.WebhookManager(client).dbl_webhook("/dblwebhook", "nopassword123") @@ -16,14 +18,107 @@ client.topgg_webhook.run(5000) # this method can be awaited as well @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}") - if data["type"] == "test": - # this is roughly equivalent to - # `return await on_dbl_test(data)` in this case - return client.dispatch("dbl_test", data) + client.log(f"Received a vote: \n{data}") + db.topggvotes.insert( + userid=data['user'], + boostedTimestamp = datetime.datetime.utcnow() + ) + + await send_user_dm(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}") \ No newline at end of file + client.log(f"Received a test vote:\n{data}") + + +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: + await user.send("Thankyou for upvoting.\n https://cdn.discordapp.com/attachments/908283085999706153/930559064323268618/unknown.png") + except discord.HTTPException: + # Nothing we can really do here. Maybe tell the user about their reminder next time? + pass + + +from LionContext import register_reply_callback, unregister_reply_callback + +@module.launch_task +async def register_hook(client): + client.log("register_reply_hook " ) + + register_reply_callback(reply) + + +@module.unload_task +async def unregister_hook(client): + client.log("register_reply_hook " ) + + unregister_reply_callback(reply) + + +def reply(util_func, *args, **kwargs): + # *args will have LionContext + # **kwargs should have the actual reply() call's extra arguments + + args = list(args) + + if 'embed' in kwargs: + kwargs['embed'].add_field( + name="\u200b", + value=( + f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + ), + inline=False + ) + elif 'content' in args and args['content']: + args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + elif len(args) > 1: + args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + else: + args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + + args = tuple(args) + client.log('test') + + return [args, kwargs] + + +@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)) + \ No newline at end of file From 6e005cf0427acec42febddd71df587a6b04b7fc7 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Sat, 15 Jan 2022 16:02:52 +0530 Subject: [PATCH 05/20] [Data-Interface] Allow single column selection --- bot/data/formatters.py | 2 ++ 1 file changed, 2 insertions(+) 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) From b92866f95481db65817b03340de4f75857e29187 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Sat, 15 Jan 2022 16:06:08 +0530 Subject: [PATCH 06/20] Implement !vote and update reply cbHandler - Also init'ed Remind me framework --- bot/modules/topgg/data.py | 2 +- bot/modules/topgg/topgg.py | 83 +++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/bot/modules/topgg/data.py b/bot/modules/topgg/data.py index 829ce6da..fef6153a 100644 --- a/bot/modules/topgg/data.py +++ b/bot/modules/topgg/data.py @@ -1,4 +1,4 @@ -from data import RowTable, Table +from data.interfaces import RowTable, Table topggvotes = RowTable( 'topgg', diff --git a/bot/modules/topgg/topgg.py b/bot/modules/topgg/topgg.py index 2ddf0ae1..fd204324 100644 --- a/bot/modules/topgg/topgg.py +++ b/bot/modules/topgg/topgg.py @@ -1,3 +1,4 @@ +from bot.cmdClient.checks.global_perms import in_guild from meta import client import discord import topgg @@ -7,6 +8,7 @@ from .module import module from wards import guild_admin from cmdClient.Context import Context from . import data as db +from data.conditions import GEQ # This example uses topggpy's webhook system. client.topgg_webhook = topgg.WebhookManager(client).dbl_webhook("/dblwebhook", "nopassword123") @@ -26,6 +28,7 @@ async def on_dbl_vote(data): ) await send_user_dm(data['user']) + check_remainder_settings(data['user']) if data["type"] == "test": return client.dispatch("dbl_test", data) @@ -72,25 +75,34 @@ def reply(util_func, *args, **kwargs): # *args will have LionContext # **kwargs should have the actual reply() call's extra arguments - args = list(args) + author = args[0].author.id - if 'embed' in kwargs: - kwargs['embed'].add_field( - name="\u200b", - value=( - f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - ), - inline=False - ) - elif 'content' in args and args['content']: - args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - elif len(args) > 1: - args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - else: - args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + if not db.topggvotes.select_one_where( + userid=author, + # start_at=LEQ(utc_now() - datetime.timedelta(hours=1)), + # start_at=LEQ(utc_now()), + select_columns="boostedTimestamp", + boostedTimestamp=GEQ(datetime.datetime.utcnow() - datetime.timedelta(hours=12.5)), + _extra="ORDER BY boostedTimestamp DESC LIMIT 1" + ): + args = list(args) + if 'embed' in kwargs: + kwargs['embed'].add_field( + name="\u200b", + value=( + f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + ), + inline=False + ) + elif 'content' in args and args['content']: + args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + elif len(args) > 1: + args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + else: + args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - args = tuple(args) - client.log('test') + args = tuple(args) + client.log('test') return [args, kwargs] @@ -121,4 +133,39 @@ async def cmd_forcevote(ctx): await on_dbl_vote({"user": target.id, "type": "test"}) return await ctx.reply('Topgg vote simulation successful on {}'.format(target)) - \ No newline at end of file + + +@module.cmd( + "vote", + desc="Get Top.gg bot's link for Economy boost.", + 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 + + return await ctx.reply('My Top.gg vote link is here: https://top.gg/bot/889078613817831495/vote \nThanks!') + + +@module.cmd( + "remind_vote", + group="Personal Settings", + desc="Turn on/off DM Remainder to Upvote me.", + long_help="Use this setting to enable/disable DM remainders from me to upvote on Top.gg." +) +async def cmd_remind_vote(ctx): + """ + Usage``: + {prefix}remind_vote on + {prefix}remind_vote off + + Use this setting to enable/disable DM remainders from me to upvote on Top.gg. + """ + pass \ No newline at end of file From 36a98b1224fa1b9231ec64bc06611a3a1593be77 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:08:25 +0530 Subject: [PATCH 07/20] [Remainders] Update - Send no context if None type is present --- bot/modules/reminders/reminder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/modules/reminders/reminder.py b/bot/modules/reminders/reminder.py index 67956a1d..163c7636 100644 --- a/bot/modules/reminders/reminder.py +++ b/bot/modules/reminders/reminder.py @@ -155,7 +155,9 @@ class Reminder: 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( From 0bb96aeeb21275a504a6100bf7224db123301249 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:10:25 +0530 Subject: [PATCH 08/20] [DB][CORE][Topgg] Update DB to accommodate Topgg module --- bot/core/data.py | 2 +- bot/modules/reminders/data.py | 2 +- bot/modules/topgg/data.py | 2 +- data/migration/v8-v9/migration.sql | 72 ++++++++++++++++++++++++++++++ data/schema.sql | 44 +++++++++++++----- 5 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 data/migration/v8-v9/migration.sql 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/modules/reminders/data.py b/bot/modules/reminders/data.py index 3c57c6bb..bf8f75fc 100644 --- a/bot/modules/reminders/data.py +++ b/bot/modules/reminders/data.py @@ -1,4 +1,4 @@ -from data import RowTable +from data.interfaces import RowTable reminders = RowTable( diff --git a/bot/modules/topgg/data.py b/bot/modules/topgg/data.py index fef6153a..b12a4120 100644 --- a/bot/modules/topgg/data.py +++ b/bot/modules/topgg/data.py @@ -1,4 +1,4 @@ -from data.interfaces import RowTable, Table +from data.interfaces import RowTable topggvotes = RowTable( 'topgg', diff --git a/data/migration/v8-v9/migration.sql b/data/migration/v8-v9/migration.sql new file mode 100644 index 00000000..63d148c0 --- /dev/null +++ b/data/migration/v8-v9/migration.sql @@ -0,0 +1,72 @@ +ALTER TABLE user_config + ADD COLUMN remaind_upvote BOOLEAN DEFAULT TRUE + +-- 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 (guildid, 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 c5dfca6f..c73d8749 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 ); -- }}} @@ -144,15 +145,6 @@ CREATE TABLE tasklist( ); CREATE INDEX tasklist_users ON tasklist (userid); --- Topgg Data {{{ -create TABLE topgg( - voteid SERIAL PRIMARY KEY, - userid BIGINT NOT NULL, - boostedTimestamp TIMESTAMPTZ NOT NULL -); -CREATE INDEX topgg_member ON topgg (guildid, userid); --- }}} - CREATE TABLE tasklist_channels( guildid BIGINT NOT NULL, channelid BIGINT NOT NULL @@ -521,6 +513,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, @@ -529,8 +540,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 @@ -775,4 +786,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 (guildid, userid); +-- }}} + -- vim: set fdm=marker: From e526503bac1a733d4c17ea97808d5686ef378b7b Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:11:10 +0530 Subject: [PATCH 09/20] [Conf] Topgg specifc settings --- config/example-bot.conf | 4 ++++ 1 file changed, 4 insertions(+) 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 From 724a39a940a6760311b8b513497ff9424f110fd1 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:14:18 +0530 Subject: [PATCH 10/20] [Topgg] Finalize module framework --- bot/modules/topgg/__init__.py | 5 +- bot/modules/topgg/module.py | 45 +++++++++ bot/modules/topgg/topgg.py | 171 ---------------------------------- 3 files changed, 49 insertions(+), 172 deletions(-) delete mode 100644 bot/modules/topgg/topgg.py diff --git a/bot/modules/topgg/__init__.py b/bot/modules/topgg/__init__.py index d9785b83..bf762868 100644 --- a/bot/modules/topgg/__init__.py +++ b/bot/modules/topgg/__init__.py @@ -1,3 +1,6 @@ from .module import module -from . import topgg +from . import webhook +from . import commands +from . import data +from . import settings \ No newline at end of file diff --git a/bot/modules/topgg/module.py b/bot/modules/topgg/module.py index 783d98a9..b6fb43f2 100644 --- a/bot/modules/topgg/module.py +++ b/bot/modules/topgg/module.py @@ -1,3 +1,48 @@ from LionModule import LionModule +from LionContext import register_reply_callback, unregister_reply_callback +from meta.client import client + +from .utils import * +from .webhook import init_webhook module = LionModule("Topgg") + +@module.launch_task +async def register_hook(client): + client.log("register_reply_hook " ) + + init_webhook() + register_reply_callback(reply) + +@module.unload_task +async def unregister_hook(client): + client.log("register_reply_hook " ) + + unregister_reply_callback(reply) + + +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) + if 'embed' in kwargs: + kwargs['embed'].add_field( + name="\u200b", + value=( + f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + ), + inline=False + ) + elif 'content' in args and args['content']: + args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + elif len(args) > 1: + args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + else: + args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + + args = tuple(args) + client.log('test') + + return [args, kwargs] diff --git a/bot/modules/topgg/topgg.py b/bot/modules/topgg/topgg.py deleted file mode 100644 index fd204324..00000000 --- a/bot/modules/topgg/topgg.py +++ /dev/null @@ -1,171 +0,0 @@ -from bot.cmdClient.checks.global_perms import in_guild -from meta import client -import discord -import topgg -import datetime - -from .module import module -from wards import guild_admin -from cmdClient.Context import Context -from . import data as db -from data.conditions import GEQ - -# This example uses topggpy's webhook system. -client.topgg_webhook = topgg.WebhookManager(client).dbl_webhook("/dblwebhook", "nopassword123") - -# The port must be a number between 1024 and 49151. -client.topgg_webhook.run(5000) # this method can be awaited as well - - -@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}") - - db.topggvotes.insert( - userid=data['user'], - boostedTimestamp = datetime.datetime.utcnow() - ) - - await send_user_dm(data['user']) - check_remainder_settings(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}") - - -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: - await user.send("Thankyou for upvoting.\n https://cdn.discordapp.com/attachments/908283085999706153/930559064323268618/unknown.png") - except discord.HTTPException: - # Nothing we can really do here. Maybe tell the user about their reminder next time? - pass - - -from LionContext import register_reply_callback, unregister_reply_callback - -@module.launch_task -async def register_hook(client): - client.log("register_reply_hook " ) - - register_reply_callback(reply) - - -@module.unload_task -async def unregister_hook(client): - client.log("register_reply_hook " ) - - unregister_reply_callback(reply) - - -def reply(util_func, *args, **kwargs): - # *args will have LionContext - # **kwargs should have the actual reply() call's extra arguments - - author = args[0].author.id - - if not db.topggvotes.select_one_where( - userid=author, - # start_at=LEQ(utc_now() - datetime.timedelta(hours=1)), - # start_at=LEQ(utc_now()), - select_columns="boostedTimestamp", - boostedTimestamp=GEQ(datetime.datetime.utcnow() - datetime.timedelta(hours=12.5)), - _extra="ORDER BY boostedTimestamp DESC LIMIT 1" - ): - args = list(args) - if 'embed' in kwargs: - kwargs['embed'].add_field( - name="\u200b", - value=( - f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - ), - inline=False - ) - elif 'content' in args and args['content']: - args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - elif len(args) > 1: - args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - else: - args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" - - args = tuple(args) - client.log('test') - - return [args, kwargs] - - -@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 bot's link for Economy boost.", - 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 - - return await ctx.reply('My Top.gg vote link is here: https://top.gg/bot/889078613817831495/vote \nThanks!') - - -@module.cmd( - "remind_vote", - group="Personal Settings", - desc="Turn on/off DM Remainder to Upvote me.", - long_help="Use this setting to enable/disable DM remainders from me to upvote on Top.gg." -) -async def cmd_remind_vote(ctx): - """ - Usage``: - {prefix}remind_vote on - {prefix}remind_vote off - - Use this setting to enable/disable DM remainders from me to upvote on Top.gg. - """ - pass \ No newline at end of file From 61d192e27b5486a936f24d42b899b77b2eda787c Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:16:14 +0530 Subject: [PATCH 11/20] Update module.py - specify logging context --- bot/modules/topgg/module.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/modules/topgg/module.py b/bot/modules/topgg/module.py index b6fb43f2..83a4069d 100644 --- a/bot/modules/topgg/module.py +++ b/bot/modules/topgg/module.py @@ -1,3 +1,4 @@ +from multiprocessing import context from LionModule import LionModule from LionContext import register_reply_callback, unregister_reply_callback from meta.client import client @@ -9,16 +10,16 @@ module = LionModule("Topgg") @module.launch_task async def register_hook(client): - client.log("register_reply_hook " ) - init_webhook() register_reply_callback(reply) + + client.log("Registered LionContext reply util hook.", context="Topgg" ) @module.unload_task async def unregister_hook(client): - client.log("register_reply_hook " ) - unregister_reply_callback(reply) + + client.log("Unregistered LionContext reply util hook.", context="Topgg" ) def reply(util_func, *args, **kwargs): @@ -43,6 +44,5 @@ def reply(util_func, *args, **kwargs): args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" args = tuple(args) - client.log('test') return [args, kwargs] From ce59a8cc3f821573b8901563edc30ecef77534a8 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:18:35 +0530 Subject: [PATCH 12/20] [Topgg] Webhook implementation --- bot/modules/topgg/webhook.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 bot/modules/topgg/webhook.py 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")) From 9615f2037e136afee68605a4cc3aa09eefb9a5cb Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:18:59 +0530 Subject: [PATCH 13/20] [Topgg] Commands, Settings and Utilities --- bot/modules/topgg/commands.py | 75 +++++++++++++++++++++++++++++++++++ bot/modules/topgg/settings.py | 45 +++++++++++++++++++++ bot/modules/topgg/utils.py | 72 +++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 bot/modules/topgg/commands.py create mode 100644 bot/modules/topgg/settings.py create mode 100644 bot/modules/topgg/utils.py diff --git a/bot/modules/topgg/commands.py b/bot/modules/topgg/commands.py new file mode 100644 index 00000000..f473fbe0 --- /dev/null +++ b/bot/modules/topgg/commands.py @@ -0,0 +1,75 @@ +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 + +@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 bot's link for Economy boost.", + 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="Topgg Upvote", + description='Please click [here](https://top.gg/bot/889078613817831495/vote) to upvote.\n\nThanks.', + colour=discord.Colour.orange() + ).set_thumbnail( + url="https://cdn.discordapp.com/attachments/908283085999706153/930851470994182144/lionlogo.png" + ) + return await ctx.reply(embed=embed) + + +@module.cmd( + "vote_remainder", + group="Personal Settings", + desc="Turn on/off DM Remainder to Upvote me." +) +async def cmd_remind_vote(ctx): + """ + Usage``: + {prefix}vote_remainder on + {prefix}vote_remainder off + + Use this setting to enable/disable DM remainders from me to upvote on Top.gg. + """ + await UserSettings.settings.vote_remainder.command(ctx, ctx.author.id) diff --git a/bot/modules/topgg/settings.py b/bot/modules/topgg/settings.py new file mode 100644 index 00000000..e60595b3 --- /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 Remainder' + desc = "Turn on/off DM Remainders to Upvote me." + long_desc = "Use this setting to enable/disable DM remainders from me to upvote on Top.gg." + + @property + def success_response(self): + if self.value: + # Check if reminder is already running + create_remainder(self.id) + + return ( + "Remainder, in your DMs, to upvote me are {}.\n\n" + "`Do make sure that I can DM you.`" + ).format(self.formatted) + 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 ( + "Remainder, in your DMs, to upvote me are Off." + ).format(self.formatted) \ 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..f50c6da5 --- /dev/null +++ b/bot/modules/topgg/utils.py @@ -0,0 +1,72 @@ +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 Upvote me again in Top.gg. \nMy Upvote link is {}".format(topgg_upvote_link) + +# 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, + content=remainder_content, + message_link=None, + interval=None, + #remind_at=datetime.datetime.utcnow() + datetime.timedelta(minutes=2) + remind_at=last_vote_time[0] + datetime.timedelta(hours=12.5) if last_vote_time else datetime.datetime.utcnow() + datetime.timedelta(minutes=5) + ) + + # 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="Thankyou.", + description='Thankyou for upvoting.', + colour=discord.Colour.orange() + ).set_image( + url="https://cdn.discordapp.com/attachments/908283085999706153/930559064323268618/unknown.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 From c01de167f32c1a6e948ce119289c73026287d144 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 00:48:01 +0530 Subject: [PATCH 14/20] [DB] Update schema --- data/migration/v8-v9/migration.sql | 2 +- data/schema.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/migration/v8-v9/migration.sql b/data/migration/v8-v9/migration.sql index 63d148c0..88466e4c 100644 --- a/data/migration/v8-v9/migration.sql +++ b/data/migration/v8-v9/migration.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS topgg( userid BIGINT NOT NULL, boostedTimestamp TIMESTAMPTZ NOT NULL ); -CREATE INDEX topgg_member ON topgg (guildid, userid); +CREATE INDEX topgg_member ON topgg (userid); -- }}} DROP FUNCTION close_study_session(_guildid BIGINT, _userid BIGINT); diff --git a/data/schema.sql b/data/schema.sql index c73d8749..5000b59f 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -792,7 +792,7 @@ create TABLE topgg( userid BIGINT NOT NULL, boostedTimestamp TIMESTAMPTZ NOT NULL ); -CREATE INDEX topgg_member ON topgg (guildid, userid); +CREATE INDEX topgg_member ON topgg (userid); -- }}} -- vim: set fdm=marker: From 19816198b0897ed619035aa21cc769e150b28cd1 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 21:12:48 +0530 Subject: [PATCH 15/20] [Reminder] Allow footer and title modification Module will be more more flexible --- bot/modules/reminders/data.py | 2 +- bot/modules/reminders/reminder.py | 5 ++++- data/migration/v8-v9/migration.sql | 4 ++++ data/schema.sql | 4 +++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bot/modules/reminders/data.py b/bot/modules/reminders/data.py index bf8f75fc..ee5b1a41 100644 --- a/bot/modules/reminders/data.py +++ b/bot/modules/reminders/data.py @@ -3,6 +3,6 @@ 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 163c7636..15772b6a 100644 --- a/bot/modules/reminders/reminder.py +++ b/bot/modules/reminders/reminder.py @@ -150,7 +150,7 @@ 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() @@ -167,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/data/migration/v8-v9/migration.sql b/data/migration/v8-v9/migration.sql index 88466e4c..5a46d5e1 100644 --- a/data/migration/v8-v9/migration.sql +++ b/data/migration/v8-v9/migration.sql @@ -1,6 +1,10 @@ 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, diff --git a/data/schema.sql b/data/schema.sql index 5000b59f..4ee4378d 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -167,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); -- }}} From 417e31c7ab9ecb17599e114bd3c8dfe32ec48294 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 21:14:05 +0530 Subject: [PATCH 16/20] Finalize Topgg bot messages --- bot/modules/topgg/commands.py | 7 ++++--- bot/modules/topgg/module.py | 13 +++++++++---- bot/modules/topgg/utils.py | 19 +++++++++++++------ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/bot/modules/topgg/commands.py b/bot/modules/topgg/commands.py index f473fbe0..58e41eed 100644 --- a/bot/modules/topgg/commands.py +++ b/bot/modules/topgg/commands.py @@ -5,6 +5,7 @@ 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", @@ -50,11 +51,11 @@ async def cmd_vote(ctx): target = ctx.author embed=discord.Embed( - title="Topgg Upvote", - description='Please click [here](https://top.gg/bot/889078613817831495/vote) to upvote.\n\nThanks.', + 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/930851470994182144/lionlogo.png" + url="https://cdn.discordapp.com/attachments/908283085999706153/933012309532614666/lion-love.png" ) return await ctx.reply(embed=embed) diff --git a/bot/modules/topgg/module.py b/bot/modules/topgg/module.py index 83a4069d..94cef145 100644 --- a/bot/modules/topgg/module.py +++ b/bot/modules/topgg/module.py @@ -8,6 +8,8 @@ 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() @@ -28,20 +30,23 @@ def reply(util_func, *args, **kwargs): 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=( - f"Upvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + upvote_info_formatted ), inline=False ) elif 'content' in args and args['content']: - args['content'] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + args['content'] += '\n\n' + upvote_info_formatted elif len(args) > 1: - args[1] += "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + args[1] += '\n\n' + upvote_info_formatted else: - args['content'] = "\n\nUpvote me to get 🌟**+25% Economy Boost**🌟 - Use `!vote`" + args['content'] = '\n\n' + upvote_info_formatted args = tuple(args) diff --git a/bot/modules/topgg/utils.py b/bot/modules/topgg/utils.py index f50c6da5..d4691730 100644 --- a/bot/modules/topgg/utils.py +++ b/bot/modules/topgg/utils.py @@ -12,7 +12,10 @@ from . import data as db from data.conditions import GEQ topgg_upvote_link = 'https://top.gg/bot/889078613817831495/vote' -remainder_content = "You can now Upvote me again in Top.gg. \nMy Upvote link is {}".format(topgg_upvote_link) +remainder_content = "You can now Upvote me again in Top.gg. \nMy Upvote link is [here]({})".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 @@ -37,11 +40,14 @@ def create_remainder(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, - #remind_at=datetime.datetime.utcnow() + datetime.timedelta(minutes=2) - remind_at=last_vote_time[0] + datetime.timedelta(hours=12.5) if last_vote_time else datetime.datetime.utcnow() + datetime.timedelta(minutes=5) + title="Your boost is now available! {}".format(lion_yayemote), + footer="to stop reminders, use `{}vote_reminder off` command", + # 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 @@ -59,11 +65,12 @@ async def send_user_dm(userid): if user: try: embed=discord.Embed( - title="Thankyou.", - description='Thankyou for upvoting.', + 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/930559064323268618/unknown.png" + url="https://cdn.discordapp.com/attachments/908283085999706153/932737228440993822/lion-yay.png" ) await user.send(embed=embed) From b4e84169005330404f6a31f7212abef218ca0053 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 21:43:50 +0530 Subject: [PATCH 17/20] Final *finalize* UI Texts and some typos --- bot/modules/topgg/commands.py | 14 +++++++------- bot/modules/topgg/settings.py | 16 ++++++++-------- bot/modules/topgg/utils.py | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bot/modules/topgg/commands.py b/bot/modules/topgg/commands.py index 58e41eed..9bb63cd8 100644 --- a/bot/modules/topgg/commands.py +++ b/bot/modules/topgg/commands.py @@ -36,7 +36,7 @@ async def cmd_forcevote(ctx): @module.cmd( "vote", - desc="Get Top.gg bot's link for Economy boost.", + desc="Get top.gg boost for 25% more LCs.", group="Economy", aliases=('topgg', 'topggvote', 'upvote') ) @@ -61,16 +61,16 @@ async def cmd_vote(ctx): @module.cmd( - "vote_remainder", + "vote_reminder", group="Personal Settings", - desc="Turn on/off DM Remainder to Upvote me." + desc="Turn on/off boost reminders." ) async def cmd_remind_vote(ctx): """ - Usage``: - {prefix}vote_remainder on - {prefix}vote_remainder off + Usage: + `{prefix}vote_reminder on` + `{prefix}vote_reminder off` - Use this setting to enable/disable DM remainders from me to upvote on Top.gg. + Enable or disable DM boost reminders. """ await UserSettings.settings.vote_remainder.command(ctx, ctx.author.id) diff --git a/bot/modules/topgg/settings.py b/bot/modules/topgg/settings.py index e60595b3..2686bc02 100644 --- a/bot/modules/topgg/settings.py +++ b/bot/modules/topgg/settings.py @@ -13,9 +13,9 @@ class topgg_vote_remainder(Boolean, UserSetting): _default = True - display_name = 'Upvote Remainder' - desc = "Turn on/off DM Remainders to Upvote me." - long_desc = "Use this setting to enable/disable DM remainders from me to upvote on Top.gg." + 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): @@ -24,9 +24,9 @@ class topgg_vote_remainder(Boolean, UserSetting): create_remainder(self.id) return ( - "Remainder, in your DMs, to upvote me are {}.\n\n" - "`Do make sure that I can DM you.`" - ).format(self.formatted) + " 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( @@ -41,5 +41,5 @@ class topgg_vote_remainder(Boolean, UserSetting): Reminder.delete(r['reminderid']) return ( - "Remainder, in your DMs, to upvote me are Off." - ).format(self.formatted) \ No newline at end of file + " 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 index d4691730..0469c839 100644 --- a/bot/modules/topgg/utils.py +++ b/bot/modules/topgg/utils.py @@ -12,7 +12,7 @@ from . import data as db from data.conditions import GEQ topgg_upvote_link = 'https://top.gg/bot/889078613817831495/vote' -remainder_content = "You can now Upvote me again in Top.gg. \nMy Upvote link is [here]({})".format(topgg_upvote_link) +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>' @@ -45,9 +45,9 @@ def create_remainder(userid): message_link=None, interval=None, title="Your boost is now available! {}".format(lion_yayemote), - footer="to stop reminders, use `{}vote_reminder off` command", - # 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) + footer="Use `{}vote_reminder off` to stop receiving reminders.", + 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 From 2c9e62002a927c0fac098a1fee0fa7972f960ccd Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 21:50:22 +0530 Subject: [PATCH 18/20] [Fix] Add prefix in reminders --- bot/modules/topgg/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/modules/topgg/utils.py b/bot/modules/topgg/utils.py index 0469c839..233c61b1 100644 --- a/bot/modules/topgg/utils.py +++ b/bot/modules/topgg/utils.py @@ -45,7 +45,7 @@ def create_remainder(userid): message_link=None, interval=None, title="Your boost is now available! {}".format(lion_yayemote), - footer="Use `{}vote_reminder off` to stop receiving reminders.", + 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) ) From 5b4d54a3b92372f582bba01d035b527ea237af37 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 22:41:40 +0530 Subject: [PATCH 19/20] [Core] Expose addCoins to modules --- bot/core/lion.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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) From 038aabddfa2eaf0e5af99d5f8456e2b80f673a35 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 18 Jan 2022 22:42:28 +0530 Subject: [PATCH 20/20] [Topgg][Admin][Economy] Implement addCoins cb --- bot/modules/economy/send_cmd.py | 2 +- bot/modules/guild_admin/economy/set_coins.py | 4 ++-- .../guild_admin/reaction_roles/tracker.py | 3 ++- bot/modules/topgg/module.py | 17 ++++++++++++++++- 4 files changed, 21 insertions(+), 5 deletions(-) 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/topgg/module.py b/bot/modules/topgg/module.py index 94cef145..517058f9 100644 --- a/bot/modules/topgg/module.py +++ b/bot/modules/topgg/module.py @@ -1,7 +1,9 @@ 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 @@ -14,12 +16,14 @@ upvote_info = "You have a boost available {}, to support our project and earn ** 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" ) @@ -51,3 +55,14 @@ def reply(util_func, *args, **kwargs): 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