From a8ba54c711864a5f8bb390968a08482196298e5d Mon Sep 17 00:00:00 2001 From: osmarks Date: Sun, 1 Nov 2020 17:07:36 +0000 Subject: [PATCH] basic achievement system --- assets/achievements/spam.png | Bin 0 -> 943 bytes assets/achievements/spectre_of_communism.png | Bin 0 -> 901 bytes assets/achievements/test.png | Bin 0 -> 921 bytes assets/achievements/unicode_abuse.png | Bin 0 -> 589 bytes src/achievement.py | 68 +++++++++++++++++++ src/db.py | 31 +++++++++ src/main.py | 7 +- src/util.py | 18 ++++- 8 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 assets/achievements/spam.png create mode 100644 assets/achievements/spectre_of_communism.png create mode 100644 assets/achievements/test.png create mode 100644 assets/achievements/unicode_abuse.png create mode 100644 src/achievement.py diff --git a/assets/achievements/spam.png b/assets/achievements/spam.png new file mode 100644 index 0000000000000000000000000000000000000000..d2afaf4e56f0e977e5260676e252ef559248192f GIT binary patch literal 943 zcmV;g15o^lP)8)Q9Vx^0c&?L~-%Zr(iw|R=4{?NMpnGt~;axu1Joz3dl^;UCXz4nwi@? zs!9*ZT&=!vox`9-t!^abPLZVYWugpX-|l`NC`LiWA5r-1()MRtX4AE2E1Cx(BiR1t zeY;d1WhAfNxJY}OLO}w}(rSPKK)a4zKtZM;|15wZs)YOoHBctIiAAYO09uM1-DNam zmlE{=e-t>=1wxO9tNB$3K8v-G~$AwPMtj zU=oF73;L7Y3KX1fU>!&LQ9df+Pd7r>^J{$+mH(qpwY=l;^>6{h<>uyrj{q)b zipST3yw!OvjancJkVYVsjXvcny%Z5fRZf%(36F9 zCCIN4?O1H=nyx$4WMEVO%ZpieYhM`ZdAnU*20pr8sk|($lnZOEIj>p-?dIL8ZUd6k zczr68_xnh@-F^r#TyEO2-i=4v| z1NZVT%0hp#yOsvN4O}UiI%e-;AO#pQ2{OhgD#Tob_p&(lCJ3l zZ|!z7lU-L-$jKI|*O5f5FL}Ck=CR1)llc^%YOYs(Lt`|~bR%-|tRo7_(CdqQmx!BD zC~P<3%a%5sfMEy$6?TV*t|H~xZiGNR35qac6}gy&`|L!jM+wV4W)Sh2ndHG`Q?suD+ ztRmtnEQ9N|;hyEITxtPa-XKQ_#zb)L+1>9pbN8LqbQ$MuH11LoNRvw3NVQSuWew?@?(CFnKhAXjx z0A^!`1k7C=08+omQrzY$ZqC|dz%Z=H9xj)++Uhv2-GgD{HnDO(1vd;ULadU@p>cL| z*0La<8HQC8YpNwB?q$^ODppcYy5?KjZnqeoPA3r8JmyEja2M+HSs|5z96h*OrND4i zDu#WX;>5(JDsHVN&bs~hi3rX>Nel~u!6#gGa7&3M*1TL1=SxlTCtOsT)^@W(HV-w093(O1R`>|yu~*UspWBV>1)J>yR{49w$GnaNH!sG+J#Lz9?TYriH)fr0>Q{I1GN$~}vT>SGTS{$^e7U?G zk57lgW62tsX~4~UpnchcEa6dTo?6#heHideb}P}i#I4PAl$25p?xHw(gZAX}3OT5q z8B}qn>8!pa%`4dVVO2uA*E5x{*F;dF?0vl%hI_rPN;&R}gBur81o0thfmD(_x> zO+3H6lK(OTv3ix%K#^Zdz^<_!-Or#fcH#JTw zuFc(){IDunA2)}N{Dp*SK!y=JRe2N_WvWM?>q-RTZqSUY6uTQMo#4=Z?goo*pZ0KH bfA;ndhL%elXK)eL00000NkvXXu0mjf@1m@k literal 0 HcmV?d00001 diff --git a/assets/achievements/test.png b/assets/achievements/test.png new file mode 100644 index 0000000000000000000000000000000000000000..4a7e83e9b59f7d889c43206b1f7468641350081d GIT binary patch literal 921 zcmV;K17`e*P) zOK#gh42GGaC#W|G;DrEfoHN)b$TiX{bZvO;EA$wJPhg)R*$B|WK)P|S&_bb2aYznl zWI2LV#0czpkUziSa0Zr+w8TX?KP8+p8RMaN z%vpI(YWvnM0OW+B{mIFKzb(<5fl?Cl7n_*8=TR^wn{-DESqwNzB|)r|AsV67QY16e zoKK5X#}k#l>CVF?ib1OjXYBf6k|Mslh!`yfVKD+(>`&y4|qceO8{rGs(*EFW5z>y`Eup<7%r;dFU;#|)>?m?)dUAJQq9m6Ji= z#??UPx<#18R=!i-|B*@k{jK>aBjI~tHgG#ZP#EEL(GNZ~YcL%am3ccExI`ed%x(_l z*>MZyy7}Rw&qf)zF+~$Mxi}9Y=5uIL2crk~E&=JI-c-P8(=yS z$k>hTxo}B=8d|Ewr9DDLd`6&m?TP0DbB1>rJyvMr^Gej6VB8zpnT-U+P#sTZ4#ZCJ zCl=ae|8(Q=XK~vmDu~NFR*(TM8kO^z;A^!nEkS%4c0aexY)n}cNz6>*#+Ic)sCaNH zr`$CJ9Yn2fju^P6U2;&+3;X8AeTGVmzIS~`;n5y7F)_~W#Bgs`4E}CB)vv-}S1iOx zPB*~Sq9no9GtJ1?u}7Pv0Y(2T2Fj|{pgdWV>9L>~NQ-udMocAtR=dRP`M4iCz|{e6 zJL03{#Oj4h;V}|Vk{|o2e$6-LQbQcI>&Q4IwTHk3fL*Z=&|+r@$Zr_M&`C%@_`!C? zqLa8*GjO$$Gor&g3(&@nvN_ck*A(T0{SmpEf$OA)d-iN!7sbF$a$FaQ%Q_0ql<|Wp v7rjZ_58pM!Ko?n8teE}8aPOw|Z*Ttqa_DmY+=0}y00000NkvXXu0mjfiQlr+ literal 0 HcmV?d00001 diff --git a/assets/achievements/unicode_abuse.png b/assets/achievements/unicode_abuse.png new file mode 100644 index 0000000000000000000000000000000000000000..4c6a5fd9904b3afe9bbf73c38044a4db928c4060 GIT binary patch literal 589 zcmV-T0 zy=}xW425|?mT>3xX2?1jD`RAZ%;46YtIUuf5I_*3C_hqE00X{?mgvWi@8};K?(zBa zP1pIn64Cqmb_w8*{p;ltpbUBAvK148+uaGKvgGY9TQMQ{yYJ5U9Q#&em-U&bKYbXO zI|X_QZiQGKj&SGWCc^2)@^N(yBFgF^6ZhD+4pwKY*98HL2ed^3UmbDunhKgWWpG{V) zaCN$sR;>qdt70xtCa6Ssy%R)mPs^0&g-o;hkvSbo-g4uf7C24d+9h!1`z|$0y0dQi zDxvWkg~MHoNfr*r{r3%)MR8-Sh_#G@ytqNQ|HCS|tr)_!PVTIGeeFoettq&g75skm ziISCwAly@xN>m<#*ANjQRUlkYOH!5?G!@rJadAZnjTq)qcHp{nC3HUqTPh+x+)2d! ze&nw2AzaQpeSb|KStfS7%fu`Z4qRhUsswSzry{^gM3G@Y>|~neVDK&?C3E8L7K6iu zB?KN1;g&L9VsE}|CTAG*y`nh-;}f6YZp>rp86KzMBBMMq2BoD;T->B^W8?`6u#-00000NkvXXu0mjf9F-ER literal 0 HcmV?d00001 diff --git a/src/achievement.py b/src/achievement.py new file mode 100644 index 0000000..5691120 --- /dev/null +++ b/src/achievement.py @@ -0,0 +1,68 @@ +from discord.ext import commands +import discord +import logging +import asyncio +import discord +from datetime import datetime +import re +import collections + +import util + +Achievement = collections.namedtuple("Achievement", ["name", "condition", "description"]) + +achievements = { + "spectre_of_communism": Achievement("Containment Efforts Ongoing", "Refer to the 'spectre of communism' in a message.", "A spectre is haunting Europe. The spectre of communism. Containment efforts are ongoing and full containment is projected by 2036."), + "test": Achievement("Test", "Test achievement. Obtained for testing.", "Congratulations, you ran the test command!"), + "spam": Achievement("beesbeesbeesbeesbeesbeesbeesbeesbees", "Send a long message containing the same thing repeatedly.", "You should probably not do this, nobody* likes spam!"), + "unicode_abuse": Achievement("Anomalous Unicode", "Send a high proportion of weird Unicode characters in a message.", "h̵͖̻̮̗̹̆͛͆̎ͮͤͫ͛ͦ̓̅ͤ́͢é͒ͧ̌̀ͪ̈͂̈́̉ͣ̅̿̄̌̋̿̽̚͏̛͏͚̯͉̟͇̼̹͎ͅa̠̹̘͎̫̜̞̩͖̟̟͍͇͈͍̝͕͛ͥ͊̾̈́ͩͯͩͭ̆̋͐͗̉͋̓̀͝v͎͖̜͎͔̞͚͉̺̞̘̥͖̝͚̺̍ͤ̌͂ͨ̃̅ͫ̿͛ͯ̓̉̆̎͊̀̚̕͟s̪̠̟̣̝̹̭̻̈́ͤ͗̏ͮ̂ͯ̈́̊ͩ̓̆̌̆͌̽̓̈́̚͢͞e̛̞̙̜̗̰͕͕͎̺͍̭̲̟̭̲̫̬͓ͯ̅̓̆̂̔̃͟r̷̛̮̮͇̳̳̾ͯͮͩ̏͂ͤ̿̽ͧ͒͋́̕ͅͅv̴̠͉̼̮̭̘ͪͯͦ͌́ͯ̒̃̀́̃͜͝ͅe̵̷̢͕̣̻̥̲͓̼͍̱͕̮̯̱̤̹̱̝̎̓̈́̿ͤ̔̍ͭͭ͐ͅŗ̔ͮͯ͂́͏̻͈̱ͅ ̣͇̼͊̄ͫ̆̍̄̀̀̓͊͐͋̌͘͠į̱͔̰̭̫̱̫̊ͪ̅ͥ̈́ͥ̐͌̅ͪ̅ͨ̎̀͘͝s̍͑̌̋̅͌͂ͨͬͯ̇͊҉̛̱̺͕̰͓̗̖̬͡͡ ̥̤̺̖̪̪́ͯͣ̏̅̈ͣ̿̀͠͠͞i̢̛̭̰̻͈̦̣̮̞̤̩̊̌̾͛ͭͦ̆ͮ̃̎ͪ̔ͬ͊̆͂ͫͅn̸̖͚̣̪̩̏ͥ̈́̅ͯ̔͆́ͦ͗͛͒̃̃ͫ͟͜͝͠ȩ̸͎̟̣̞͉̫̗̙̻̯͍̰̣̌ͪͨ͛̆̕͡v̙͙̲͕͔̦̣̺͔̖͉̜̲̩̈̿ͥ̎͊̈́̊ͯͯ͒ͭ̊̀͢i̪͈̣̱̞̥̰̟̣̩̼̻̪̳̤͇̻̹͉͗ͭ͆̆̎̀͑͑̆͋̏̏͊ͣͦ͆ͣ̈́̓͟͢ţ̵̘̫̯͓̻̗͕̘͙̯̞̪̪̲̤̬̜͕ͫ̄̌̓̎͌ͧ̔͟͢ͅa̸̧̭̲̯̳̔́͋̐͂̇ͪ̔̐́̚͢b͐̅̔ͭ͗̊̂̾̀̓ͭͭ͑ͤ̏̐̃ͩͬ҉̞̼̮̤̝̲̳͓̗̤̫̭̝̹̙͘͟͝ļ̷͈̭̖͓̜̬͔̻͔̀̎ͯ͗̐̽̏ͦ̊͗ͧ́͘ͅe̢͍̦̗̬̝̠͔̳̣̯̮̣̹͍͙̞̜ͣ̉͆̊̀̎ͦ͌̂̋̊ͨ͛́") +} + +async def achieve(bot: commands.Bot, message: discord.Message, achievement): + guild_conf = await bot.database.execute_fetchone("SELECT achievement_messages FROM guild_config WHERE id = ?", (message.guild.id,)) + if guild_conf and guild_conf["achievement_messages"] == 0: return + + uid = message.author.id + # ensure the user doesn't have achievements off + conf = await bot.database.execute_fetchone("SELECT * FROM user_config") + if conf and conf["achievement_tracking_enabled"] == 0: return + if not conf: + await bot.database.execute("INSERT INTO user_config VALUES (?, NULL)", (uid,)) + await bot.database.commit() + # detect if achievement already earned + if await bot.database.execute_fetchone("SELECT 1 FROM achievements WHERE user_id = ? AND achievement = ?", (uid, achievement)): + return + achievement_info = achievements[achievement] + description = f"Congratulations! You achieved the achievement __{achievement_info.name}__.\n\n{achievement_info.description}\n*{achievement_info.condition}*" + e = util.make_embed(description=description, title="Achievement achieved!", color=util.hashbow(achievement)) + e.set_thumbnail(url=await util.get_asset(bot, f"achievements/{achievement}.png")) + await message.channel.send(embed=e) + await bot.database.execute("INSERT INTO achievements VALUES (?, ?, ?)", (uid, achievement, util.timestamp())) + await bot.database.commit() + logging.info("awarded achievement %s to %s", message.author.name, achievement) + +def setup(bot): + @bot.group(name="achievements", aliases=["ach", "achieve", "achievement"], brief="Achieve a wide variety of fun achievements!", help=f""" + Do things and get arbitrary achievements for them! + Note that due to reasons messages for achievements will not be shown except in opted-in servers, although achievements will be gained regardless. + """) + async def achievements(ctx): pass + + @achievements.command(help="Enable/disable achievement messages on this guild.") + @commands.check(util.server_mod_check(bot)) + async def set_enabled(ctx, on: bool): + await bot.database.execute("INSERT OR REPLACE INTO guild_config VALUES (?, ?)", (ctx.guild.id, int(on))) + await bot.database.commit() + await ctx.send(f"Achievement messages set to: {on}") + + @achievements.command(help="Obtain a test achievement") + async def test(ctx): + await achieve(ctx.bot, ctx.message, "test") + + @bot.listen("on_message") + async def message_listener(msg: discord.Message): + content = msg.content + content_len = len(msg.content) + if re.match("spect(re|er).{,20}(communism|☭)", content): await achieve(bot, msg, "spectre_of_communism") + if re.match(r"^(.+)\1+$", content) and len(content) >= 1950: await achieve(bot, msg, "spam") + if content_len > 30 and (len(re.findall("[\u0300-\u036f\U00040000-\U0010FFFF]", content)) / content_len) > 0.35: await achieve(bot, msg, "unicode_abuse") \ No newline at end of file diff --git a/src/db.py b/src/db.py index 2950a47..7719bfd 100644 --- a/src/db.py +++ b/src/db.py @@ -36,6 +36,37 @@ CREATE TABLE calls ( to_id TEXT NOT NULL REFERENCES telephone_config(id), start_time INTEGER NOT NULL ); +""", +""" +CREATE TABLE guild_config ( + id INTEGER PRIMARY KEY, + achievement_messages INTEGER +); + +CREATE TABLE user_config ( + id INTEGER PRIMARY KEY, + achievement_tracking_enabled INTEGER +); + +CREATE TABLE stats ( + user_id INTEGER NOT NULL REFERENCES user_config (id), + stat TEXT NOT NULL COLLATE NOCASE, + value BLOB NOT NULL, + UNIQUE (user_id, stat) +); + +CREATE TABLE achievements ( + user_id INTEGER NOT NULL REFERENCES user_config (id), + achievement TEXT NOT NULL, + achieved_time INTEGER NOT NULL, + UNIQUE (user_id, achievement) +); +""", +""" +CREATE TABLE assets ( + identifier TEXT PRIMARY KEY, + url TEXT NOT NULL +); """ ] diff --git a/src/main.py b/src/main.py index e42fc74..53e846b 100644 --- a/src/main.py +++ b/src/main.py @@ -11,6 +11,7 @@ import argparse import traceback import random import rolldice +#import aiopubsub import tio import db @@ -204,14 +205,16 @@ async def andrew_bad(ctx): @bot.event async def on_ready(): logging.info("Connected as " + bot.user.name) - await bot.change_presence(status=discord.Status.online, activity=discord.Activity(type=discord.ActivityType.listening, name=f"commands beginning with {bot.command_prefix}")) + await bot.change_presence(status=discord.Status.online, + activity=discord.CustomActivity(name=f"{bot.command_prefix}help")) async def run_bot(): bot.database = await db.init(config["database"]) for ext in ( "reminders", "debug", - "telephone" + "telephone", + "achievement" ): bot.load_extension(ext) await bot.start(config["token"]) diff --git a/src/util.py b/src/util.py index d904725..d9532a3 100644 --- a/src/util.py +++ b/src/util.py @@ -8,6 +8,8 @@ from dateutil.relativedelta import relativedelta import json import discord import toml +import os.path +from discord.ext import commands config = toml.load(open("config.toml", "r")) @@ -229,4 +231,18 @@ def json_encode(x): return json.dumps(x, separators=(',', ':')) def server_mod_check(bot): async def check(ctx): return ctx.author.permissions_in(ctx.channel).manage_channels or (await bot.is_owner(ctx.author)) - return check \ No newline at end of file + return check + +async def get_asset(bot: commands.Bot, identifier): + safe_ident = re.sub("[^A-Za-z0-9_.-]", "_", identifier) + x = await bot.database.execute_fetchone("SELECT * FROM assets WHERE identifier = ?", (safe_ident,)) + if x: + return x["url"] + file = discord.File(os.path.join("./assets", identifier), filename=safe_ident) + message = await (bot.get_channel(config["image_upload_channel"])).send(identifier, file=file) + url = message.attachments[0].proxy_url + await bot.database.execute("INSERT INTO assets VALUES (?, ?)", (safe_ident, url)) + return url + +def hashbow(thing): + return hash(thing) % 0x1000000 \ No newline at end of file