class Backfill(object): Mode = Enum('FULL', 'SPARSE', 'BACKFILL') Direction = Enum('UP', 'DOWN') def __init__(self, log, channel, mode, direction=Direction.UP): self.log = log self.channel = channel self.mode = mode self.direction = direction self.scanned = 0 self.inserted = 0 def start(self): # First, generate a starting point self.log.info('Starting %s backfill on %s going %s', self.mode, self.channel, self.direction) start = None if self.mode in (Backfill.Mode.FULL, Backfill.Mode.SPARSE): # If we are going newest - oldest if self.direction is Backfill.Direction.UP: start = self.channel.last_message_id if not start: self.log.warning('Invalid last_message_id for {}'.format(self.channel)) return else: start = 0 elif self.mode is Backfill.Mode.BACKFILL: q = Message.for_channel(self.channel) if self.direction is Backfill.Direction.UP: q = q.order_by(Message.id.asc()).limit(1).get().id else: q = q.order_by(Message.id.desc()).limit(1).get().id if self.direction is Backfill.Direction.UP: msgs = self.channel.messages_iter(bulk=True, before=start) else: msgs = self.channel.messages_iter(bulk=True, after=start) for chunk in msgs: self.scanned += len(chunk) existing = {i.id for i in Message.select(Message.id).where((Message.id << [i.id for i in chunk]))} if len(existing) < len(chunk): Message.from_disco_message_many([i for i in chunk if i.id not in existing]) self.inserted += len(chunk) - len(existing) if len(existing) and self.mode is Backfill.Mode.BACKFILL: self.log.info('Found %s existing messages, breaking', len(existing)) break if len(existing) == len(chunk) and self.mode is Backfill.Mode.Sparse: self.log.info('Found %s existing messages, breaking', len(existing)) break
def get_max_emoji_slots(self, guild): emoji_max_slots = 50 emoji_max_slots_more = 200 PremiumGuildLimits = Enum( NONE=50, TIER_1=100, TIER_2=150, TIER_3=250, ) return max(emoji_max_slots_more if 'MORE_EMOJI' in guild.features else emoji_max_slots, PremiumGuildLimits[guild.premium_tier.name].value)
def test_model_field_enum(self): en = Enum(A=1, B=2, C=3) class _M(Model): field = Field(enum(en)) self.assertEqual(_M(field=en.A).field, en.A) self.assertEqual(_M(field=2).field, en.B) self.assertEqual(_M(field='3').field, None) self.assertEqual(_M(field='a').field, en.A) self.assertEqual(_M(field='A').field, en.A)
def test_bitmask_enum(self): enum = Enum('a', 'b', 'c', 'd', 'e', 'f', 'g') self.assertEqual(enum.a, enum.a) self.assertNotEqual(enum.a, enum.b) self.assertLess(enum.b, enum.e) self.assertLess(enum.a, enum.b) self.assertGreater(enum.g, enum.a) self.assertGreater(enum.g, enum.f) self.assertLessEqual(enum.c, enum.c) self.assertLessEqual(enum.c, enum.d) self.assertGreaterEqual(enum.e, enum.e) self.assertGreaterEqual(enum.e, enum.d) self.assertEqual(enum.a.index, 1) self.assertEqual(enum.b.index, 2) self.assertEqual(enum.c.index, 4)
class Submission(Model): State = Enum( 'COUNCIL_QUEUE', 'APPROVAL_QUEUE', 'DENIED', 'APPROVED', ) class Meta: database = db name = TextField() author = BigIntegerField() contents = TextField(null=True) temp_emoji_id = BigIntegerField(null=True) submission_queue_msg = BigIntegerField(null=True) council_queue_msg = BigIntegerField(null=True) approval_queue_msg = BigIntegerField(null=True) yay = IntegerField(default=0) nay = IntegerField(default=0) state = IntegerField(default=State.COUNCIL_QUEUE)
from holster.enum import Enum from disco.types.base import SlottedModel, Field, snowflake, text, with_equality, with_hash DefaultAvatars = Enum( BLURPLE=0, GREY=1, GREEN=2, ORANGE=3, RED=4, ) class User(SlottedModel, with_equality('id'), with_hash('id')): id = Field(snowflake) username = Field(text) avatar = Field(text) discriminator = Field(text) bot = Field(bool, default=False) verified = Field(bool) email = Field(text) presence = Field(None) def get_avatar_url(self, fmt='webp', size=1024): if not self.avatar: return 'https://cdn.discordapp.com/embed/avatars/{}.png'.format( self.default_avatar.value) return 'https://cdn.discordapp.com/avatars/{}/{}.{}?size={}'.format( self.id,
class Player(object): Events = Enum( 'START_PLAY', 'STOP_PLAY', 'PAUSE_PLAY', 'RESUME_PLAY', 'DISCONNECT', ) def __init__(self, client, queue=None): self.client = client # Queue contains playable items self.queue = queue or PlayableQueue() # Whether we're playing music (true for lifetime) self.playing = True # Set to an event when playback is paused self.paused = None # Current playing item self.now_playing = None # Current play task self.play_task = None # Core task self.run_task = gevent.spawn(self.run) # Event triggered when playback is complete self.complete = gevent.event.Event() # Event emitter for metadata self.events = Emitter(spawn_each=True) def disconnect(self): self.client.disconnect() self.events.emit(self.Events.DISCONNECT) def skip(self): self.play_task.kill() def pause(self): if self.paused: return self.paused = gevent.event.Event() self.events.emit(self.Events.PAUSE_PLAY) def resume(self): if self.paused: self.paused.set() self.paused = None self.events.emit(self.Events.RESUME_PLAY) def play(self, item): # Grab the first frame before we start anything else, sometimes playables # can do some lengthy async tasks here to setup the playable and we # don't want that lerp the first N frames of the playable into playing # faster frame = item.next_frame() if frame is None: return start = time.time() loops = 0 while True: loops += 1 if self.paused: self.client.set_speaking(False) self.paused.wait() gevent.sleep(2) self.client.set_speaking(True) start = time.time() loops = 0 if self.client.state == VoiceState.DISCONNECTED: return if self.client.state != VoiceState.CONNECTED: self.client.state_emitter.wait(VoiceState.CONNECTED) self.client.send_frame(frame) self.client.timestamp += item.samples_per_frame if self.client.timestamp > MAX_TIMESTAMP: self.client.timestamp = 0 frame = item.next_frame() if frame is None: return next_time = start + 0.02 * loops delay = max(0, 0.02 + (next_time - time.time())) gevent.sleep(delay) def run(self): self.client.set_speaking(True) while self.playing: self.now_playing = self.queue.get() self.events.emit(self.Events.START_PLAY, self.now_playing) self.play_task = gevent.spawn(self.play, self.now_playing) self.play_task.join() self.events.emit(self.Events.STOP_PLAY, self.now_playing) if self.client.state == VoiceState.DISCONNECTED: self.playing = False self.complete.set() return self.client.set_speaking(False) self.disconnect()
from disco.gateway.packets import OPCode from disco.api.http import APIException from disco.util.snowflake import to_snowflake from disco.util.functional import cached_property from disco.types.base import (SlottedModel, Field, ListField, AutoDictField, snowflake, text, binary, enum, datetime) from disco.types.user import User from disco.types.voice import VoiceState from disco.types.channel import Channel from disco.types.message import Emoji from disco.types.permissions import PermissionValue, Permissions, Permissible VerificationLevel = Enum( NONE=0, LOW=1, MEDIUM=2, HIGH=3, ) class GuildEmoji(Emoji): """ An emoji object. Attributes ---------- id : snowflake The ID of this emoji. name : str The name of this emoji. require_colons : bool
from holster.enum import Enum, EnumAttr Permissions = Enum( CREATE_INSTANT_INVITE=1 << 0, KICK_MEMBERS=1 << 1, BAN_MEMBERS=1 << 2, ADMINISTRATOR=1 << 3, MANAGE_CHANNELS=1 << 4, MANAGE_GUILD=1 << 5, READ_MESSAGES=1 << 10, SEND_MESSAGES=1 << 11, SEND_TSS_MESSAGES=1 << 12, MANAGE_MESSAGES=1 << 13, EMBED_LINKS=1 << 14, ATTACH_FILES=1 << 15, READ_MESSAGE_HISTORY=1 << 16, MENTION_EVERYONE=1 << 17, USE_EXTERNAL_EMOJIS=1 << 18, CONNECT=1 << 20, SPEAK=1 << 21, MUTE_MEMBERS=1 << 22, DEAFEN_MEMBERS=1 << 23, MOVE_MEMBERS=1 << 24, USE_VAD=1 << 25, CHANGE_NICKNAME=1 << 26, MANAGE_NICKNAMES=1 << 27, MANAGE_ROLES=1 << 28, MANAGE_WEBHOOKS=1 << 29, MANAGE_EMOJIS=1 << 30, )
class Infraction(BaseModel): Types = Enum( 'MUTE', 'KICK', 'TEMPBAN', 'SOFTBAN', 'BAN', 'TEMPMUTE', 'UNBAN', 'TEMPROLE', 'WARNING', bitmask=False, ) guild_id = BigIntegerField() user_id = BigIntegerField() actor_id = BigIntegerField(null=True) type_ = IntegerField(db_column='type') reason = TextField(null=True) metadata = BinaryJSONField(default={}) expires_at = DateTimeField(null=True) created_at = DateTimeField(default=datetime.utcnow) active = BooleanField(default=True) class Meta: db_table = 'infractions' indexes = ((('guild_id', 'user_id'), False), ) def serialize(self, guild=None, user=None, actor=None, include_metadata=False): base = { 'id': str(self.id), 'guild': (guild and guild.serialize()) or { 'id': str(self.guild_id) }, 'user': (user and user.serialize()) or { 'id': str(self.user_id) }, 'actor': (actor and actor.serialize()) or { 'id': str(self.actor_id) }, 'reason': self.reason, 'expires_at': self.expires_at, 'created_at': self.created_at, 'active': self.active, } base['type'] = { 'id': self.type_, 'name': next(i.name for i in Infraction.Types.attrs if i.index == self.type_) } if include_metadata: base['metadata'] = self.metadata return base @staticmethod def infractions_config(event): return getattr(event.base_config.plugins, 'infractions', None) @classmethod def load_archived(cls, event, user, user_tag, actor, actor_tag, type_, reason): User.from_archive(user, user_tag) User.from_archive(actor, actor_tag) load_time = datetime.utcnow() cls.create(guild_id=event.guild.id, user_id=user, actor_id=actor, type_={i.name: i.index for i in Infraction.Types.attrs}[type_], reason=reason, expires_at=load_time, created_at=load_time, active=False) @classmethod def temprole(cls, plugin, event, member, role_id, reason, expires_at): User.from_disco_user(member.user) # TODO: modlog member.add_role(role_id, reason=reason) cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPROLE, reason=reason, expires_at=expires_at, metadata={'role': role_id}) @classmethod def kick(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) # Prevent the GuildMemberRemove log event from triggering plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove'], user_id=member.user.id) member.kick(reason=reason) # Create a kick modlog event plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_KICk, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.KICK, reason=reason) @classmethod def tempban(cls, plugin, event, member, reason, expires_at): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd'], user_id=member.user.id) member.ban(reason=reason) plugin.call( 'ModLogPlugin.log_action_ext', Actions.MEMBER_TEMPBAN, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason', expires=expires_at, ) cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPBAN, reason=reason, expires_at=expires_at) @classmethod def softban(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd', 'GuildBanRemove'], user_id=member.user.id) member.ban(delete_message_days=7, reason=reason) member.unban(reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_SOFTBAN, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.SOFTBAN, reason=reason) @classmethod def ban(cls, plugin, event, member, reason, guild): from rowboat.plugins.modlog import Actions if isinstance(member, (int, long)): user_id = member else: User.from_disco_user(member.user) user_id = member.user.id plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd'], user_id=user_id, ) guild.create_ban(user_id, reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_BAN, event.guild.id, user=unicode(member), user_id=user_id, actor=unicode(event.author) if event.author.id != user_id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=guild.id, user_id=user_id, actor_id=event.author.id, type_=cls.Types.BAN, reason=reason) @classmethod def warn(cls, plugin, event, member, reason, guild): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) user_id = member.user.id cls.create(guild_id=guild.id, user_id=user_id, actor_id=event.author.id, type_=cls.Types.WARNING, reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_WARNED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') @classmethod def mute(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions infractions_config = cls.infractions_config(event) plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberUpdate'], user_id=member.user.id, role_id=infractions_config.mute_role, ) member.add_role(infractions_config.mute_role, reason=reason) try: if infractions_config.mute_channel: member.modify(channel_id=infractions_config.mute_channel) except: pass plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_MUTED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.MUTE, reason=reason, metadata={ 'role': infractions_config.mute_role, 'mute': True }) @classmethod def tempmute(cls, plugin, event, member, reason, expires_at): from rowboat.plugins.modlog import Actions infractions_config = cls.infractions_config(event) if not infractions_config.mute_role: plugin.log.warning('Cannot tempmute member %s, no tempmute role', member.id) return plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberUpdate'], user_id=member.user.id, role_id=infractions_config.mute_role, ) member.add_role(infractions_config.mute_role, reason=reason) try: if infractions_config.mute_channel: member.modify(channel_id=infractions_config.mute_channel) except: pass plugin.call( 'ModLogPlugin.log_action_ext', Actions.MEMBER_TEMP_MUTED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason', expires=expires_at, ) cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPMUTE, reason=reason, expires_at=expires_at, metadata={ 'role': infractions_config.mute_role, 'mute': True }) @classmethod def clear_active(cls, event, user_id, types): """ Marks a previously active tempmute as inactive for the given event/user. This should be used in all locations where we either think this is no longer active (e.g. the mute role was removed) _or_ when we don't want to unmute the user any longer, e.g. they've been remuted by another command. """ return cls.update(active=False).where( (cls.guild_id == event.guild.id) & (cls.user_id == user_id) & (cls.type_ << types) & (cls.active == 1)).execute() >= 1
from disco.util.websocket import Websocket from disco.util.logging import LoggingClass from disco.util.limiter import SimpleLimiter TEN_MEGABYTES = 10490000 ZLIB_SUFFIX = b'\x00\x00\xff\xff' GatewayIntent = Enum( GUILDS=1 << 0, GUILD_MEMBERS=1 << 1, GUILD_BANS=1 << 2, GUILD_EMOJIS=1 << 3, GUILD_INTEGRATIONS=1 << 4, GUILD_WEBHOOKS=1 << 5, GUILD_INVITES=1 << 6, GUILD_VOICE_STATES=1 << 7, GUILD_PRESENCES=1 << 8, GUILD_MESSAGES=1 << 9, GUILD_MESSAGE_REACTIONS=1 << 10, GUILD_MESSAGE_TYPING=1 << 11, DIRECT_MESSAGES=1 << 12, DIRECT_MESSAGE_REACTIONS=1 << 13, DIRECT_MESSAGE_TYPING=1 << 14, ) class GatewayClient(LoggingClass): GATEWAY_VERSION = 6 def __init__(self, client,
import gevent import six import sys from holster.enum import Enum from disco import VERSION as disco_version from requests import __version__ as requests_version from disco.util.logging import LoggingClass from disco.api.ratelimit import RateLimiter # Enum of all HTTP methods used HTTPMethod = Enum( GET='GET', POST='POST', PUT='PUT', PATCH='PATCH', DELETE='DELETE', ) def to_bytes(obj): if six.PY2: if isinstance(obj, six.text_type): return obj.encode('utf-8') return obj class Routes(object): """ Simple Python object-enum of all method/url route combinations available to
class MessageIterator(object): """ An iterator which supports scanning through the messages for a channel. Parameters ---------- client : :class:`disco.client.Client` The disco client instance to use when making requests. channel : `Channel` The channel to iterate within. direction : :attr:`MessageIterator.Direction` The direction in which this iterator will move. bulk : bool If true, this iterator will yield messages in list batches, otherwise each message will be yield individually. before : snowflake The message to begin scanning at. after : snowflake The message to begin scanning at. chunk_size : int The number of messages to request per API call. """ Direction = Enum('UP', 'DOWN') def __init__(self, client, channel, direction=Direction.UP, bulk=False, before=None, after=None, chunk_size=100): self.client = client self.channel = channel self.direction = direction self.bulk = bulk self.before = before self.after = after self.chunk_size = chunk_size self.last = None self._buffer = [] if not any((before, after)) and self.direction == self.Direction.DOWN: raise Exception( 'Must specify either before or after for downward seeking') def fill(self): """ Fills the internal buffer up with :class:`disco.types.message.Message` objects from the API """ self._buffer = self.client.api.channels_messages_list( self.channel, before=self.before, after=self.after, limit=self.chunk_size) if not len(self._buffer): raise StopIteration self.after = None self.before = None if self.direction == self.Direction.UP: self.before = self._buffer[-1].id else: self._buffer.reverse() self.after == self._buffer[-1].id def next(self): return self.__next__() def __iter__(self): return self def __next__(self): if not len(self._buffer): self.fill() if self.bulk: res = self._buffer self._buffer = [] return res else: return self._buffer.pop()
import six from holster.enum import Enum from disco.util.snowflake import to_snowflake from disco.util.functional import cached_property, one_or_many, chunks from disco.types.user import User from disco.types.base import Model, Field, snowflake, enum, listof, dictof, text from disco.types.permissions import Permissions, Permissible, PermissionValue from disco.voice.client import VoiceClient ChannelType = Enum( GUILD_TEXT=0, DM=1, GUILD_VOICE=2, GROUP_DM=3, ) PermissionOverwriteType = Enum(ROLE='role', MEMBER='member') class PermissionOverwrite(Model): """ A PermissionOverwrite for a :class:`Channel` Attributes ---------- id : snowflake The overwrite ID type : :const:`disco.types.channel.PermissionsOverwriteType`
from disco.bot import CommandLevels from disco.types.base import UNSET, cached_property from disco.util.snowflake import to_unix, to_datetime from disco.util.sanitize import S from rowboat.plugins import RowboatPlugin as Plugin from rowboat.types import SlottedModel, Field, ListField, DictField, ChannelField, snowflake from rowboat.types.plugin import PluginConfig from rowboat.models.message import Message, MessageArchive from rowboat.models.guild import Guild from rowboat.util import ordered_load, MetaException from .pump import ModLogPump # Dynamically updated by the plugin Actions = Enum() URL_REGEX = re.compile(r'(https?://[^\s]+)') def filter_urls(content): return URL_REGEX.sub(r'<\1>', content) class ChannelConfig(SlottedModel): compact = Field(bool, default=True) include = ListField(Actions) exclude = ListField(Actions) rich = ListField(Actions) timestamps = Field(bool, default=False)
import json import arrow from datetime import datetime from holster.enum import Enum from peewee import IntegerField, DateTimeField from playhouse.postgres_ext import BinaryJSONField, BooleanField from rowboat.sql import ModelBase from rowboat.redis import rdb NotificationTypes = Enum( GENERIC=1, CONNECT=2, RESUME=3, GUILD_JOIN=4, GUILD_LEAVE=5, ) @ModelBase.register class Notification(ModelBase): Types = NotificationTypes type_ = IntegerField(column_name='type') metadata = BinaryJSONField(default={}) read = BooleanField(default=False) created_at = DateTimeField(default=datetime.utcnow) class Meta: table_name = 'notifications'
class Guild(BaseModel): WhitelistFlags = Enum( 'MUSIC', 'MODLOG_CUSTOM_FORMAT', bitmask=False ) guild_id = BigIntegerField(primary_key=True) owner_id = BigIntegerField(null=True) name = TextField(null=True) icon = TextField(null=True) splash = TextField(null=True) region = TextField(null=True) last_ban_sync = DateTimeField(null=True) # Rowboat specific data config = BinaryJSONField(null=True) config_raw = BlobField(null=True) enabled = BooleanField(default=True) whitelist = BinaryJSONField(default=[]) added_at = DateTimeField(default=datetime.utcnow) # SQL = ''' # CREATE OR REPLACE FUNCTION shard (int, bigint) # RETURNS bigint AS $$ # SELECT ($2 >> 22) % $1 # $$ LANGUAGE SQL; # ''' class Meta: db_table = 'guilds' @classmethod def with_id(cls, guild_id): return cls.get(guild_id=guild_id) @classmethod def setup(cls, guild): return cls.create( guild_id=guild.id, owner_id=guild.owner_id, name=guild.name, icon=guild.icon, splash=guild.splash, region=guild.region, config={'web': {guild.owner_id: 'admin'}}, config_raw='') def is_whitelisted(self, flag): return int(flag) in self.whitelist def update_config(self, actor_id, raw): from rowboat.types.guild import GuildConfig parsed = yaml.load(raw) GuildConfig(parsed).validate() GuildConfigChange.create( user_id=actor_id, guild_id=self.guild_id, before_raw=self.config_raw, after_raw=raw) self.update(config=parsed, config_raw=raw).where(Guild.guild_id == self.guild_id).execute() self.emit('GUILD_UPDATE') def emit(self, action, **kwargs): emit(action, id=self.guild_id, **kwargs) def sync(self, guild): updates = {} for key in ['owner_id', 'name', 'icon', 'splash', 'region']: if getattr(guild, key) != getattr(self, key): updates[key] = getattr(guild, key) if updates: Guild.update(**updates).where(Guild.guild_id == self.guild_id).execute() def get_config(self, refresh=False): from rowboat.types.guild import GuildConfig if refresh: self.config = Guild.select(Guild.config).where(Guild.guild_id == self.guild_id).get().config if refresh or not hasattr(self, '_cached_config'): try: self._cached_config = GuildConfig(self.config) except: log.exception('Failed to load config for Guild %s, invalid: ', self.guild_id) return None return self._cached_config def sync_bans(self, guild): # Update last synced time Guild.update( last_ban_sync=datetime.utcnow() ).where(Guild.guild_id == self.guild_id).execute() try: bans = guild.get_bans() except: log.exception('sync_bans failed:') return log.info('Syncing %s bans for guild %s', len(bans), guild.id) GuildBan.delete().where( (~(GuildBan.user_id << list(bans.keys()))) & (GuildBan.guild_id == guild.id) ).execute() for ban in bans.values(): GuildBan.ensure(guild, ban.user, ban.reason) def serialize(self): base = { 'id': str(self.guild_id), 'owner_id': str(self.owner_id), 'name': self.name, 'icon': self.icon, 'splash': self.splash, 'region': self.region, 'enabled': self.enabled, 'whitelist': self.whitelist } if hasattr(self, 'role'): base['role'] = self.role return base
from holster.enum import Enum from holster.emitter import Emitter from disco.gateway.encoding.json import JSONEncoder from disco.util.websocket import Websocket from disco.util.logging import LoggingClass from disco.gateway.packets import OPCode from disco.voice.packets import VoiceOPCode from disco.voice.udp import UDPVoiceClient VoiceState = Enum( DISCONNECTED=0, RECONNECTING=1, AWAITING_ENDPOINT=2, AUTHENTICATING=3, AUTHENTICATED=4, CONNECTING=5, CONNECTED=6, VOICE_CONNECTING=7, VOICE_CONNECTED=8, ) class VoiceException(Exception): def __init__(self, msg, client): self.voice_client = client super(VoiceException, self).__init__(msg) class VoiceClient(LoggingClass): VOICE_GATEWAY_VERSION = 3
AutoDictField, snowflake, text, datetime, enum, cached_property, ) from disco.util.paginator import Paginator from disco.util.snowflake import to_snowflake from disco.types.user import User MessageType = Enum( DEFAULT=0, RECIPIENT_ADD=1, RECIPIENT_REMOVE=2, CALL=3, CHANNEL_NAME_CHANGE=4, CHANNEL_ICON_CHANGE=5, PINS_ADD=6, GUILD_MEMBER_JOIN=7, ) class Emoji(SlottedModel): """ Represents either a standard or custom Discord emoji. Attributes ---------- id : snowflake? The emoji ID (will be none if this is not a custom emoji). name : str
snowflake, text, enum, datetime, cached_property, ) from disco.types.user import User from disco.types.voice import VoiceState from disco.types.channel import Channel, ChannelType from disco.types.message import Emoji from disco.types.permissions import PermissionValue, Permissions, Permissible VerificationLevel = Enum( NONE=0, LOW=1, MEDIUM=2, HIGH=3, EXTREME=4, ) ExplicitContentFilterLevel = Enum( NONE=0, WITHOUT_ROLES=1, ALL=2, ) DefaultMessageNotificationsLevel = Enum( ALL_MESSAGES=0, ONLY_MENTIONS=1, )
from disco.util.sanitize import S from disco.api.http import APIException from rowboat.redis import rdb from rowboat.util.stats import timed from rowboat.util.zalgo import ZALGO_RE from rowboat.plugins import RowboatPlugin as Plugin from rowboat.types import SlottedModel, Field, ListField, DictField, ChannelField, snowflake, lower from rowboat.types.plugin import PluginConfig from rowboat.models.message import Message from rowboat.plugins.modlog import Actions from rowboat.constants import INVITE_LINK_RE, URL_RE CensorReason = Enum( 'INVITE', 'DOMAIN', 'WORD', 'ZALGO', ) class CensorSubConfig(SlottedModel): filter_zalgo = Field(bool, default=True) filter_invites = Field(bool, default=True) invites_guild_whitelist = ListField(snowflake, default=[]) invites_whitelist = ListField(lower, default=[]) invites_blacklist = ListField(lower, default=[]) filter_domains = Field(bool, default=True) domains_whitelist = ListField(lower, default=[]) domains_blacklist = ListField(lower, default=[])
import string import weakref from holster.enum import Enum from disco.util.logging import LoggingClass from disco.util.serializer import dump_function, load_function def get_random_str(size): return ''.join([random.choice(string.printable) for _ in range(size)]) IPCMessageType = Enum( 'CALL_FUNC', 'GET_ATTR', 'EXECUTE', 'RESPONSE', ) class GIPCProxy(LoggingClass): def __init__(self, obj, pipe): super(GIPCProxy, self).__init__() self.obj = obj self.pipe = pipe self.results = weakref.WeakValueDictionary() gevent.spawn(self.read_loop) def resolve(self, parts): base = self.obj for part in parts:
class Infraction(BaseModel): Types = Enum( 'MUTE', 'KICK', 'TEMPBAN', 'SOFTBAN', 'BAN', 'TEMPMUTE', 'UNBAN', 'TEMPROLE', 'WARNING', bitmask=False, ) guild_id = BigIntegerField() user_id = BigIntegerField() actor_id = BigIntegerField(null=True) type_ = IntegerField(db_column='type') reason = TextField(null=True) metadata = BinaryJSONField(default={}) expires_at = DateTimeField(null=True) created_at = DateTimeField(default=datetime.utcnow) active = BooleanField(default=True) class Meta: db_table = 'infractions' indexes = ((('guild_id', 'user_id'), False), ) @staticmethod def admin_config(event): return getattr(event.base_config.plugins, 'admin', None) @classmethod def temprole(cls, plugin, event, member, role_id, reason, expires_at): User.from_disco_user(member.user) # TODO: modlog member.add_role(role_id, reason=reason) cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPROLE, reason=reason, expires_at=expires_at, metadata={'role': role_id}) @classmethod def kick(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) # Prevent the GuildMemberRemove log event from triggering plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove'], user_id=member.user.id) member.kick(reason=reason) # Create a kick modlog event plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_KICk, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.KICK, reason=reason) @classmethod def tempban(cls, plugin, event, member, reason, expires_at): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd'], user_id=member.user.id) member.ban(reason=reason) plugin.call( 'ModLogPlugin.log_action_ext', Actions.MEMBER_TEMPBAN, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason', expires=expires_at, ) cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPBAN, reason=reason, expires_at=expires_at) @classmethod def softban(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) plugin.call('ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd', 'GuildBanRemove'], user_id=member.user.id) member.ban(delete_message_days=7, reason=reason) member.unban(reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_SOFTBAN, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=member.guild_id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.SOFTBAN, reason=reason) @classmethod def ban(cls, plugin, event, member, reason, guild): from rowboat.plugins.modlog import Actions if isinstance(member, (int, long)): user_id = member else: User.from_disco_user(member.user) user_id = member.user.id plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberRemove', 'GuildBanAdd'], user_id=user_id, ) guild.create_ban(user_id, reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_BAN, event.guild.id, user=unicode(member), user_id=user_id, actor=unicode(event.author) if event.author.id != user_id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=guild.id, user_id=user_id, actor_id=event.author.id, type_=cls.Types.BAN, reason=reason) @classmethod def warn(cls, plugin, event, member, reason, guild): from rowboat.plugins.modlog import Actions User.from_disco_user(member.user) user_id = member.user.id cls.create(guild_id=guild.id, user_id=user_id, actor_id=event.author.id, type_=cls.Types.WARNING, reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_WARNED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') @classmethod def mute(cls, plugin, event, member, reason): from rowboat.plugins.modlog import Actions admin_config = cls.admin_config(event) plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberUpdate'], user_id=member.user.id, role_id=admin_config.mute_role, ) member.add_role(admin_config.mute_role, reason=reason) plugin.call('ModLogPlugin.log_action_ext', Actions.MEMBER_MUTED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason') cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.MUTE, reason=reason, metadata={'role': admin_config.mute_role}) @classmethod def tempmute(cls, plugin, event, member, reason, expires_at): from rowboat.plugins.modlog import Actions admin_config = cls.admin_config(event) if not admin_config.mute_role: plugin.log.warning('Cannot tempmute member %s, no tempmute role', member.id) return plugin.call( 'ModLogPlugin.create_debounce', event, ['GuildMemberUpdate'], user_id=member.user.id, role_id=admin_config.mute_role, ) member.add_role(admin_config.mute_role, reason=reason) plugin.call( 'ModLogPlugin.log_action_ext', Actions.MEMBER_TEMP_MUTED, event.guild.id, member=member, actor=unicode(event.author) if event.author.id != member.id else 'Automatic', reason=reason or 'no reason', expires=expires_at, ) cls.create(guild_id=event.guild.id, user_id=member.user.id, actor_id=event.author.id, type_=cls.Types.TEMPMUTE, reason=reason, expires_at=expires_at, metadata={'role': admin_config.mute_role}) @classmethod def clear_active(cls, event, user_id, types): """ Marks a previously active tempmute as inactive for the given event/user. This should be used in all locations where we either think this is no longer active (e.g. the mute role was removed) _or_ when we don't want to unmute the user any longer, e.g. they've been remuted by another command. """ return cls.update(active=False).where( (cls.guild_id == event.guild.id) & (cls.user_id == user_id) & (cls.type_ << types) & (cls.active == 1)).execute() >= 1
import re from holster.enum import Enum from disco.bot.parser import ArgumentSet, ArgumentError from disco.util.functional import cached_property REGEX_FMT = '({})' ARGS_REGEX = '( (.*)$|$)' MENTION_RE = re.compile('<@!?([0-9]+)>') CommandLevels = Enum( DEFAULT=0, TRUSTED=10, MOD=50, ADMIN=100, OWNER=500, ) class CommandEvent(object): """ An event which is created when a command is triggered. Contains information about the message, command, and parsed arguments (along with shortcuts to message information). Attributes --------- command : :class:`Command` The command this event was created for (aka the triggered command). msg : :class:`disco.types.message.Message`
print('WARNING: nacl is not installed, voice support is disabled') from holster.enum import Enum from holster.emitter import Emitter from disco.gateway.encoding.json import JSONEncoder from disco.util.websocket import Websocket from disco.util.logging import LoggingClass from disco.voice.packets import VoiceOPCode from disco.gateway.packets import OPCode VoiceState = Enum( DISCONNECTED=0, AWAITING_ENDPOINT=1, AUTHENTICATING=2, CONNECTING=3, CONNECTED=4, VOICE_CONNECTING=5, VOICE_CONNECTED=6, ) class VoiceException(Exception): def __init__(self, msg, client): self.voice_client = client super(VoiceException, self).__init__(msg) class UDPVoiceClient(LoggingClass): def __init__(self, vc): super(UDPVoiceClient, self).__init__()
class Player(LoggingClass): Events = Enum( 'START_PLAY', 'STOP_PLAY', 'PAUSE_PLAY', 'RESUME_PLAY', 'DISCONNECT', ) def __init__(self, client, queue=None): super(Player, self).__init__() self.client = client self.last_activity = time.time() self.force_kick = False self.already_play = False self.force_return = 1 self.max_returns = 1 self.sleep_time_returns = 3 # replay self.replay = False # Text channel self.text_id = None # Queue contains playable items self.queue = queue or PlayableQueue() # Whether we're playing music (true for lifetime) self.playing = True # Set to an event when playback is paused self.paused = None # Current playing item self.now_playing = None # Last playing item self.then_playing = None # Current play task self.play_task = None # Core task self.run_task = gevent.spawn(self.run) # Event triggered when playback is complete self.complete = gevent.event.Event() # Event emitter for metadata self.events = Emitter() def disconnect(self): self.client.disconnect() self.events.emit(self.Events.DISCONNECT) def skip(self): if self.now_playing and self.now_playing.source: self.now_playing.source.killed() else: print u'source have unknown type???' def pause(self): if self.paused: return self.paused = gevent.event.Event() self.events.emit(self.Events.PAUSE_PLAY) def resume(self): if self.paused: self.paused.set() self.paused = None self.events.emit(self.Events.RESUME_PLAY) def play(self, item): # Grab the first frame before we start anything else, sometimes playables # can do some lengthy async tasks here to setup the playable and we # don't want that lerp the first N frames of the playable into playing # faster frame = item.next_frame() if frame is None: return connected = True start = time.time() loops = 0 if getattr(item.source, "broadcast", True) and getattr( item.source, "embed", None): try: gevent.spawn(self.client.client.api.channels_messages_create, channel=self.text_id, embed=item.source.embed) except Exception as error: print error pass try: print item except Exception as error: print error pass while True: loops += 1 if self.client.state == VoiceState.DISCONNECTED: return if self.client.state != VoiceState.CONNECTED: self.client.state_emitter.once(VoiceState.CONNECTED, timeout=30) # Send the voice frame and increment our timestamp try: self.client.send_frame(frame) self.client.increment_timestamp(item.samples_per_frame) self.client.set_speaking(True) except WebSocketConnectionClosedException as error: connected = False print "WS Error: {}, gid: {}".format( error, self.client.channel.guild_id) self.client.set_state(VoiceState.RECONNECTING) while self.force_return and self.max_returns < 15 and not connected: # and item.source.proc_working: print "gid: {}, try number: {}, connect to WebSocket".format( self.client.channel.guild_id, self.max_returns) self.max_returns += 1 try: #self.client.connect() self.client.state_emitter.once(VoiceState.CONNECTED, timeout=5) self.max_returns = 0 connected = True except Exception as error: print "gid: {}, connect error: {}, sleep... {} sec".format( self.client.channel.guild_id, error, self.sleep_time_returns) gevent.sleep(self.sleep_time_returns) pass if not self.max_returns < 15: self.client.set_state(VoiceState.DISCONNECTED) return # Check proc live if not item.source.proc_working: return # Get next frame = item.next_frame() self.last_activity = time.time() if frame is None: return next_time = start + 0.02 * loops delay = max(0, 0.02 + (next_time - time.time())) item.source.played += delay gevent.sleep(delay) def run(self): while self.playing: self.now_playing = self.queue.get() self.events.emit(self.Events.START_PLAY, self.now_playing) self.play_task = gevent.spawn(self.play, self.now_playing) self.already_play = True self.last_activity = time.time() self.play_task.join() self.events.emit(self.Events.STOP_PLAY, self.now_playing) self.now_playing = None self.already_play = False if self.client.state == VoiceState.DISCONNECTED: self.playing = False self.queue.clear() self.complete.set() self.client.set_speaking(False) self.disconnect()
import json import emoji import requests from collections import defaultdict from holster.enum import Enum from disco.types.message import MessageEmbed from rowboat.plugins import RowboatPlugin as Plugin from rowboat.redis import rdb from rowboat.models.guild import Guild from rowboat.types.plugin import PluginConfig from rowboat.types import SlottedModel, DictField, Field, ChannelField FormatMode = Enum('PLAIN', 'PRETTY') class SubRedditConfig(SlottedModel): channel = Field(ChannelField) mode = Field(FormatMode, default=FormatMode.PRETTY) nsfw = Field(bool, default=False) text_length = Field(int, default=256) include_stats = Field(bool, default=False) class RedditConfig(PluginConfig): # TODO: validate they have less than 3 reddits selected subs = DictField(str, SubRedditConfig) def validate(self):
return 'https://discordapp.com/api/users/{}/avatars/{}.jpg'.format( self.id, self.avatar) @property def mention(self): return '<@{}>'.format(self.id) def __str__(self): return u'{}#{}'.format(self.username, str(self.discriminator).zfill(4)) def __repr__(self): return u'<User {} ({})>'.format(self.id, self) GameType = Enum( DEFAULT=0, STREAMING=1, ) Status = Enum('ONLINE', 'IDLE', 'DND', 'INVISIBLE', 'OFFLINE') class Game(SlottedModel): type = Field(GameType) name = Field(text) url = Field(text) class Presence(SlottedModel): user = Field(User, alias='user') game = Field(Game) status = Field(Status)
from holster.enum import Enum from rowboat.models.message import Message, EMOJI_RE from rowboat.models.user import Infraction from rowboat.plugins import RowboatPlugin as Plugin from rowboat.plugins.censor import URL_RE from rowboat.plugins.modlog import Actions from rowboat.redis import rdb from rowboat.types import SlottedModel, DictField, Field from rowboat.types.plugin import PluginConfig from rowboat.util.leakybucket import LeakyBucket from rowboat.util.stats import timed UPPER_RE = re.compile('[A-Z]') PunishmentType = Enum('NONE', 'MUTE', 'KICK', 'TEMPBAN', 'BAN', 'TEMPMUTE') class CheckConfig(SlottedModel): count = Field(int) interval = Field(int) meta = Field(dict, default=None) punishment = Field(PunishmentType, default=None) punishment_duration = Field(int, default=None) class SubConfig(SlottedModel): max_messages = Field(CheckConfig, default=None) max_mentions = Field(CheckConfig, default=None) max_links = Field(CheckConfig, default=None) max_upper_case = Field(CheckConfig, default=None)
class Guild(BaseModel): WhitelistFlags = Enum('MUSIC', 'MODLOG_CUSTOM_FORMAT', bitmask=False) guild_id = BigIntegerField(primary_key=True) owner_id = BigIntegerField(null=True) name = TextField(null=True) icon = TextField(null=True) splash = TextField(null=True) region = TextField(null=True) last_ban_sync = DateTimeField(null=True) # Rowboat specific data config = BinaryJSONField(null=True) config_raw = BlobField(null=True) enabled = BooleanField(default=True) whitelist = BinaryJSONField(default=[]) added_at = DateTimeField(default=datetime.utcnow) class Meta: db_table = 'guilds' @classmethod def with_id(cls, guild_id): return cls.get(guild_id=guild_id) @classmethod def setup(cls, guild): return cls.create(guild_id=guild.id, owner_id=guild.owner_id, name=guild.name, icon=guild.icon, splash=guild.splash, region=guild.region, config={'web': { str(guild.owner_id): 'admin' }}, config_raw='') def is_whitelisted(self, flag): return int(flag) in self.whitelist def update_config(self, actor_id, raw): from rowboat.types.guild import GuildConfig parsed = yaml.load(raw) GuildConfig(parsed).validate() GuildConfigChange.create(user_id=actor_id, guild_id=self.guild_id, before_raw=self.config_raw, after_raw=raw) self.update( config=parsed, config_raw=raw).where(Guild.guild_id == self.guild_id).execute() self.emit_update() def emit_update(self): rdb.publish( 'actions', json.dumps({ 'type': 'GUILD_UPDATE', 'id': self.guild_id, })) def sync(self, guild): updates = {} for key in ['owner_id', 'name', 'icon', 'splash', 'region']: if getattr(guild, key) != getattr(self, key): updates[key] = getattr(guild, key) if updates: Guild.update(**updates).where( Guild.guild_id == self.guild_id).execute() def get_config(self, refresh=False): from rowboat.types.guild import GuildConfig if refresh: self.config = Guild.select(Guild.config).where( Guild.guild_id == self.guild_id).get().config if refresh or not hasattr(self, '_cached_config'): self._cached_config = GuildConfig(self.config) return self._cached_config def sync_bans(self, guild): # Update last synced time Guild.update(last_ban_sync=datetime.utcnow()).where( Guild.guild_id == self.guild_id).execute() try: bans = guild.get_bans() except: log.exception('sync_bans failed:') return log.info('Syncing %s bans for guild %s', len(bans), guild.id) GuildBan.delete().where((~(GuildBan.user_id << list(bans.keys()))) & (GuildBan.guild_id == guild.id)).execute() for ban in bans.values(): GuildBan.ensure(guild, ban.user, ban.reason)