Merge branch 'staging' of cgithub:StudyLions/StudyLion into staging
This commit is contained in:
48
bot/LionContext.py
Normal file
48
bot/LionContext.py
Normal file
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ meta = RowTable(
|
|||||||
|
|
||||||
user_config = RowTable(
|
user_config = RowTable(
|
||||||
'user_config',
|
'user_config',
|
||||||
('userid', 'timezone'),
|
('userid', 'timezone', 'remaind_upvote'),
|
||||||
'userid',
|
'userid',
|
||||||
cache=TTLCache(5000, ttl=60*5)
|
cache=TTLCache(5000, ttl=60*5)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from meta import client
|
|||||||
from data import tables as tb
|
from data import tables as tb
|
||||||
from settings import UserSettings, GuildSettings
|
from settings import UserSettings, GuildSettings
|
||||||
|
|
||||||
|
# Give modules the ability to intercept addCoin() calls
|
||||||
|
_lion_add_coins_callbacks: list = []
|
||||||
|
|
||||||
class Lion:
|
class Lion:
|
||||||
"""
|
"""
|
||||||
@@ -214,10 +216,13 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
for cb in _lion_add_coins_callbacks:
|
||||||
|
[self, amount, flush, ignorebonus] = cb(self, amount, flush, ignorebonus)
|
||||||
|
|
||||||
self._pending_coins += amount
|
self._pending_coins += amount
|
||||||
self._pending[self.key] = self
|
self._pending[self.key] = self
|
||||||
if flush:
|
if flush:
|
||||||
@@ -251,3 +256,12 @@ class Lion:
|
|||||||
for lion in lions:
|
for lion in lions:
|
||||||
lion._pending_coins -= int(lion._pending_coins)
|
lion._pending_coins -= int(lion._pending_coins)
|
||||||
cls._pending.pop(lion.key, None)
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 *
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,12 +150,14 @@ 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()
|
||||||
)
|
)
|
||||||
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:
|
if self.data.interval:
|
||||||
embed.add_field(
|
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
|
# 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)
|
||||||
|
|||||||
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
|
||||||
76
bot/modules/topgg/commands.py
Normal file
76
bot/modules/topgg/commands.py
Normal file
@@ -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)
|
||||||
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'
|
||||||
|
)
|
||||||
|
|
||||||
68
bot/modules/topgg/module.py
Normal file
68
bot/modules/topgg/module.py
Normal file
@@ -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]
|
||||||
45
bot/modules/topgg/settings.py
Normal file
45
bot/modules/topgg/settings.py
Normal file
@@ -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."
|
||||||
|
)
|
||||||
79
bot/modules/topgg/utils.py
Normal file
79
bot/modules/topgg/utils.py
Normal file
@@ -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
|
||||||
36
bot/modules/topgg/webhook.py
Normal file
36
bot/modules/topgg/webhook.py
Normal file
@@ -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"))
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from cmdClient import Context
|
from LionContext import LionContext
|
||||||
from cmdClient.lib import UserCancelled, ResponseTimedOut
|
from cmdClient.lib import UserCancelled, ResponseTimedOut
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from cmdClient import lib
|
||||||
from .lib import paginate_list
|
from .lib import paginate_list
|
||||||
|
|
||||||
# TODO: Interactive locks
|
# TODO: Interactive locks
|
||||||
@@ -19,7 +21,7 @@ async def discord_shield(coro):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout=300):
|
async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout=300):
|
||||||
"""
|
"""
|
||||||
Add a cancellation reaction to the given message.
|
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
|
return task
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=None):
|
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,
|
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
|
return message
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def selector(ctx, header, select_from, timeout=120, max_len=20):
|
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.
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def pager(ctx, pages, locked=True, start_at=0, add_cancel=False, **kwargs):
|
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,
|
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
|
pass
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def input(ctx, msg="", timeout=120):
|
async def input(ctx, msg="", timeout=120):
|
||||||
"""
|
"""
|
||||||
Listen for a response in the current channel, from ctx.author.
|
Listen for a response in the current channel, from ctx.author.
|
||||||
@@ -413,7 +415,7 @@ async def input(ctx, msg="", timeout=120):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@Context.util
|
@LionContext.util
|
||||||
async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False):
|
async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False):
|
||||||
"""
|
"""
|
||||||
Ask ctx.author a yes/no question.
|
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"]:
|
if result in ["n", "no"]:
|
||||||
return 0
|
return 0
|
||||||
return 1
|
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
|
||||||
@@ -14,3 +14,7 @@ data_appid = LionBot
|
|||||||
shard_count = 1
|
shard_count = 1
|
||||||
|
|
||||||
lion_sync_period = 60
|
lion_sync_period = 60
|
||||||
|
|
||||||
|
topgg_password =
|
||||||
|
topgg_route =
|
||||||
|
topgg_port =
|
||||||
76
data/migration/v8-v9/migration.sql
Normal file
76
data/migration/v8-v9/migration.sql
Normal file
@@ -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');
|
||||||
@@ -41,7 +41,8 @@ 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,
|
||||||
|
remaind_upvote BOOLEAN DEFAULT TRUE
|
||||||
);
|
);
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
@@ -166,7 +167,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 DEFAULT NULL,
|
||||||
|
footer TEXT DEFAULT NULL
|
||||||
);
|
);
|
||||||
CREATE INDEX reminder_users ON reminders (userid);
|
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,
|
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 +542,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 +788,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_member ON topgg (userid);
|
||||||
|
-- }}}
|
||||||
|
|
||||||
-- vim: set fdm=marker:
|
-- vim: set fdm=marker:
|
||||||
|
|||||||
Reference in New Issue
Block a user