def set_prefix(guild_id: int, prefix: str): """Sets the command prefix for this guild id to the given prefix. Instead deletes if prefix is None.""" if prefix is None: Database(DEFAULT_DB_NAME).delete_prefix(guild_id) else: Database(DEFAULT_DB_NAME).insert_prefix(Prefix(guild_id, prefix)) load()
def test_set_prefix_none(): assert not prefixes.cache set_prefix(3, "&") assert Database(BOT_TEST_DB_NAME).retrieve_prefix("guild_id=%s", (3, )) == Prefix(3, "&") assert prefixes.cache[3] == "&" set_prefix(3, None) assert Database(BOT_TEST_DB_NAME).retrieve_prefix("guild_id=%s", (3, )) is None assert not prefixes.cache
def set_permission_filter(guild_id: int, command_wrapper: FunctionWrapper, permission_filter: str) -> None: """Updates the permission filter in this guild for this command to the given value. If given `None`, the permission entry is deleted.""" if permission_filter is None: Database(DEFAULT_DB_NAME).delete_permission(guild_id, command_wrapper.names[0]) else: Database(DEFAULT_DB_NAME).insert_permission( CommandPermission(guild_id, command_wrapper.names[0], permission_filter)) load()
async def recent(self, ctx, filter: Option( str, "The first event matching this will be sent in the channel.", required=False, default=None)): """Returns the most recent event gathered, optionally matching `filter`.""" if filter and not await validate_filter(ctx, filter, filter_context): return # `validate_filter` will respond for us. await ctx.defer(ephemeral=False) matching_filter_str = f" matching `{filter}`" if filter else "" filter_query, filter_values = filter_to_sql(filter) database = Database(SCRAPER_DB_NAME) try: event = await database.retrieve_event( where=filter_query, where_values=filter_values, order_by="time DESC", extensive=True if filter else False) except TimeoutError: await ctx.followup.send( f"✗ Took too long to find an event{matching_filter_str}.") return if not event: await ctx.followup.send( f"✗ No event{matching_filter_str} could be found.") return await ctx.followup.send( f"✓ Most recent event{matching_filter_str}:\r\n{format_link(event)}", embed=await format_embed(event))
async def test_unsub(): subs = [ Subscription(guild_id=2, channel_id=6, _filter="type:nominate"), Subscription(guild_id=2, channel_id=4, _filter="user:someone"), Subscription(guild_id=1, channel_id=6, _filter="type:nominate") ] database = Database(BOT_TEST_DB_NAME) for sub in subs: database.insert_subscription(sub) subscriber.load() mock_message = MockMessage( channel=MockChannel(_id=6, guild=MockGuild(_id=2))) mock_command = MockCommand("unsub", context=mock_message) assert all(sub in subscriber.cache for sub in subs) assert await receive_command(mock_command) assert mock_command.response.startswith("✓") assert "🔕" in mock_command.response_embed.fields[0].name.lower() assert "unsubscribed from" in mock_command.response_embed.fields[ 0].name.lower() assert "type:nominate" in mock_command.response_embed.fields[0].value assert "`type:nominate`" in mock_command.response_embed.fields[0].value assert subs[0] not in subscriber.cache assert subs[1] in subscriber.cache assert subs[2] in subscriber.cache
def load() -> None: """Retrieves all subscriptions from the database and appends them to the internal list.""" global cache cache = [] for sub in Database(DEFAULT_DB_NAME).retrieve_subscriptions(): cache.append(sub)
def bot_test_database(): database = Database(BOT_TEST_DB_NAME) database.clear_table_data("subscriptions") database.clear_table_data("prefixes") database.clear_table_data("permissions") db_module.clear_cache(BOT_TEST_DB_NAME) return database
def test_correct_setup(): database = Database(SCRAPER_TEST_DB_NAME) assert not database.retrieve_table_data("events") assert not database.retrieve_table_data("discussions") assert not database.retrieve_table_data("beatmapsets") assert not database.retrieve_table_data("users") assert not database.retrieve_table_data("beatmapset_modes")
def setup_function(): database = Database(BOT_TEST_DB_NAME) # Reset database to state before any tests ran. database.clear_table_data("subscriptions") # Use the test database by default, so we don't clutter the production one. subscriber.DEFAULT_DB_NAME = BOT_TEST_DB_NAME subscriber.cache = []
def setup_function(): database = Database(SCRAPER_TEST_DB_NAME) # Reset database to state before any tests ran. database.clear_table_data("events") database.clear_table_data("discussions") database.clear_table_data("beatmapsets") database.clear_table_data("users") database.clear_table_data("beatmapset_modes")
def add_subscription(sub: Subscription) -> None: """Inserts a subscription into the subscription table of the database and reloads the cache. Causes any new events passing the filter to be sent to the channel.""" if sub.guild_id is None: # Prevents excessive discord rate limiting (5 DMs per second globally). raise ValueError("Cannot subscribe in DM channels.") Database(DEFAULT_DB_NAME).insert_subscription(sub) load()
def load() -> None: """Loads the guild-specific command permissions from the database into the cache.""" cache.clear() for perm_obj in Database(DEFAULT_DB_NAME).retrieve_permissions(): if perm_obj.guild_id not in cache: cache[perm_obj.guild_id] = { perm_obj.command_name: perm_obj.permission_filter } else: cache[perm_obj.guild_id][ perm_obj.command_name] = perm_obj.permission_filter
def _initialize_models(self): """ Set up our database connection and load up the model classes :return: None """ self.db = Database(self.settings) self.db.run_migrations() for channel in self.settings.CHANNEL_LIST: self.channel_models[channel] = self.db.get_models(channel)
async def db_connect(self) -> None: """Estabolish connection with the database.""" self.database = Database(config.DATABASE) connected = await self.database.connect() while not connected: logger.warning("Retrying to connect to database in 5s") # Synchronous sleep function to stop everything until db is connecting time.sleep(5) connected = await self.database.connect() await self.database.load_tables(self.db_table_list, self)
def test_load(): sub1 = Subscription(guild_id=1, channel_id=1, _filter="type:nominate") sub2 = Subscription(guild_id=1, channel_id=2, _filter="type:ranked") database = Database(BOT_TEST_DB_NAME) database.insert_subscription(sub1) database.insert_subscription(sub2) subscriber.load() assert sub1 in subscriber.cache assert sub2 in subscriber.cache
def test_load(): permissions.load() assert not permissions.cache Database(BOT_TEST_DB_NAME).insert_permission( CommandPermission(guild_id=3, command_name="test1", permission_filter="filter")) permissions.load() assert permissions.cache assert permissions.cache[3]["test1"] == "filter"
def retrieve_with_timeout(db_name, table, where="TRUE", selection="*", group_by: str = None, order_by: str = None, limit: int = None): try: db = Database(db_name) return db.retrieve_table_data(table=table, where=where, selection=selection, group_by=group_by, order_by=order_by, limit=limit)[0][0] except TimeoutError: return "(timed out)"
async def format_history(beatmapset: Beatmapset, length_limit: int = None, database: Database = None) -> str: """Returns the nomination history of this beatmapset (i.e. icons and names of actions and their authors). Optionally within a certain length, smartly shortening/truncating the contents if needed.""" if not database: database = Database( SCRAPER_DB_NAME) # Using wrapped database to access events. # Sorted by time ascending; newer events first. events = list( filter(lambda event: type_props[event.type].show_in_history, await database.retrieve_beatmapset_events(beatmapset))) long_history = "" for event in events: # Some nomination events seem to be missing a user. if not event.user: if event.discussion and event.discussion.user: event.user = event.discussion.user else: # Old versions of the scraper inserted system qualifications, we should ignore those. continue long_history = ( f"{type_props[event.type].emoji} [{event.user}](https://osu.ppy.sh/users/{event.user.id})" + ("\u2000" if long_history else "") + long_history) if length_limit is None or len(long_history) <= length_limit: return f"\n{long_history}" short_history = "" for event in events: emoji = (f"{type_props[event.type].emoji}" + (" " if short_history else "")) # `- 3` to give space to ellipses if we need them. if len(short_history) + len(emoji) <= length_limit - 3: short_history = emoji + short_history else: # If there isn't enough space for anything, we skip the history completely. if short_history: short_history = "..." + short_history break return f"\n{short_history}"
async def test_recent(): beatmapset = Beatmapset(1, "artist", "title", creator=User(2, "sometwo"), modes=["osu"]) event1 = Event("nominate", from_string("2020-01-01 00:00:00"), beatmapset, user=User(1, "someone")) event2 = Event("qualify", from_string("2020-01-01 01:00:00"), beatmapset, user=User(4, "somefour"), content="nicely done") database = Database(SCRAPER_TEST_DB_NAME) database.insert_event(event1) database.insert_event(event2) mock_message = MockMessage( channel=MockChannel(_id=6, guild=MockGuild(_id=2))) mock_command = MockCommand("recent", "type:(nominate or qualify)", context=mock_message) assert await receive_command(mock_command) assert mock_command.response.startswith("✓") assert "https://osu.ppy.sh/beatmapsets/1" in mock_command.response assert mock_command.response_embed assert mock_command.response_embed.fields assert mock_command.response_embed.fields[0].name.startswith( ":heart:\u2000Qualified (**") assert mock_command.response_embed.fields[0].name.endswith("** ago)") assert "artist - title" in mock_command.response_embed.fields[0].value assert "sometwo" in mock_command.response_embed.fields[0].value assert mock_command.response_embed.footer.text == "somefour \"nicely done\"" assert mock_command.response_embed.footer.icon_url == "https://a.ppy.sh/4"
async def cmd_recent(command: Command, _filter: str = None): if _filter and not await validate_filter(command, _filter, filter_context): return # `validate_filter` will respond for us. filter_query, filter_values = filter_to_sql(_filter) database = Database(SCRAPER_DB_NAME) event = await database.retrieve_event(where=f""" {filter_query} ORDER BY time DESC """, where_values=filter_values, extensive=True) matching_filter_str = f" matching `{_filter}`" if _filter else "" if not event: await command.respond_err( f"No event{matching_filter_str} could be found.") return await command.respond( response= f"✓ Most recent event{matching_filter_str}\r\n{format_link(event)}", embed=await format_embed(event))
import string import random import time import os from bot.database import Database from pyrogram import filters from pyrogram import Client from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid import aiofiles import aiofiles.os from bot import (SESSION_NAME, DATABASE_URL) db = Database(DATABASE_URL, SESSION_NAME) CURRENT_PROCESSES = {} CHAT_FLOOD = {} broadcast_ids = {} async def send_msg(user_id, message): try: await message.forward(chat_id=user_id) return 200, None except FloodWait as e: await asyncio.sleep(e.x) return send_msg(user_id, message) except InputUserDeactivated: return 400, f"{user_id} : deactivated\n" except UserIsBlocked:
def setup_function(): prefixes.DEFAULT_DB_NAME = BOT_TEST_DB_NAME Database(BOT_TEST_DB_NAME).clear_table_data("prefixes") prefixes.cache = {}
from pyrogram import filters as Filters from bot.screenshotbot import ScreenShotBot from bot.utils import Utilities from bot.config import Config from bot.database import Database db = Database() @ScreenShotBot.on_callback_query( Filters.create(lambda _, __, query: query.data.startswith("set")) ) async def settings_cb(c, m): try: _, typ, action = m.data.split("+") # Reverse compatibility. except Exception: _, typ = m.data.split("+") chat_id = m.from_user.id alert_text = "Not supported action." if typ == "af": as_file = await db.is_as_file(chat_id) await db.update_as_file(chat_id, not as_file) alert_text = "Successfully changed screenshot upload mode!" elif typ == "wm": watermark_text = await db.get_watermark_text(chat_id) if watermark_text: await db.update_watermark_text(chat_id)
def test_database(): database = Database(SCRAPER_TEST_DB_NAME) database.clear_table_data("events") db_module.clear_cache(SCRAPER_TEST_DB_NAME) return database
def test_correct_setup(): assert not Database(BOT_TEST_DB_NAME).retrieve_table_data("subscriptions")
import logging import os import sys from sqlite3 import DatabaseError from telegram import ParseMode, TelegramError from telegram.ext import Defaults, Updater from bot.database import Database logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(funcName)s - %(message)s", datefmt="%Y.%m.%d %H:%M:%S") try: updater = Updater(token=os.environ['TOKEN'], defaults=Defaults(parse_mode=ParseMode.MARKDOWN)) except KeyError: logging.critical("'TOKEN' environment variable is required.") sys.exit(1) except TelegramError as err: logging.critical("Telegram connection error: %s", err) sys.exit(1) try: db = Database('data/facebook.db') except DatabaseError as err: logging.critical("Database connection error: %s", err) sys.exit(1)
def get_subscription(channel: TextChannel) -> Subscription: """Returns the subscription associated with the given channel, if any, otherwise None.""" return Database(DEFAULT_DB_NAME).retrieve_subscription( "guild_id=%s AND channel_id=%s", (guild_id_or_none(channel), channel.id))
def remove_subscription(sub: Subscription) -> None: """Deletes a subscription from the subscription table of the database and reloads the cache.""" Database(DEFAULT_DB_NAME).delete_subscription(sub) load()
def setup_function(): permissions.DEFAULT_DB_NAME = BOT_TEST_DB_NAME Database(BOT_TEST_DB_NAME).clear_table_data("permissions") permissions.cache = {}
def setup_function(): subscriber.DEFAULT_DB_NAME = BOT_TEST_DB_NAME # Reset database to state before any tests ran. Database(BOT_TEST_DB_NAME).clear_table_data("subscriptions")