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 diff --git a/bot.py b/bot.py index ed215dd..8a32269 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__) @@ -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() diff --git a/cogs/notification.py b/cogs/notification.py index 40eda17..7357c0a 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() @@ -41,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 '' @@ -58,7 +60,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 +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 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() @@ -76,5 +78,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/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 70% rename from src/init_db.py rename to src/db_function/init_db.py index d7d990c..f89750a 100644 --- a/src/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 diff --git a/src/notification/account_tracker.py b/src/notification/account_tracker.py index 28bc6ec..e65fc8c 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,14 @@ 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'],)): + 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'])) - 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.commit() conn.close()