From ab7cccdf6041c98d98e48fde62128f2def7b8358 Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:50:20 +0800 Subject: [PATCH 1/7] fix: solve the problem of database locking when multiple tasks update at the same time --- bot.py | 2 +- src/db_function/db_executor.py | 18 ++++++++++++++++++ src/{ => db_function}/init_db.py | 0 src/notification/account_tracker.py | 4 ++-- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 src/db_function/db_executor.py rename src/{ => db_function}/init_db.py (100%) diff --git a/bot.py b/bot.py index ed215dd..ae1a350 100644 --- a/bot.py +++ b/bot.py @@ -5,7 +5,7 @@ import os from src.log import setup_logger -from src.init_db import init_db +from src.db_function.init_db import init_db from configs.load_configs import configs log = setup_logger(__name__) diff --git a/src/db_function/db_executor.py b/src/db_function/db_executor.py new file mode 100644 index 0000000..acfda6f --- /dev/null +++ b/src/db_function/db_executor.py @@ -0,0 +1,18 @@ +import sqlite3 +from time import sleep +from random import randint + +from src.log import setup_logger + +log = setup_logger(__name__) + +def execute(conn: sqlite3.Connection, sql_statement: str, param: tuple, task_name: str = 'unknown'): + cursor = conn.cursor() + for retry in range(5): + try: + cursor.execute(sql_statement, param) + conn.commit() + except: + waiting = randint(1, 5) + log.info(f'in task {task_name}: the database is locked, try again in {waiting} seconds, number of retries: {retry}') + sleep(waiting) \ No newline at end of file diff --git a/src/init_db.py b/src/db_function/init_db.py similarity index 100% rename from src/init_db.py rename to src/db_function/init_db.py diff --git a/src/notification/account_tracker.py b/src/notification/account_tracker.py index 28bc6ec..cc31e84 100644 --- a/src/notification/account_tracker.py +++ b/src/notification/account_tracker.py @@ -10,6 +10,7 @@ from src.notification.display_tools import gen_embed, get_action from src.notification.get_tweets import get_tweets from src.notification.date_comparator import date_comparator +from src.db_function.db_executor import execute from configs.load_configs import configs log = setup_logger(__name__) @@ -56,14 +57,13 @@ async def notification(self, username): user = cursor.execute('SELECT * FROM user WHERE username = ?', (username,)).fetchone() if date_comparator(lastest_tweet.created_on, user['lastest_tweet']) == 1: - cursor.execute('UPDATE user SET lastest_tweet = ? WHERE username = ?', (str(lastest_tweet.created_on), username)) + execute(conn, 'UPDATE user SET lastest_tweet = ? WHERE username = ?', (str(lastest_tweet.created_on), username), username) log.info(f'find a new tweet from {username}') for data in cursor.execute('SELECT * FROM notification WHERE user_id = ?', (user['id'],)): channel = self.bot.get_channel(int(data['channel_id'])) mention = f"{channel.guild.get_role(int(data['role_id'])).mention} " if data['role_id'] != '' else '' await channel.send(f"{mention}**{lastest_tweet.author.name}** just {get_action(lastest_tweet)} here: \n{lastest_tweet.url}", file = discord.File('images/twitter.png', filename='twitter.png'), embeds = gen_embed(lastest_tweet)) - conn.commit() conn.close() From 03652326f5e9ccdbe44c4e0159984b12e9914fab Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 14:14:19 +0800 Subject: [PATCH 2/7] feat: download log file command for owner --- bot.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bot.py b/bot.py index ae1a350..8a32269 100644 --- a/bot.py +++ b/bot.py @@ -48,6 +48,11 @@ async def reload(ctx, extension): await bot.reload_extension(f'cogs.{extension}') await ctx.send(f'Re - Loaded {extension} done.') +@bot.command() +@commands.is_owner() +async def download_log(ctx : commands.context.Context): + message = await ctx.send(file=discord.File('console.log')) + await message.delete(delay=15) @bot.command() @commands.is_owner() From 528e0e22c132e1d6f4835f2f0ce0674e68a9caea Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 14:25:20 +0800 Subject: [PATCH 3/7] fix: if the channel cannot be found, pass it --- src/notification/account_tracker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/notification/account_tracker.py b/src/notification/account_tracker.py index cc31e84..f60f6a4 100644 --- a/src/notification/account_tracker.py +++ b/src/notification/account_tracker.py @@ -61,8 +61,9 @@ async def notification(self, username): log.info(f'find a new tweet from {username}') for data in cursor.execute('SELECT * FROM notification WHERE user_id = ?', (user['id'],)): channel = self.bot.get_channel(int(data['channel_id'])) - mention = f"{channel.guild.get_role(int(data['role_id'])).mention} " if data['role_id'] != '' else '' - await channel.send(f"{mention}**{lastest_tweet.author.name}** just {get_action(lastest_tweet)} here: \n{lastest_tweet.url}", file = discord.File('images/twitter.png', filename='twitter.png'), embeds = gen_embed(lastest_tweet)) + if channel != None: + mention = f"{channel.guild.get_role(int(data['role_id'])).mention} " if data['role_id'] != '' else '' + await channel.send(f"{mention}**{lastest_tweet.author.name}** just {get_action(lastest_tweet)} here: \n{lastest_tweet.url}", file = discord.File('images/twitter.png', filename='twitter.png'), embeds = gen_embed(lastest_tweet)) conn.close() From 7a84810b3542a7d24234e789a4087943dd2bc181 Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:33:48 +0800 Subject: [PATCH 4/7] feat: add remove notifier slash command --- cogs/notification.py | 36 +++++++++++++++++++++++++++-- src/notification/account_tracker.py | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/cogs/notification.py b/cogs/notification.py index 40eda17..fbe71be 100644 --- a/cogs/notification.py +++ b/cogs/notification.py @@ -21,6 +21,7 @@ def __init__(self, bot): self.account_tracker = AccountTracker(bot) add_group = app_commands.Group(name='add', description="Add something") + remove_group = app_commands.Group(name='remove', description='Remove something') @is_administrator() @@ -58,7 +59,7 @@ async def notifier(self, itn : discord.Interaction, username: str, channel: disc cursor.execute('INSERT INTO user VALUES (?, ?, ?)', (str(new_user.id), username, datetime.utcnow().replace(tzinfo=timezone.utc).strftime('%Y-%m-%d %H:%M:%S%z'))) cursor.execute('INSERT OR IGNORE INTO channel VALUES (?)', (str(channel.id),)) - cursor.execute('INSERT INTO notification VALUES (?, ?, ?)', (str(new_user.id), str(channel.id), roleID)) + cursor.execute('INSERT INTO notification (user_id, channel_id, role_id) VALUES (?, ?, ?)', (str(new_user.id), str(channel.id), roleID)) app.follow_user(new_user) @@ -66,7 +67,7 @@ async def notifier(self, itn : discord.Interaction, username: str, channel: disc else: log.warning(f'unable to turn on notifications for {username}') else: cursor.execute('INSERT OR IGNORE INTO channel VALUES (?)', (str(channel.id),)) - cursor.execute('REPLACE INTO notification VALUES (?, ?, ?)', (match_user[0], str(channel.id), roleID)) + cursor.execute('REPLACE INTO notification (user_id, channel_id, role_id) VALUES (?, ?, ?)', (match_user[0], str(channel.id), roleID)) conn.commit() conn.close() @@ -76,5 +77,36 @@ async def notifier(self, itn : discord.Interaction, username: str, channel: disc await itn.followup.send(f'successfully add notifier of {username}!', ephemeral=True) + @is_administrator() + @remove_group.command(name='notifier') + async def notifier(self, itn : discord.Interaction, username: str, channel: discord.TextChannel): + """Remove a notifier on your server. + + Parameters + ----------- + username: str + The username of the twitter user you want to turn off notifications for. + channel: discord.TextChannel + The channel which set to delivers notifications. + """ + + await itn.response.defer(ephemeral=True) + + conn = sqlite3.connect(f"{os.getenv('DATA_PATH')}tracked_accounts.db") + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + cursor.execute('SELECT user_id FROM notification, user WHERE username = ? AND channel_id = ? AND user_id = id', (username, str(channel.id))) + match_notifier = cursor.fetchone() + if match_notifier != None: + cursor.execute('UPDATE notification SET enabled = 0 WHERE user_id = ? AND channel_id = ?', (match_notifier['user_id'], str(channel.id))) + conn.commit() + await itn.followup.send(f'successfully remove notifier of {username}!', ephemeral=True) + else: + await itn.followup.send(f'can\'t find notifier {username} in {channel.mention}!', ephemeral=True) + + conn.close() + + async def setup(bot): await bot.add_cog(Notification(bot)) \ No newline at end of file diff --git a/src/notification/account_tracker.py b/src/notification/account_tracker.py index f60f6a4..e65fc8c 100644 --- a/src/notification/account_tracker.py +++ b/src/notification/account_tracker.py @@ -59,7 +59,7 @@ async def notification(self, username): if date_comparator(lastest_tweet.created_on, user['lastest_tweet']) == 1: execute(conn, 'UPDATE user SET lastest_tweet = ? WHERE username = ?', (str(lastest_tweet.created_on), username), username) log.info(f'find a new tweet from {username}') - for data in cursor.execute('SELECT * FROM notification WHERE user_id = ?', (user['id'],)): + for data in cursor.execute('SELECT * FROM notification WHERE user_id = ? AND enabled = 1', (user['id'],)): channel = self.bot.get_channel(int(data['channel_id'])) if channel != None: mention = f"{channel.guild.get_role(int(data['role_id'])).mention} " if data['role_id'] != '' else '' From cc0f5003f5bf24bf7c5eed692817abca0edb242f Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:44:41 +0800 Subject: [PATCH 5/7] docs: update README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb78211..8c8a0a4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ Tweetcord is a discord bot that uses the tweety module to let you receive tweet | `channel` | discord.TextChannel | The channel to which the bot delivers notifications | | `mention` | discord.Role | The role to mention when notifying | +👉 `/remove notifier` `username` `channel` + +| parameters | types | descriptions | +| --------- | ----- | ----------- | +| `username` | str | The username of the twitter user you want to turn off notifications for | +| `channel` | discord.TextChannel | The channel which set to delivers notifications | + ## Installation @@ -45,7 +52,7 @@ In certain operating systems, you may need to use the command `pip3` instead of ## Usage -**📢This tutorial is suitable for version 0.3.2 or higher.** +**📢This tutorial is suitable for version 0.3.2 or later. (Recommended: 0.3.4 or later)** ### 1. Create and configure the .env file From ea4aa9aee26a10965438e6906517403255cc6e27 Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:57:39 +0800 Subject: [PATCH 6/7] refactor: change coding style of add notifier command --- cogs/notification.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cogs/notification.py b/cogs/notification.py index fbe71be..7357c0a 100644 --- a/cogs/notification.py +++ b/cogs/notification.py @@ -42,9 +42,10 @@ async def notifier(self, itn : discord.Interaction, username: str, channel: disc await itn.response.defer(ephemeral=True) conn = sqlite3.connect(f"{os.getenv('DATA_PATH')}tracked_accounts.db") + conn.row_factory = sqlite3.Row cursor = conn.cursor() - cursor.execute(f"SELECT * FROM user WHERE username='{username}'") + cursor.execute('SELECT * FROM user WHERE username = ?', (username,)) match_user = cursor.fetchone() roleID = str(mention.id) if mention != None else '' @@ -67,7 +68,7 @@ async def notifier(self, itn : discord.Interaction, username: str, channel: disc else: log.warning(f'unable to turn on notifications for {username}') else: cursor.execute('INSERT OR IGNORE INTO channel VALUES (?)', (str(channel.id),)) - cursor.execute('REPLACE INTO notification (user_id, channel_id, role_id) VALUES (?, ?, ?)', (match_user[0], str(channel.id), roleID)) + cursor.execute('REPLACE INTO notification (user_id, channel_id, role_id) VALUES (?, ?, ?)', (match_user['id'], str(channel.id), roleID)) conn.commit() conn.close() From da41f47af049b37d3b885c8eb8c9de5066a252a8 Mon Sep 17 00:00:00 2001 From: GoldOrange261 <63157008+GoldOrange261@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:05:53 +0800 Subject: [PATCH 7/7] feat: update init_db --- src/db_function/init_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db_function/init_db.py b/src/db_function/init_db.py index d7d990c..f89750a 100644 --- a/src/db_function/init_db.py +++ b/src/db_function/init_db.py @@ -7,7 +7,7 @@ def init_db(): cursor.executescript(""" CREATE TABLE IF NOT EXISTS user (id TEXT PRIMARY KEY, username TEXT, lastest_tweet TEXT); CREATE TABLE IF NOT EXISTS channel (id TEXT PRIMARY KEY); - CREATE TABLE IF NOT EXISTS notification (user_id TEXT, channel_id TEXT, role_id TEXT, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (channel_id) REFERENCES channel (id), PRIMARY KEY(user_id, channel_id)); + CREATE TABLE IF NOT EXISTS notification (user_id TEXT, channel_id TEXT, role_id TEXT, enabled INTEGER DEFAULT 1, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (channel_id) REFERENCES channel (id), PRIMARY KEY(user_id, channel_id)); """) conn.commit() conn.close() \ No newline at end of file