def command_backup(self, event, args): if args.include_channel: channel_ids = set() for channel_ident in args.include_channel: channel_id = _channel_ident_to_channel_id(event.guild, channel_ident) if not channel_id: raise CommandError(f'invalid channel to include `{channel_ident}`') channel_ids.add(channel_id) else: channel_ids = set(i.id for i in event.guild.channels.values() if i.type == ChannelType.GUILD_TEXT) if args.exclude_channel: for channel_ident in args.exclude_channel: channel_id = _channel_ident_to_channel_id(event.guild, channel_ident) if not channel_id: raise CommandError(f'invalid channel to exclude `{channel_ident}`') channel_ids.remove(channel_id) backup = self._open_backup(args.name) messages_count = 0 message = event.msg.reply('Preparing to backup...') for idx, channel_id in enumerate(channel_ids): channel = event.guild.channels[channel_id] message.edit( f'Backing up channel {channel.mention} ({idx+1}/{len(channel_ids)} | {messages_count} total messages)' ) messages_count += self._backup_channel(backup, channel, args.dl_attachments) message.edit(f'Backup complete, a total of {messages_count} messages where preserved') backup.close()
def parse_duration(raw, source=None, negative=False, safe=False): if not raw: if safe: return None raise CommandError('Invalid duration') value = 0 digits = '' for char in raw: if char.isdigit(): digits += char continue if char not in UNITS or not digits: if safe: return None raise CommandError('Invalid duration') value += UNITS[char](int(digits)) digits = '' if negative: value = value * -1 return (source or datetime.utcnow()) + timedelta(seconds=value + 1)
def get_user_info(self, target: str, guild: int = None): """ Used to get a Discord user's information from the SQL server. Args: target: int/str The target user's Discord id or their name/alias. Return dict format: "user_id": int The user's Discord id. "username": 2 <= string <= 15 The user's Last.FM username. "period": string [ 'overall', '7day', '1month', '3month', '6month', '12month' ] The period which 'Top' commands should use. "guild": int The guild id used for alias lookup. """ try: target = AT_to_id(target) except CommandError as e: if guild is not None and not isinstance(guild, int): data = handle_sql( db_session.query(aliases).filter( aliases.guild_id == guild, aliases.alias.like(target)).first) if data: target = data.user_id else: raise CommandError("User alias not found.") elif isinstance(guild, bool): raise CommandError("User aliases aren't enabled in DMs.") else: raise e data = handle_sql( db_session.query(users).filter_by(user_id=target, ).first) if data is None: user = users(user_id=target) handle_sql(db_session.add, user) handle_sql(db_session.flush) data = {"user_id": target, "username": None, "period": periods[0]} else: data = { "user_id": data.user_id, "username": data.last_username, "period": periods[data.period], } if data["username"] is None: raise CommandError( "User should set a last.fm account using ``fm.username``") return data
def api_loop(command, *args, **kwargs): init_time = time() while True: if time() - init_time > 10: raise CommandError("Command timed out.") try: return command(*args, **kwargs) except ConnectionError as e: log.info("Didn't catch error reset.") except APIException as e: if e.code == 50013: raise CommandError( "Missing permissions to respond (possibly Embed Links).") else: log.critical("Api exception: {}: {}".format(e.code, e)) raise e
def generic_user_data(self, username, title_template="{}", guild=None, **kwargs): user_data = self.get_user(username, guild) username = user_data["name"] if username is None: raise CommandError( "User should set a last.fm account using ``fm.username``") inline = { "Playcount": user_data["playcount"], "Registered": strftime( "%Y-%m-%d %H:%M", gmtime(user_data["registered"]["#text"]), ), } fm_embed = bot.generic_embed_values( title=title_template.format(user_data["name"]), url=user_data["url"], thumbnail=user_data["image"][len(user_data["image"]) - 1]["#text"], inlines=inline, skip_inlines="N/A", **kwargs, ) return fm_embed, user_data["name"]
def command_trader_buy(self, event, entity, amount, trader): entity = entity.lower() price_per = self._get_price_for_entity(entity) price_total = price_per * amount if price_total > trader.balance: event.msg.reply( 'Bro... you are gonna need more money for that (${} required) bro...' .format(price_total, )) return current_amount = trader.holdings.get(entity, 0) up = Trader.update( balance=trader.balance - price_total, holdings=Trader.holdings[entity].set(current_amount + amount), ).where((Trader.trader_id == trader.trader_id) & (Trader.balance == trader.balance)).execute() if up != 1: self.log.error( 'Failed to update balance (buy) for {} / {} / {}'.format( entity, amount, trader.trader_id, )) raise CommandError('Bro... something went wrong bro :(') event.msg.reply('Bro... you now own {} (${}) of {} bro...'.format( amount, price_total, entity, ))
def pre_check(self, *args): """ Checks to see if api keys are available. returns a CommandError if not present """ for key in args: if getattr(self, key, None) == None: raise CommandError("This function is disabled.")
def AT_to_id(id: str): if (discord_user_reg.match(str(id)) or discord_nick_reg.match(str(id)) or discord_id_reg.match(str(id))): return int( str(id).replace("<", "").replace("@", "").replace("!", "").replace(">", "")) else: raise CommandError("Invalid @user.")
def remove_player(self, guild_id): if guild_id not in self.guilds: raise CommandError("I'm not currently playing music here.") else: self.get_player(guild_id).player.disconnect() if self.get_player(guild_id).thread.isAlive(): self.get_player(guild_id).thread_end = True self.get_player(guild_id).thread.join() del self.guilds[guild_id]
def get_last_account(self, username: str): if self.user_reg.match(username) and 2 <= len(username) <= 15: url = "https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user={}&api_key={}&format=json".format( username, self.last_key, ) user_data = self.get_cached(url, cool_down=1800, item="user") return user_data else: raise CommandError("Invalid username format.")
def _open_backup(self, name): if os.path.exists(f'{name}.db'): raise CommandError(f'a backup by the name {name} already exists') conn = sqlite3.connect(f'{name}.db') c = conn.cursor() c.execute('CREATE TABLE IF NOT EXISTS messages (id INTEGER, data TEXT)') c.execute('CREATE TABLE IF NOT EXISTS attachments (id INTEGER, data BLOB)') conn.commit() return conn
def on_alias_list_command(self, event, target=None): """ Last.fm Used to get a list of a user's aliases in a guild. When no arguments are given, this will return the author's aliases. Otherwise, this accepts one argument (a target user's @, ID or alias) and will return a list of the target's alises. """ if event.channel.is_dm: api_loop( event.channel.send_message, "Alias commands are guild specific.", ) else: if target is None: target = event.author.id else: try: target = AT_to_id(target) except CommandError as e: data = handle_sql( db_session.query(aliases).filter( aliases.guild_id == event.guild.id, aliases.alias.like(target), ).first) if data is None: raise CommandError( "User alias not found in this guild.") else: target = data.user_id data = handle_sql( db_session.query(aliases).filter_by( user_id=target, guild_id=event.guild.id, ).all) user = self.client.api.guilds_members_get(event.guild.id, target) if data: inline = { str(index + 1): alias.alias for index, alias in enumerate(data) } embed = bot.generic_embed_values( title="{}'s aliases in {}".format( user.name, event.guild.name, ), non_inlines=inline, ) api_loop( event.channel.send_message, embed=embed, ) else: api_loop( event.channel.send_message, "User doesn't have any aliases set in this guild.", )
def search_devices(self, search, device_filter=None): ranks = self.rank_devices(search) if device_filter: ranks = filter(lambda t: device_filter(t[0]), ranks) top = ranks[0] top2 = ranks[1] if top[1] < 30 or top[1] == top2[1]: raise CommandError(':warning: Can\'t find that device! Maybe you mean `{}`, or `{}`?'.format( top[0]['name'], top2[0]['name'] )) return top[0]
def same_channel_check(self, event): try: user_state = event.guild.get_member(event.author).get_voice_state() except Exception as e: log.warning(e) user_state = None if user_state is None: raise CommandError( "You need to be in a voice channel to use this command.") else: bot_state = self.get_player( event.guild.id, ).player.client.channel_id try: same_channel = bot_state == user_state.channel_id except CommandError as e: raise e except Exception as e: log.warning(e) if not same_channel: raise CommandError( "You need to be in the same voice channel to use this command." )
def parse_message_arg(channel_id, message_raw): message_id = None if message_raw.isdigit(): message_id = int(message_raw) else: res = QUOTE_LINK_RE.findall(message_raw) if res: channel_id = int(res[0][1]) message_id = int(res[0][2]) if not message_id: raise CommandError("invalid message id or link provided") return channel_id, message_id
def _get_price_for_entity(self, entity): if entity in self._price_cache: (ts, data) = self._price_cache[entity] if ts > time.time() - CACHE_TTL: return data del self._price_cache[entity] value = None if entity.startswith('$'): r = self._session.get(IEX_URL + '/stock/{}/price'.format(entity[1:].lower())) try: r.raise_for_status() value = r.json() except Exception: self.log.exception('Failed to fetch quote: ') raise CommandError('failed to fetch quote bro!') else: raise CommandError( 'only US stocks prefixed with `$` are supported right now bro...' ) self._price_cache[entity] = (time.time(), value) return value
def on_move(self, event): # """ # Voice Move me to another voice channel (this currently clears play queue). # Accepts no arguments. # """ raise CommandError("NotImplemented") self.pre_check(event) if event.guild.id != 152559372126519296: return api_loop( event.channel.send_message, "This command is currently B R O K E and crippingly out of date.", ) self.get_player(event.guild.id).thread.isAlive() state = event.guild.get_member(event.author).get_voice_state() if not state: return api_loop( event.channel.send_message, "You must be in a voice channel to use that command.", )
def get_spotify_auth(self, auth): access_time = time() r = post( "https://accounts.spotify.com/api/token", data={"grant_type": "client_credentials"}, headers={ "Authorization": "Basic {}".format(auth), "User-Agent": self.user_agent, }, ) if r.status_code != 200: log.warning(r.text) raise CommandError( "Error code {} returned by initial".format( r.status_code, ) ) self.spotify_auth = r.json()["access_token"] self.spotify_auth_expire = r.json()["expires_in"] self.spotify_auth_time = access_time
def command_trader_sell(self, event, entity, amount, trader): entity = entity.lower() if entity not in trader.holdings or trader.holdings[entity] <= 0: event.msg.reply( 'Bro... you don\'t hold any {} bro...'.format(entity)) return current_amount = trader.holdings[entity] if current_amount < amount: event.msg.reply( 'Bro... you only have {}x {} to sell bro...'.format( entity, current_amount)) return price = self._get_price_for_entity(entity) holdings_upd = None if current_amount - amount > 0: holdings_upd = Trader.holdings[entity].set(current_amount - amount) else: holdings_upd = Trader.holdings[entity].remove() up = Trader.update( balance=trader.balance + (price * amount), holdings=holdings_upd, ).where((Trader.trader_id == trader.trader_id) & (Trader.balance == trader.balance)).execute() if up != 1: self.log.error( 'Failed to update balance (sell) for {} / {} / {}'.format( entity, amount, trader.trader_id, )) raise CommandError('Bro... something went wrong bro :(') event.msg.reply('Bro... you sold {} of {} for ${} grats bro...'.format( amount, entity, price * amount, ))
def get_player(self, guild_id): if guild_id not in self.guilds: raise CommandError("I'm not currently playing music here.") return self.guilds.get(guild_id)
def pre_check(self, event): if event.channel.is_dm: raise CommandError( "Voice commands aren't allowed in the forbidden lands.")
def add_reactors(self, client, message, reaction, author_id, *args, channel=None, time: int = 30): if isinstance(message, Message): if (message.id is None or message.channel is None or message.channel.id is None): log.info( "Failed to add reactors, either message.id or message.channel or message.channel.id was None." ) return message_id = message.id channel_id = message.channel.id else: message_id = int(message) if channel_id is not None: if isinstance(channel_id, Channel): if channel_id.id is None: log.info( "Failed to add reactors, channel_id.id was None.") return channel_id = channel_id.id else: channel_id = int(channel_id) else: raise Exception( "Unable to add reactor, either unable to work out channel id or missing channel id." ) for reactor in args: self.add_argument( id=message_id, reactor=reactor, function=reaction, owner_id=author_id, ) for reactor in args: try: client.client.api.channels_messages_reactions_create( channel_id, message_id, reactor, ) except APIException as e: if e.code == 10008: if message_id in self.events: # self.events.pop(message_id, None) del self.events[message_id] break elif e.code == 50001: if message_id in self.events: del self.events[message_id] raise CommandError("Missing ``add reactions`` permission.") else: raise e sleep(time) if message_id in self.events: del self.events[message_id] try: client.client.api.channels_messages_reactions_delete_all( channel_id, message_id, ) except APIException as e: if e.code == 10008: pass elif e.code == 50013: client.client.api.channels_messages_create( channel=channel_id, content= "Missing permission required to clear message reactions ``Manage Messages``.", ) else: raise e
def on_chart_command(self, event): raise CommandError("Not implemented yet, coming soon.")