def create_stream(self, status): log.info('Attempting to create a stream!') with DBManager.create_session_scope(expire_on_commit=False) as db_session: stream_chunk = db_session.query(StreamChunk).filter_by(broadcast_id=status['broadcast_id']).one_or_none() new_stream = False if stream_chunk is not None: stream = stream_chunk.stream else: log.info('checking if there is an active stream already') stream = db_session.query(Stream).filter_by(ended=False).order_by(Stream.stream_start.desc()).first() new_stream = stream is None if new_stream: log.info('No active stream, create new!') stream = Stream(status['created_at'], title=status['title']) db_session.add(stream) db_session.commit() log.info('Successfully added stream!') stream_chunk = StreamChunk(stream, status['broadcast_id'], status['created_at']) db_session.add(stream_chunk) db_session.commit() stream.stream_chunks.append(stream_chunk) log.info('Created stream chunk') self.current_stream = stream self.current_stream_chunk = stream_chunk db_session.expunge_all() if new_stream: HandlerManager.trigger('on_stream_start', stop_on_false=False) log.info('Successfully created a stream')
def on_usernotice(self, source, message, tags): if 'msg-id' not in tags or 'msg-param-months' not in tags: return if tags['msg-id'] == 'resub': num_months = int(tags['msg-param-months']) self.on_resub(source, num_months) HandlerManager.trigger('on_user_resub', source, num_months)
def on_message(self, source, message, emotes, whisper, urls, event): if whisper is False and source.username in self.valid_usernames: # Did twitchnotify tell us about a new sub? m = self.new_sub_regex.search(message) if m: username = m.group(1) with UserManager.get().get_user_context(username) as user: self.on_new_sub(user) HandlerManager.trigger('on_user_sub', user)
def on_usernotice(self, source, message, tags): if 'msg-id' not in tags or 'msg-param-months' not in tags: return if tags['msg-id'] == 'resub': # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead num_months = int(tags['msg-param-months']) self.on_resub(source, num_months) HandlerManager.trigger('on_user_resub', source, num_months)
def commit_all(self): log.info('Commiting all...') for key, manager in self.commitable.items(): log.info('Commiting {0}'.format(key)) manager.commit() log.info('Done with {0}'.format(key)) log.info('ok!') HandlerManager.trigger('on_commit', stop_on_false=False)
def base_paid_timeout(self, bot, source, message, _time, _cost): if message is None or len(message) == 0: return False target = message.split(' ')[0] if len(target) < 2: return False with bot.users.find_context(target) as victim: if victim is None: bot.whisper(source.username, 'This user does not exist FailFish') return False if victim.last_active is None or (datetime.datetime.now() - victim._last_active).total_seconds() > 10 * 60: bot.whisper(source.username, 'This user has not been active in chat within the last 10 minutes.') return False """ if victim == source: bot.whisper(source.username, 'You can\'t timeout yourself FailFish') return False """ if victim.moderator is True: bot.whisper(source.username, 'This person has mod privileges, timeouting this person is not worth it.') return False if victim.level >= self.settings['bypass_level']: bot.whisper(source.username, 'This person\'s user level is too high, you can\'t timeout this person.') return False now = datetime.datetime.now() if victim.timed_out is True and victim.timeout_end > now: victim.timeout_end += datetime.timedelta(seconds=_time) bot.whisper(victim.username, '{victim.username}, you were timed out for an additional {time} seconds by {source.username}'.format( victim=victim, source=source, time=_time)) bot.whisper(source.username, 'You just used {0} points to time out {1} for an additional {2} seconds.'.format(_cost, victim.username, _time)) num_seconds = int((victim.timeout_end - now).total_seconds()) bot._timeout(victim.username, num_seconds, reason='Timed out by {}'.format(source.username_raw)) # songs = session.query(PleblistSong, func.count(PleblistSong.song_info).label('total')).group_by(PleblistSong.youtube_id).order_by('total DESC') else: bot.whisper(source.username, 'You just used {0} points to time out {1} for {2} seconds.'.format(_cost, victim.username, _time)) bot.whisper(victim.username, '{0} just timed you out for {1} seconds. /w {2} !$unbanme to unban yourself for points forsenMoney'.format(source.username, _time, bot.nickname)) bot._timeout(victim.username, _time, reason='Timed out by {}'.format(source.username_raw)) victim.timed_out = True victim.timeout_start = now victim.timeout_end = now + datetime.timedelta(seconds=_time) if self.settings['show_on_clr']: payload = {'user': source.username, 'victim': victim.username} bot.websocket_manager.emit('timeout', payload) HandlerManager.trigger('on_paid_timeout', source, victim, _cost, stop_on_false=False)
def parse_message(self, msg_raw, source, event, tags={}, whisper=False): msg_lower = msg_raw.lower() emote_tag = None for tag in tags: if tag['key'] == 'subscriber' and event.target == self.channel: source.subscriber = tag['value'] == '1' elif tag['key'] == 'emotes' and tag['value']: emote_tag = tag['value'] elif tag['key'] == 'display-name' and tag['value']: source.username_raw = tag['value'] elif tag['key'] == 'user-type': source.moderator = tag['value'] == 'mod' or source.username == self.streamer # source.num_lines += 1 if source is None: log.error('No valid user passed to parse_message') return False if source.banned: self.ban(source.username) return False # If a user types when timed out, we assume he's been unbanned for a good reason and remove his flag. if source.timed_out is True: source.timed_out = False # Parse emotes in the message message_emotes = self.emotes.parse_message_twitch_emotes(source, msg_raw, emote_tag, whisper) urls = self.find_unique_urls(msg_raw) log.debug('{2}{0}: {1}'.format(source.username, msg_raw, '<w>' if whisper else '')) res = HandlerManager.trigger('on_message', source, msg_raw, message_emotes, whisper, urls, event, stop_on_false=True) if res is False: return False source.last_seen = datetime.datetime.now() source.last_active = datetime.datetime.now() if source.ignored: return False if msg_lower[:1] == '!': msg_lower_parts = msg_lower.split(' ') trigger = msg_lower_parts[0][1:] msg_raw_parts = msg_raw.split(' ') remaining_message = ' '.join(msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if trigger in self.commands: command = self.commands[trigger] extra_args = { 'emotes': message_emotes, 'trigger': trigger, } command.run(self, source, remaining_message, event=event, args=extra_args, whisper=whisper)
def on_usernotice(self, chatconn, event): # We use .lower() in case twitch ever starts sending non-lowercased usernames tags = {} for d in event.tags: tags[d['key']] = d['value'] if 'login' not in tags: return username = tags['login'] with self.users.get_user_context(username) as source: msg = '' if len(event.arguments) > 0: msg = event.arguments[0] HandlerManager.trigger('on_usernotice', source, msg, tags)
def go_offline(self): with DBManager.create_session_scope(expire_on_commit=False) as db_session: self.current_stream.ended = True self.current_stream.stream_end = self.first_offline self.current_stream_chunk.chunk_end = self.first_offline db_session.add(self.current_stream) db_session.add(self.current_stream_chunk) db_session.commit() db_session.expunge_all() self.last_stream = self.current_stream self.current_stream = None self.current_stream_chunk = None HandlerManager.trigger('on_stream_stop', stop_on_false=False)
def end_raffle(self): if not self.raffle_running: return False self.raffle_running = False if len(self.raffle_users) == 0: self.bot.me('Wow, no one joined the raffle DansGame') return False winner = random.choice(self.raffle_users) self.raffle_users = [] if self.settings['show_on_clr']: self.bot.websocket_manager.emit('notification', {'message': '{} won {} points in the raffle!'.format(winner.username_raw, self.raffle_points)}) self.bot.me('The raffle has finished! {0} won {1} points! PogChamp'.format(winner.username_raw, self.raffle_points)) winner.points += self.raffle_points winner.save() HandlerManager.trigger('on_raffle_win', winner, self.raffle_points)
def create_stream(self, status): log.info("Attempting to create a stream!") with DBManager.create_session_scope( expire_on_commit=False) as db_session: stream_chunk = db_session.query(StreamChunk).filter_by( broadcast_id=status["broadcast_id"]).one_or_none() new_stream = False if stream_chunk is not None: stream = stream_chunk.stream else: log.info("checking if there is an active stream already") stream = db_session.query(Stream).filter_by( ended=False).order_by(Stream.stream_start.desc()).first() new_stream = stream is None if new_stream: log.info("No active stream, create new!") stream = Stream(status["created_at"], title=status["title"]) db_session.add(stream) db_session.commit() log.info("Successfully added stream!") stream_chunk = StreamChunk(stream, status["broadcast_id"], status["created_at"]) db_session.add(stream_chunk) db_session.commit() stream.stream_chunks.append(stream_chunk) log.info("Created stream chunk") self.current_stream = stream self.current_stream_chunk = stream_chunk db_session.expunge_all() if new_stream: HandlerManager.trigger("on_stream_start", stop_on_false=False) log.info("Successfully created a stream")
def on_usernotice(self, chatconn, event): tags = { tag["key"]: tag["value"] if tag["value"] is not None else "" for tag in event.tags } id = tags["user-id"] login = tags["login"] name = tags["display-name"] with DBManager.create_session_scope( expire_on_commit=False) as db_session: source = User.from_basics(db_session, UserBasics(id, login, name)) if event.arguments and len(event.arguments) > 0: msg = event.arguments[0] else: msg = None # e.g. user didn't type an extra message to share with the streamer HandlerManager.trigger("on_usernotice", source=source, message=msg, tags=tags) if msg is not None: self.parse_message(msg, source, event, tags)
def on_pubmsg(self, chatconn, event): if event.source.user == self.nickname: return False username = event.source.user.lower() # We use .lower() in case twitch ever starts sending non-lowercased usernames with self.users.get_user_context(username) as source: res = HandlerManager.trigger('on_pubmsg', source, event.arguments[0], stop_on_false=True) if res is False: return False self.parse_message(event.arguments[0], source, event, tags=event.tags)
def on_usernotice(self, source, message, tags): if 'msg-id' not in tags: return if tags['msg-id'] == 'resub': if 'msg-param-months' not in tags: log.debug('subalert msg-id is resub, but missing msg-param-months: {}'.format(tags)) return if 'msg-param-sub-plan' not in tags: log.debug('subalert msg-id is resub, but missing msg-param-sub-plan: {}'.format(tags)) return # log.debug('msg-id resub tags: {}'.format(tags)) # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead num_months = int(tags['msg-param-months']) self.on_resub(source, num_months, tags['msg-param-sub-plan']) HandlerManager.trigger('on_user_resub', source, num_months) elif tags['msg-id'] == 'subgift': if 'msg-param-months' not in tags: log.debug('subalert msg-id is subgift, but missing msg-param-months: {}'.format(tags)) return if 'display-name' not in tags: log.debug('subalert msg-id is subgift, but missing display-name: {}'.format(tags)) return num_months = int(tags['msg-param-months']) with self.bot.users.get_user_context(tags['msg-param-recipient-user-name']) as receiver: if num_months > 1: # Resub self.on_resub(receiver, num_months, tags['msg-param-sub-plan'], tags['display-name']) HandlerManager.trigger('on_user_resub', receiver, num_months) else: # New sub self.on_new_sub(receiver, tags['msg-param-sub-plan'], tags['display-name']) HandlerManager.trigger('on_user_sub', receiver) elif tags['msg-id'] == 'sub': if 'msg-param-sub-plan' not in tags: log.debug('subalert msg-id is sub, but missing msg-param-sub-plan: {}'.format(tags)) return self.on_new_sub(source, tags['msg-param-sub-plan']) HandlerManager.trigger('on_user_sub', source) else: log.debug('Unhandled msg-id: {}'.format(tags['msg-id']))
def on_pubmsg(self, chatconn, event): tags = {tag["key"]: tag["value"] if tag["value"] is not None else "" for tag in event.tags} id = tags["user-id"] login = event.source.user name = tags["display-name"] if event.source.user == self.nickname: return False if self.streamer == "forsen": if "zonothene" in login: self._ban(login) return True raw_m = event.arguments[0].lower() if raw_m.startswith("!lastseen forsen"): if len(raw_m) > len("!lastseen forsen2"): if raw_m[16] == " ": return True else: return True if raw_m.startswith("!lastseen @forsen"): if len(raw_m) > len("!lastseen @forsen2"): if raw_m[17] == " ": return True else: return True if self.streamer == "nymn": if "hades_k" in login: self.timeout_login(login, 3600, reason="Bad username") return True if "hades_b" in login: self.timeout_login(login, 3600, reason="Bad username") return True with DBManager.create_session_scope(expire_on_commit=False) as db_session: source = User.from_basics(db_session, UserBasics(id, login, name)) with new_message_processing_scope(self): res = HandlerManager.trigger("on_pubmsg", source=source, message=event.arguments[0], tags=tags) if res is False: return False self.parse_message(event.arguments[0], source, event, tags=tags)
def on_pubmsg(self, chatconn, event): tags = { tag["key"]: tag["value"] if tag["value"] is not None else "" for tag in event.tags } id = tags["user-id"] login = event.source.user name = tags["display-name"] if event.source.user == self.nickname: return False with DBManager.create_session_scope( expire_on_commit=False) as db_session: source = User.from_basics(db_session, UserBasics(id, login, name)) res = HandlerManager.trigger("on_pubmsg", source=source, message=event.arguments[0]) if res is False: return False self.parse_message(event.arguments[0], source, event, tags=tags)
def onMessage(self, payload, isBinary): if isBinary: log.info(f"Binary message received: {len(payload)} bytes") else: log.info( f"Text message received: {payload.decode('utf8')}") if not self.websocket_origin: parsedPayload = json.loads(payload) if parsedPayload["event"] == "open_bets": HandlerManager.trigger("on_open_bets") elif parsedPayload["event"] == "lock_bets": HandlerManager.trigger("on_lock_bets") elif parsedPayload["event"] == "end_bets": HandlerManager.trigger( "on_end_bets", winning_team=parsedPayload["data"] ["winning_team"], player_team=parsedPayload["data"] ["player_team"], ) else: for client in WebSocketServer.clients: client.sendMessage(payload, False)
def roulette(self, **options): if self.settings['only_roulette_after_sub']: if self.last_sub is None: return False if datetime.datetime.now() - self.last_sub > datetime.timedelta( seconds=self.settings['after_sub_roulette_time']): return False message = options['message'] user = options['source'] bot = options['bot'] if message is None: bot.whisper( user.username, 'I didn\'t recognize your bet! Usage: !roulette 150 to bet 150 points' ) return False msg_split = message.split(' ') try: bet = pajbot.utils.parse_points_amount(user, msg_split[0]) except pajbot.exc.InvalidPointAmount as e: bot.whisper(user.username, str(e)) return False if not user.can_afford(bet): bot.whisper( user.username, 'You don\'t have enough points to do a roulette for {} points :(' .format(bet)) return False if bet < self.settings['min_roulette_amount']: bot.whisper( user.username, 'You have to bet at least {} point! :('.format( self.settings['min_roulette_amount'])) return False # Calculating the result result = self.rigged_random_result() points = bet if result else -bet user.points += points with DBManager.create_session_scope() as db_session: r = Roulette(user.id, points) db_session.add(r) arguments = { 'bet': bet, 'user': user.username_raw, 'points': user.points_available() } if points > 0: out_message = self.get_phrase('message_won', **arguments) else: out_message = self.get_phrase('message_lost', **arguments) if self.settings['options_output'] == '1. Show results in chat': bot.me(out_message) if self.settings['options_output'] == '2. Show results in whispers': bot.whisper(user.username, out_message) if self.settings[ 'options_output'] == '3. Show results in chat if it\'s over X points else it will be whispered.': if abs(points) >= self.settings['min_show_points']: bot.me(out_message) else: bot.whisper(user.username, out_message) HandlerManager.trigger('on_roulette_finish', user, points)
def on_usernotice(self, source, message, tags): if 'msg-id' not in tags: return if tags['msg-id'] == 'resub': num_months = -1 substreak_count = 0 if 'msg-param-months' in tags: num_months = int(tags['msg-param-months']) if 'msg-param-cumulative-months' in tags: num_months = int(tags['msg-param-cumulative-months']) if 'msg-param-streak-months' in tags: substreak_count = int(tags['msg-param-streak-months']) if 'msg-param-should-share-streak' in tags: should_share = bool(tags['msg-param-should-share-streak']) if not should_share: substreak_count = 0 if 'msg-param-sub-plan' not in tags: log.debug('subalert msg-id is resub, but missing msg-param-sub-plan: {}'.format(tags)) return # log.debug('msg-id resub tags: {}'.format(tags)) # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead self.on_resub(source, num_months, tags['msg-param-sub-plan'], None, substreak_count) HandlerManager.trigger('on_user_resub', source, num_months) elif tags['msg-id'] == 'subgift': num_months = 0 substreak_count = 0 if 'msg-param-months' in tags: num_months = int(tags['msg-param-months']) if 'msg-param-cumulative-months' in tags: num_months = int(tags['msg-param-cumulative-months']) if 'msg-param-streak-months' in tags: substreak_count = int(tags['msg-param-streak-months']) if 'msg-param-should-share-streak' in tags: should_share = bool(tags['msg-param-should-share-streak']) if not should_share: substreak_count = 0 if 'display-name' not in tags: log.debug('subalert msg-id is subgift, but missing display-name: {}'.format(tags)) return with self.bot.users.get_user_context(tags['msg-param-recipient-user-name']) as receiver: if num_months > 1: # Resub self.on_resub(receiver, num_months, tags['msg-param-sub-plan'], tags['display-name'], substreak_count) HandlerManager.trigger('on_user_resub', receiver, num_months) else: # New sub self.on_new_sub(receiver, tags['msg-param-sub-plan'], tags['display-name']) HandlerManager.trigger('on_user_sub', receiver) elif tags['msg-id'] == 'sub': if 'msg-param-sub-plan' not in tags: log.debug('subalert msg-id is sub, but missing msg-param-sub-plan: {}'.format(tags)) return self.on_new_sub(source, tags['msg-param-sub-plan']) HandlerManager.trigger('on_user_sub', source) else: log.debug('Unhandled msg-id: {} - tags: {}'.format(tags['msg-id'], tags))
def __init__(self, config, args=None): # Load various configuration variables from the given config object # The config object that should be passed through should # come from pajbot.utils.load_config self.load_config(config) log.debug("Loaded config") # streamer is additionally initialized here so streamer can be accessed by the DB migrations # before StreamHelper.init_bot() is called later (which depends on an upgraded DB because # StreamManager accesses the DB) StreamHelper.init_streamer(self.streamer) # Update the database (and partially redis) scheme if necessary using alembic # In case of errors, i.e. if the database is out of sync or the alembic # binary can't be called, we will shut down the bot. pajbot.utils.alembic_upgrade() log.debug("ran db upgrade") # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = pajbot.utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() twitch_client_id = None twitch_oauth = None if "twitchapi" in self.config: twitch_client_id = self.config["twitchapi"].get("client_id", None) twitch_oauth = self.config["twitchapi"].get("oauth", None) # A client ID is required for the bot to work properly now, give an error for now if twitch_client_id is None: log.error( 'MISSING CLIENT ID, SET "client_id" VALUE UNDER [twitchapi] SECTION IN CONFIG FILE' ) self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.emote_manager = EmoteManager(twitch_client_id) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager(socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() HandlerManager.trigger("on_managers_loaded") # Reloadable managers self.reloadable = {} # Commitable managers self.commitable = { "commands": self.commands, "banphrases": self.banphrase_manager } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) try: self.admin = self.config["main"]["admin"] except KeyError: log.warning( "No admin user specified. See the [main] section in config.example.ini for its usage." ) if self.admin: with self.users.get_user_context(self.admin) as user: user.level = 2000 self.parse_version() relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is None or relay_password is None: self.irc = MultiIRCManager(self) else: self.irc = SingleIRCManager(self) self.reactor.add_global_handler("all_events", self.irc._dispatcher, -10) self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data["broadcaster"] = self.streamer self.data["version"] = self.version self.data["version_brief"] = self.version_brief self.data["bot_name"] = self.nickname self.data_cb["status_length"] = self.c_status_length self.data_cb["stream_status"] = self.c_stream_status self.data_cb["bot_uptime"] = self.c_uptime self.data_cb["current_time"] = self.c_current_time self.silent = True if args.silent else self.silent if self.silent: log.info("Silent mode enabled") """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) try: if self.config["twitchapi"]["update_subscribers"] == "1": self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass
def accept_duel(self, bot, source, **rest): """ Accepts any active duel requests you've received. How to use: !accept """ if source.id not in self.duel_targets: bot.whisper(source, "You are not being challenged to a duel by anyone.") return with DBManager.create_session_scope() as db_session: requestor = User.find_by_id(db_session, self.duel_targets[source.id]) duel_price = self.duel_request_price[self.duel_targets[source.id]] if not source.can_afford(duel_price) or not requestor.can_afford( duel_price): bot.whisper( source, f"Your duel request with {requestor} was cancelled due to one of you not having enough points.", ) bot.whisper( requestor, f"Your duel request with {source} was cancelled due to one of you not having enough points.", ) del self.duel_requests[requestor.id] del self.duel_request_price[requestor.id] del self.duel_begin_time[requestor.id] del self.duel_targets[source.id] return False source.points -= duel_price requestor.points -= duel_price participants = [source, requestor] winner = random.choice(participants) participants.remove(winner) loser = participants.pop() winner.points += duel_price * 2 # Persist duel statistics winner.duel_stats.won(duel_price) loser.duel_stats.lost(duel_price) arguments = { "winner": winner.name, "loser": loser.name, "total_pot": duel_price, "extra_points": duel_price, } if duel_price > 0: message = self.get_phrase("message_won_points", **arguments) if duel_price >= 500 and self.settings["show_on_clr"]: bot.websocket_manager.emit( "notification", {"message": f"{winner} won the duel vs {loser}"}) else: message = self.get_phrase("message_won", **arguments) bot.say(message) del self.duel_requests[requestor.id] del self.duel_request_price[requestor.id] del self.duel_begin_time[requestor.id] del self.duel_targets[source.id] HandlerManager.trigger("on_duel_complete", winner=winner, loser=loser, points_won=duel_price, points_bet=duel_price)
def pull(self, bot, source, message, **rest): if message is None: bot.whisper( source, "I didn't recognize your bet! Usage: !slotmachine 150 to bet 150 points" ) return False low_tier_emotes = self.settings["low_tier_emotes"].split() high_tier_emotes = self.settings["high_tier_emotes"].split() if len(low_tier_emotes) == 0 or len(high_tier_emotes) == 0: return False msg_split = message.split(" ") try: bet = pajbot.utils.parse_points_amount(source, msg_split[0]) except pajbot.exc.InvalidPointAmount as e: bot.whisper(source, str(e)) return False if not source.can_afford(bet): bot.whisper( source, f"You don't have enough points to do a slot machine pull for {bet} points :(" ) return False if bet < self.settings["min_bet"]: bot.whisper( source, f"You have to bet at least {self.settings['min_bet']} point! :(" ) return False # how much of the users point they're expected to get back (basically how much the house yoinks) expected_return = 1.0 ltsw = self.settings["ltsw"] / 100.0 htsw = self.settings["htsw"] / 100.0 ltbw = self.settings["ltbw"] / 100.0 htbw = self.settings["htbw"] / 100.0 bet_return, randomized_emotes = pull_lol(low_tier_emotes, high_tier_emotes, bet, expected_return, ltsw, htsw, ltbw, htbw) # Calculating the result if bet_return <= 0.0: points = -bet else: points = bet * bet_return source.points += points arguments = { "bet": bet, "result": points, "user": source.name, "points": source.points, "win": points > 0, "emotes": " ".join(randomized_emotes), } if points > 0: out_message = self.get_phrase("message_won", **arguments) else: out_message = self.get_phrase("message_lost", **arguments) if self.settings["options_output"] == "4. Combine output in chat": if bot.is_online: self.add_message(bot, arguments) else: bot.me(out_message) if self.settings["options_output"] == "1. Show results in chat": bot.me(out_message) if self.settings["options_output"] == "2. Show results in whispers": bot.whisper(source, out_message) if (self.settings["options_output"] == "3. Show results in chat if it's over X points else it will be whispered." ): if abs(points) >= self.settings["min_show_points"]: bot.me(out_message) else: bot.whisper(source, out_message) HandlerManager.trigger("on_slot_machine_finish", user=source, points=points)
def poll_trackobot_stage2(self, game_data): latest_game = game_data['history'][0] if latest_game['id'] != self.last_game_id: # A new game has been detected # Reset all variables winners = [] losers = [] total_winning_points = 0 total_losing_points = 0 points_bet = { 'win': 0, 'loss': 0, } bet_game_id = None # Mark down the last game's results with DBManager.create_session_scope() as db_session: bet_game = HSBetGame(latest_game['id'], latest_game['result']) db_session.add(bet_game) db_session.flush() bet_game_id = bet_game.id db_bets = {} for username in self.bets: bet_for_win, points = self.bets[username] """ self.bot.me('{} bet {} points on the last game to end up as a {}'.format( username, points, 'win' if bet_for_win else 'loss')) """ user = self.bot.users.find(username, db_session=db_session) if user is None: continue correct_bet = (latest_game['result'] == 'win' and bet_for_win is True) or (latest_game['result'] == 'loss' and bet_for_win is False) points_bet['win' if bet_for_win else 'loss'] += points db_bets[username] = HSBetBet(bet_game_id, user.id, 'win' if bet_for_win else 'loss', points, 0) if correct_bet: winners.append((user, points)) total_winning_points += points user.remove_debt(points) else: losers.append((user, points)) total_losing_points += points user.pay_debt(points) db_bets[username].profit = -points self.bot.whisper(user.username, 'You bet {} points on the wrong outcome, so you lost it all. :('.format( points)) for obj in losers: user, points = obj user.save() log.debug('{} lost {} points!'.format(user, points)) for obj in winners: points_reward = 0 user, points = obj if points == 0: # If you didn't bet any points, you don't get a part of the cut. HandlerManager.trigger('on_user_win_hs_bet', user, points_reward) continue pot_cut = points / total_winning_points points_reward = int(pot_cut * total_losing_points) db_bets[user.username].profit = points_reward user.points += points_reward user.save() HandlerManager.trigger('on_user_win_hs_bet', user, points_reward) self.bot.whisper(user.username, 'You bet {} points on the right outcome, that rewards you with a profit of {} points! (Your bet was {:.2f}% of the total pool)'.format( points, points_reward, pot_cut * 100)) """ self.bot.me('{} bet {} points, and made a profit of {} points by correctly betting on the HS game!'.format( user.username_raw, points, points_reward)) """ for username in db_bets: bet = db_bets[username] db_session.add(bet) self.bot.me('A new game has begun! Vote with !hsbet win/lose POINTS') self.bets = {} self.last_game_id = latest_game['id'] self.last_game_start = datetime.datetime.now() + datetime.timedelta(seconds=self.settings['time_until_bet_closes']) payload = { 'time_left': self.settings['time_until_bet_closes'], 'win': 0, 'loss': 0, } self.bot.websocket_manager.emit('hsbet_new_game', data=payload) # stats about the game ratio = 0.0 try: ratio = (total_losing_points / total_winning_points) * 100.0 except: pass self.bot.me('The game ended as a {result}. {points_bet[win]} points bet on win, {points_bet[loss]} points bet on loss. Winners can expect a {ratio:.2f}% return on their bet points.'.format(ratio=ratio, result=latest_game['result'], points_bet=points_bet)) redis = RedisManager.get() redis.set('{streamer}:last_hsbet_game_id'.format(streamer=StreamHelper.get_streamer()), self.last_game_id) redis.set('{streamer}:last_hsbet_game_start'.format(streamer=StreamHelper.get_streamer()), self.last_game_start.timestamp())
def do_tick(): HandlerManager.trigger("on_tick")
def __init__(self, config, args): self.config = config self.args = args ScheduleManager.init() DBManager.init(self.config["main"]["db"]) # redis redis_options = {} if "redis" in config: redis_options = dict(config.items("redis")) RedisManager.init(**redis_options) utils.wait_for_redis_data_loaded(RedisManager.get()) self.nickname = config["main"].get("nickname", "pajbot") if config["main"].getboolean("verified", False): self.tmi_rate_limits = TMIRateLimits.VERIFIED elif config["main"].getboolean("known", False): self.tmi_rate_limits = TMIRateLimits.KNOWN else: self.tmi_rate_limits = TMIRateLimits.BASE self.whisper_output_mode = WhisperOutputMode.from_config_value( config["main"].get("whisper_output_mode", "normal") ) # phrases self.phrases = { "welcome": ["{nickname} {version} running! HeyGuys"], "quit": ["{nickname} {version} shutting down... BibleThump"], } if "phrases" in config: phrases = config["phrases"] if "welcome" in phrases: self.phrases["welcome"] = phrases["welcome"].splitlines() if "quit" in phrases: self.phrases["quit"] = phrases["quit"].splitlines() # Remembers whether the "welcome" phrases have already been said. We don't want to send the # welcome messages to chat again on a reconnect. self.welcome_messages_sent = False # streamer if "streamer" in config["main"]: self.streamer = config["main"]["streamer"] self.channel = "#" + self.streamer elif "target" in config["main"]: self.channel = config["main"]["target"] self.streamer = self.channel[1:] self.bot_domain = self.config["web"]["domain"] self.streamer_display = self.config["web"]["streamer_name"] log.debug("Loaded config") # do this earlier since schema upgrade can depend on the helix api self.api_client_credentials = ClientCredentials( self.config["twitchapi"]["client_id"], self.config["twitchapi"]["client_secret"], self.config["twitchapi"]["redirect_uri"], ) self.twitch_id_api = TwitchIDAPI(self.api_client_credentials) self.twitch_tmi_api = TwitchTMIAPI() self.app_token_manager = AppAccessTokenManager(self.twitch_id_api, RedisManager.get()) self.twitch_helix_api = TwitchHelixAPI(RedisManager.get(), self.app_token_manager) self.twitch_v5_api = TwitchKrakenV5API(self.api_client_credentials, RedisManager.get()) self.bot_user_id = self.twitch_helix_api.get_user_id(self.nickname) if self.bot_user_id is None: raise ValueError("The bot login name you entered under [main] does not exist on twitch.") self.streamer_user_id = self.twitch_helix_api.get_user_id(self.streamer) if self.streamer_user_id is None: raise ValueError("The streamer login name you entered under [main] does not exist on twitch.") self.streamer_access_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.streamer, user_id=self.streamer_user_id ) StreamHelper.init_streamer(self.streamer, self.streamer_user_id, self.streamer_display) # SQL migrations with DBManager.create_dbapi_connection_scope() as sql_conn: sql_migratable = DatabaseMigratable(sql_conn) sql_migration = Migration(sql_migratable, pajbot.migration_revisions.db, self) sql_migration.run() # Redis migrations redis_migratable = RedisMigratable(redis_options=redis_options, namespace=self.streamer) redis_migration = Migration(redis_migratable, pajbot.migration_revisions.redis, self) redis_migration.run() # Thread pool executor for async actions self.action_queue = ActionQueue() # refresh points_rank and num_lines_rank regularly UserRanksRefreshManager.start(self.action_queue) self.reactor = irc.client.Reactor() # SafeDefaultScheduler makes the bot not exit on exception in the main thread # e.g. on actions via bot.execute_now, etc. self.reactor.scheduler_class = SafeDefaultScheduler self.reactor.scheduler = SafeDefaultScheduler() self.start_time = utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer, self.execute_now) self.stream_manager = StreamManager(self) StreamHelper.init_stream_manager(self.stream_manager) self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() # bot access token if "password" in self.config["main"]: log.warning( "DEPRECATED - Using bot password/oauth token from file. " "You should authenticate in web gui using route /bot_login " "and remove password from config file" ) access_token = self.config["main"]["password"] if access_token.startswith("oauth:"): access_token = access_token[6:] self.bot_token_manager = UserAccessTokenManager( api=None, redis=None, username=self.nickname, user_id=self.bot_user_id, token=UserAccessToken.from_implicit_auth_flow_token(access_token), ) else: self.bot_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.nickname, user_id=self.bot_user_id ) self.emote_manager = EmoteManager(self.twitch_v5_api, self.action_queue) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() if "twitter" in self.config and self.config["twitter"].get("streaming_type", "twitter") == "tweet-provider": self.twitter_manager = PBTwitterManager(self) else: self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager( socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self ).load() self.websocket_manager = WebSocketManager(self) HandlerManager.trigger("on_managers_loaded") # Commitable managers self.commitable = {"commands": self.commands, "banphrases": self.banphrase_manager} self.execute_every(60, self.commit_all) self.execute_every(1, self.do_tick) # promote the admin to level 2000 self.admin = self.config["main"].get("admin", None) if self.admin is None: log.warning("No admin user specified. See the [main] section in the example config for its usage.") else: with DBManager.create_session_scope() as db_session: admin_user = User.find_or_create_from_login(db_session, self.twitch_helix_api, self.admin) if admin_user is None: log.warning( "The login name you entered for the admin user does not exist on twitch. " "No admin user has been created." ) else: admin_user.level = 2000 # silent mode self.silent = ( "flags" in config and "silent" in config["flags"] and config["flags"]["silent"] == "1" ) or args.silent if self.silent: log.info("Silent mode enabled") # dev mode self.dev = "flags" in config and "dev" in config["flags"] and config["flags"]["dev"] == "1" if self.dev: self.version_long = utils.extend_version_if_possible(VERSION) else: self.version_long = VERSION self.irc = IRCManager(self) relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is not None or relay_password is not None: log.warning( "DEPRECATED - Relaybroker support is no longer implemented. relay_host and relay_password are ignored" ) self.data = { "broadcaster": self.streamer, "version": self.version_long, "version_brief": VERSION, "bot_name": self.nickname, "bot_domain": self.bot_domain, "streamer_display": self.streamer_display, } self.data_cb = { "status_length": self.c_status_length, "stream_status": self.c_stream_status, "bot_uptime": self.c_uptime, "current_time": self.c_current_time, "molly_age_in_years": self.c_molly_age_in_years, } self.user_agent = f"pajbot1/{VERSION} ({self.nickname})"
def multi_end_raffle(self): if not self.raffle_running: return False self.raffle_running = False if len(self.raffle_users) == 0: self.bot.me('Wow, no one joined the raffle DansGame') return False # Shuffle the list of participants random.shuffle(self.raffle_users) num_participants = len(self.raffle_users) abs_points = abs(self.raffle_points) max_winners = min(num_participants, 200) min_point_award = 100 negative = self.raffle_points < 0 # Decide how we should pick the winners log.info('Num participants: {}'.format(num_participants)) for winner_percentage in [x * 0.01 for x in range(1, 26)]: log.info('Winner percentage: {}'.format(winner_percentage)) num_winners = math.ceil(num_participants * winner_percentage) points_per_user = math.ceil(abs_points / num_winners) log.info('nw: {}, ppu: {}'.format(num_winners, points_per_user)) if num_winners > max_winners: num_winners = max_winners points_per_user = math.ceil(abs_points / num_winners) break elif points_per_user < min_point_award: num_winners = max( 1, min(math.floor(abs_points / min_point_award), num_participants)) points_per_user = math.ceil(abs_points / num_winners) break log.info('k done. got {} winners'.format(num_winners)) winners = self.raffle_users[:num_winners] self.raffle_users = [] if negative: points_per_user *= -1 self.bot.me( 'The multi-raffle has finished! {0} users won {1} points each! PogChamp' .format(len(winners), points_per_user)) winners_arr = [] for winner in winners: winner.points += points_per_user winners_arr.append(winner) winners_str = generate_winner_list(winners_arr) if len(winners_str) > 300: self.bot.me('{} won {} points each!'.format( winners_str, points_per_user)) winners_arr = [] winner.save() if len(winners_arr) > 0: winners_str = generate_winner_list(winners_arr) self.bot.me('{} won {} points each!'.format( winners_str, points_per_user)) HandlerManager.trigger('on_multiraffle_win', winners, points_per_user)
def parse_message(self, msg_raw, source, event, tags={}, whisper=False): msg_lower = msg_raw.lower() emote_tag = None for tag in tags: if tag['key'] == 'subscriber' and event.target == self.channel: source.subscriber = tag['value'] == '1' elif tag['key'] == 'emotes' and tag['value']: emote_tag = tag['value'] elif tag['key'] == 'display-name' and tag['value']: source.username_raw = tag['value'] elif tag['key'] == 'user-type': source.moderator = tag['value'] == 'mod' or source.username == self.streamer # source.num_lines += 1 if source is None: log.error('No valid user passed to parse_message') return False if source.banned: self.ban(source.username) return False # If a user types when timed out, we assume he's been unbanned for a good reason and remove his flag. if source.timed_out is True: source.timed_out = False # Parse emotes in the message message_emotes = self.emotes.parse_message_twitch_emotes(source, msg_raw, emote_tag, whisper) urls = self.find_unique_urls(msg_raw) if whisper: self.whisper('datguy1', '{} said: {}'.format(source.username, msg_raw)) # log.debug('{2}{0}: {1}'.format(source.username, msg_raw, '<w>' if whisper else '')) res = HandlerManager.trigger('on_message', source, msg_raw, message_emotes, whisper, urls, event, stop_on_false=True) if res is False: return False source.last_seen = datetime.datetime.now() source.last_active = datetime.datetime.now() if source.ignored: return False if msg_lower[:1] == '!': msg_lower_parts = msg_lower.split(' ') trigger = msg_lower_parts[0][1:] msg_raw_parts = msg_raw.split(' ') remaining_message = ' '.join(msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if trigger in self.commands: command = self.commands[trigger] extra_args = { 'emotes': message_emotes, 'trigger': trigger, } command.run(self, source, remaining_message, event=event, args=extra_args, whisper=whisper)
def base_paid_timeout(self, bot, source, message, _time, _cost): if message is None or len(message) == 0: return False target = message.split(" ")[0] if len(target) < 2: return False with DBManager.create_session_scope() as db_session: victim = User.find_by_user_input(db_session, target) if victim is None: bot.whisper(source, "This user does not exist FailFish") return False if victim == source: # bot.whisper(source.username, 'You can\'t timeout yourself FailFish') source.points += 2 return True if victim.last_active is None or ( utils.now() - victim.last_active) > datetime.timedelta(minutes=10): bot.whisper( source, "This user has not been active in chat within the last 10 minutes." ) return False if victim.moderator is True: bot.whisper( source, "This person has mod privileges, timeouting this person is not worth it." ) return False if victim.level >= self.settings["bypass_level"]: bot.whisper( source, "This person's user level is too high, you can't timeout this person." ) return False if not victim.subscriber and source.subscriber: _cost = int(_cost * 0.8) if not source.can_afford(_cost): bot.whisper( source.username, "You need {} more points to be able to timeout {}".format( source.points - _cost, victim.username_raw), ) return False if source.username != "datguy1": source.points -= _cost now = utils.now() if victim.timeout_end is not None and victim.timeout_end > now: victim.timeout_end += datetime.timedelta(seconds=_time) bot.whisper( victim, f"{victim}, you were timed out for an additional {_time} seconds by {source}" ) bot.whisper( source, f"You just used {_cost} points to time out {victim} for an additional {_time} seconds." ) num_seconds = int((victim.timeout_end - now).total_seconds()) bot.timeout(victim, num_seconds, reason=f"Timed out by {source}") else: bot.whisper( source, f"You just used {_cost} points to time out {victim} for {_time} seconds." ) bot.whisper( victim, f"{source} just timed you out for {_time} seconds.") bot.timeout(victim, _time, reason=f"Timed out by {source} for {_cost} points") bot.say( f"{source} timed out {victim} with !timeout for {_cost} points!" ) victim.timeout_end = now + datetime.timedelta(seconds=_time) if self.settings["show_on_clr"]: payload = {"user": source.name, "victim": victim.name} bot.websocket_manager.emit("timeout", payload) HandlerManager.trigger("on_paid_timeout", source=source, victim=victim, cost=_cost, stop_on_false=False)
def on_pubmsg(self, chatconn, event): if event.source.user == self.nickname: return False username = event.source.user.lower() if self.streamer == 'forsen': if 'zonothene' in username: self._ban(username) return True raw_m = event.arguments[0].lower() if raw_m.startswith('!lastseen forsen'): if len(raw_m) > len('!lastseen forsen2'): if raw_m[16] == ' ': return True else: return True if raw_m.startswith('!lastseen @forsen'): if len(raw_m) > len('!lastseen @forsen2'): if raw_m[17] == ' ': return True else: return True if self.streamer == 'nymn': if 'hades_k' in username: self._timeout(username, 3600) return True if 'hades_b' in username: self._timeout(username, 3600) return True raw_m = event.arguments[0] m = ''.join(sorted(set(raw_m), key=raw_m.index)) m = ''.join(ch for ch in m if ch.isalnum()) if 'niqers' in m: self.timeout(username, 600) return True if 'niqe3rs' in m: self.timeout(username, 600) return True if 'niq3ers' in m: self.timeout(username, 600) return True if 'niqurs' in m: self.timeout(username, 600) return True if 'nigurs' in m: self.timeout(username, 600) return True if 'nige3rs' in m: self.timeout(username, 600) return True if 'nig3ers' in m: self.timeout(username, 600) return True if 'nig3ers' in m: self.timeout(username, 600) return True if 'nigger' in m: self.timeout(username, 600) return True # We use .lower() in case twitch ever starts sending non-lowercased usernames with self.users.get_user_context(username) as source: res = HandlerManager.trigger('on_pubmsg', source, event.arguments[0], stop_on_false=True) if res is False: return False self.parse_message(event.arguments[0], source, event, tags=event.tags)
def poll_trackobot_stage2(self, trackobot_game): if not self.detect_trackobot_game_change(trackobot_game): return with DBManager.create_session_scope() as db_session: current_game = self.get_current_game(db_session, with_bets=True, with_users=True) current_game.trackobot_id = trackobot_game.id current_game.outcome = trackobot_game.outcome points_by_outcome = current_game.get_points_by_outcome(db_session) total_points_in_pot = sum(points_by_outcome.values()) for bet in current_game.bets: correct_bet = bet.outcome == current_game.outcome if not correct_bet: # lost the bet bet.profit = -bet.points self.bot.whisper( bet.user, f"You bet {bet.points} points on the wrong outcome, so you lost it all. :(" ) else: # won the bet investment_ratio = bet.points / points_by_outcome[ bet.outcome] # pot_cut includes the user's initial investment pot_cut = int(investment_ratio * total_points_in_pot) # profit is just how much they won bet.profit = pot_cut - bet.points bet.user.points = User.points + pot_cut self.bot.whisper( bet.user, f"You bet {bet.points} points on the right outcome, that leaves you with a profit of {bet.profit} points! (Your bet was {investment_ratio * 100:.2f}% of the total pot)", ) HandlerManager.trigger("on_user_win_hs_bet", user=bet.user, points_won=bet.profit) winning_points = sum( points for outcome, points in points_by_outcome.items() if outcome == current_game.outcome) losing_points = sum( points for outcome, points in points_by_outcome.items() if outcome != current_game.outcome) end_message = f"The game ended as a {trackobot_game.outcome.name}. {points_by_outcome[HSGameOutcome.win]} points bet on win, {points_by_outcome[HSGameOutcome.loss]} points bet on loss." # don't want to divide by 0 if winning_points != 0: ratio = losing_points / winning_points * 100 end_message += f" Winners can expect a {ratio:.2f}% return on their bet points." else: end_message += " Nobody won any points. KKona" self.bot.me(end_message) # so we can create a new game db_session.flush() self.bot.me( "A new game has begun! Vote with !hsbet win/lose POINTS") current_game = self.get_current_game(db_session) time_limit = self.settings["time_until_bet_closes"] current_game.bet_deadline = utils.now() + datetime.timedelta( seconds=time_limit) bets_statistics = current_game.get_bets_by_outcome(db_session) payload = { "time_left": time_limit, **{key.name: value for key, value in bets_statistics.items()} } self.bot.websocket_manager.emit("hsbet_new_game", data=payload)
def __init__(self, config, args=None): # Load various configuration variables from the given config object # The config object that should be passed through should # come from pajbot.utils.load_config self.load_config(config) # Update the database scheme if necessary using alembic # In case of errors, i.e. if the database is out of sync or the alembic # binary can't be called, we will shut down the bot. pajbot.utils.alembic_upgrade() # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = datetime.datetime.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager( socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.filters = FilterManager().reload() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() self.emotes = EmoteManager(self) self.twitter_manager = TwitterManager(self) HandlerManager.trigger('on_managers_loaded') # Reloadable managers self.reloadable = { 'filters': self.filters, } # Commitable managers self.commitable = { 'commands': self.commands, 'filters': self.filters, 'banphrases': self.banphrase_manager, } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) try: self.admin = self.config['main']['admin'] except KeyError: log.warning('No admin user specified. See the [main] section in config.example.ini for its usage.') if self.admin: with self.users.get_user_context(self.admin) as user: user.level = 2000 self.parse_version() relay_host = self.config['main'].get('relay_host', None) relay_password = self.config['main'].get('relay_password', None) if relay_host is None or relay_password is None: self.irc = MultiIRCManager(self) else: self.irc = SingleIRCManager(self) self.reactor.add_global_handler('all_events', self.irc._dispatcher, -10) twitch_client_id = None twitch_oauth = None if 'twitchapi' in self.config: twitch_client_id = self.config['twitchapi'].get('client_id', None) twitch_oauth = self.config['twitchapi'].get('oauth', None) # A client ID is required for the bot to work properly now, give an error for now if twitch_client_id is None: log.error('MISSING CLIENT ID, SET "client_id" VALUE UNDER [twitchapi] SECTION IN CONFIG FILE') self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data['broadcaster'] = self.streamer self.data['version'] = self.version self.data['version_brief'] = self.version_brief self.data['bot_name'] = self.nickname self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['bot_uptime'] = self.c_uptime self.data_cb['current_time'] = self.c_current_time self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) try: if self.config['twitchapi']['update_subscribers'] == '1': self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass # XXX: TEMPORARY UGLY CODE HandlerManager.add_handler('on_user_gain_tokens', self.on_user_gain_tokens) HandlerManager.add_handler('send_whisper', self.whisper)
def multi_end_raffle(self): if not self.raffle_running: return False self.raffle_running = False if len(self.raffle_users) == 0: self.bot.me('Wow, no one joined the raffle DansGame') return False # Shuffle the list of participants random.shuffle(self.raffle_users) num_participants = len(self.raffle_users) abs_points = abs(self.raffle_points) max_winners = min(num_participants, 200) min_point_award = 100 negative = self.raffle_points < 0 # Decide how we should pick the winners log.info('Num participants: {}'.format(num_participants)) for winner_percentage in [x * 0.01 for x in range(1, 26)]: log.info('Winner percentage: {}'.format(winner_percentage)) num_winners = math.ceil(num_participants * winner_percentage) points_per_user = math.ceil(abs_points / num_winners) log.info('nw: {}, ppu: {}'.format(num_winners, points_per_user)) if num_winners > max_winners: num_winners = max_winners points_per_user = math.ceil(abs_points / num_winners) break elif points_per_user < min_point_award: num_winners = max(1, min(math.floor(abs_points / min_point_award), num_participants)) points_per_user = math.ceil(abs_points / num_winners) break log.info('k done. got {} winners'.format(num_winners)) winners = self.raffle_users[:num_winners] self.raffle_users = [] if negative: points_per_user *= -1 self.bot.me('The multi-raffle has finished! {0} users won {1} points each! PogChamp'.format(len(winners), points_per_user)) winners_arr = [] for winner in winners: winner.points += points_per_user winners_arr.append(winner) winners_str = generate_winner_list(winners_arr) if len(winners_str) > 300: self.bot.me('{} won {} points each!'.format(winners_str, points_per_user)) winners_arr = [] winner.save() if len(winners_arr) > 0: winners_str = generate_winner_list(winners_arr) self.bot.me('{} won {} points each!'.format(winners_str, points_per_user)) HandlerManager.trigger('on_multiraffle_win', winners, points_per_user)
def roulette(self, **options): if self.settings['only_roulette_after_sub']: if self.last_sub is None: return False if datetime.datetime.now() - self.last_sub > datetime.timedelta(seconds=self.settings['after_sub_roulette_time']): return False message = options['message'] user = options['source'] bot = options['bot'] if message is None: bot.whisper(user.username, 'I didn\'t recognize your bet! Usage: !roulette 150 to bet 150 points') return False msg_split = message.split(' ') try: bet = pajbot.utils.parse_points_amount(user, msg_split[0]) except pajbot.exc.InvalidPointAmount as e: bot.whisper(user.username, str(e)) return False if not user.can_afford(bet): bot.whisper(user.username, 'You don\'t have enough points to do a roulette for {} points :('.format(bet)) return False if bet < self.settings['min_roulette_amount']: bot.whisper(user.username, 'You have to bet at least {} point! :('.format(self.settings['min_roulette_amount'])) return False # Calculating the result result = self.rigged_random_result() points = bet if result else -bet user.points += points with DBManager.create_session_scope() as db_session: r = Roulette(user.id, points) db_session.add(r) arguments = { 'bet': bet, 'user': user.username_raw, 'points': user.points_available(), 'win': points > 0, } if points > 0: out_message = self.get_phrase('message_won', **arguments) else: out_message = self.get_phrase('message_lost', **arguments) if self.settings['options_output'] == '4. Combine output in chat': self.add_message(bot, arguments) if self.settings['options_output'] == '1. Show results in chat': bot.me(out_message) if self.settings['options_output'] == '2. Show results in whispers': bot.whisper(user.username, out_message) if self.settings['options_output'] == '3. Show results in chat if it\'s over X points else it will be whispered.': if abs(points) >= self.settings['min_show_points']: bot.me(out_message) else: bot.whisper(user.username, out_message) HandlerManager.trigger('on_roulette_finish', user, points)
def do_tick(self): HandlerManager.trigger('on_tick')
def base_paid_timeout(self, bot, source, message, _time, _cost): if message is None or len(message) == 0: return False target = message.split(" ")[0] if len(target) < 2: return False with DBManager.create_session_scope() as db_session: victim = User.find_by_user_input(db_session, target) if victim is None: bot.whisper(source, "This user does not exist FailFish") return False if victim.last_active is None or ( utils.now() - victim.last_active) > datetime.timedelta(minutes=10): bot.whisper( source, "This user has not been active in chat within the last 10 minutes." ) return False if victim.moderator is True: bot.whisper( source, "This person has mod privileges, timeouting this person is not worth it." ) return False if victim.level >= self.settings["bypass_level"]: bot.whisper( source, "This person's user level is too high, you can't timeout this person." ) return False now = utils.now() if victim.timeout_end is not None and victim.timeout_end > now: victim.timeout_end += datetime.timedelta(seconds=_time) bot.whisper( victim, f"{victim}, you were timed out for an additional {_time} seconds by {source}" ) bot.whisper( source, f"You just used {_cost} points to time out {victim} for an additional {_time} seconds." ) num_seconds = int((victim.timeout_end - now).total_seconds()) bot.timeout(victim, num_seconds, reason=f"Timed out by {source}") else: bot.whisper( source, f"You just used {_cost} points to time out {victim} for {_time} seconds." ) bot.whisper( victim, f"{source} just timed you out for {_time} seconds. /w {bot.nickname} !$unbanme to unban yourself for points forsenMoney", ) bot.timeout(victim, _time, reason=f"Timed out by {source}") victim.timeout_end = now + datetime.timedelta(seconds=_time) if self.settings["show_on_clr"]: payload = {"user": source.name, "victim": victim.name} bot.websocket_manager.emit("timeout", payload) HandlerManager.trigger("on_paid_timeout", source=source, victim=victim, cost=_cost, stop_on_false=False)
def parse_message(self, message, source, event, tags={}, whisper=False): msg_lower = message.lower() emote_tag = tags["emotes"] msg_id = tags.get("id", None) # None on whispers! if not whisper and event.target == self.channel: # Moderator or broadcaster, both count source.moderator = tags[ "mod"] == "1" or source.id == self.streamer_user_id source.subscriber = tags["subscriber"] == "1" if not whisper and source.banned: self.ban(source) return False # Parse emotes in the message emote_instances, emote_counts = self.emote_manager.parse_all_emotes( message, emote_tag) now = utils.now() source.last_seen = now source.last_active = now if not whisper: # increment epm and ecount self.epm_manager.handle_emotes(emote_counts) self.ecount_manager.handle_emotes(emote_counts) urls = self.find_unique_urls(message) res = HandlerManager.trigger( "on_message", source=source, message=message, emote_instances=emote_instances, emote_counts=emote_counts, whisper=whisper, urls=urls, msg_id=msg_id, event=event, ) if res is False: return False if source.ignored: return False if whisper: self.whisper_login("datguy1", "{} said: {}".format(source, message)) if msg_lower[:1] == "!": msg_lower_parts = msg_lower.split(" ") trigger = msg_lower_parts[0][1:] msg_raw_parts = message.split(" ") remaining_message = " ".join( msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if trigger in self.commands: command = self.commands[trigger] extra_args = { "emote_instances": emote_instances, "emote_counts": emote_counts, "trigger": trigger, "msg_id": msg_id, } command.run(self, source, remaining_message, event=event, args=extra_args, whisper=whisper)
def roulette(self, bot, source, message, **rest): if self.settings["stream_status"] == "Online" and not bot.is_online: return if self.settings["stream_status"] == "Offline" and bot.is_online: return if self.settings["only_roulette_after_sub"]: if self.last_sub is None: return False if utils.now() - self.last_sub > datetime.timedelta( seconds=self.settings["after_sub_roulette_time"]): return False if message is None: bot.whisper( source, "I didn't recognize your bet! Usage: !" + self.settings["command_name"] + " 150 to bet 150 points", ) return False msg_split = message.split(" ") try: bet = utils.parse_points_amount(source, msg_split[0]) except pajbot.exc.InvalidPointAmount as e: bot.whisper(source, str(e)) return False if not source.can_afford(bet): bot.whisper( source, f"You don't have enough points to do a roulette for {bet} points :(" ) return False if bet < self.settings["min_roulette_amount"]: bot.whisper( source, f"You have to bet at least {self.settings['min_roulette_amount']} point! :(" ) return False # Calculating the result result = self.rigged_random_result() points = bet if result else -bet source.points += points with DBManager.create_session_scope() as db_session: r = Roulette(source.id, points) db_session.add(r) arguments = { "bet": bet, "user": source.name, "points": source.points, "win": points > 0 } if points > 0: out_message = self.get_phrase("message_won", **arguments) else: out_message = self.get_phrase("message_lost", **arguments) if self.settings["options_output"] == "4. Combine output in chat": if bot.is_online: self.add_message(bot, arguments) else: bot.me(out_message) if self.settings["options_output"] == "1. Show results in chat": bot.me(out_message) if self.settings["options_output"] == "2. Show results in whispers": bot.whisper(source, out_message) if (self.settings["options_output"] == "3. Show results in chat if it's over X points else it will be whispered." ): if abs(points) >= self.settings["min_show_points"]: bot.me(out_message) else: bot.whisper(source, out_message) HandlerManager.trigger("on_roulette_finish", user=source, points=points)
def __init__(self, config, args): self.config = config self.args = args self.last_ping = utils.now() self.last_pong = utils.now() DBManager.init(self.config["main"]["db"]) # redis redis_options = {} if "redis" in config: redis_options = dict(config.items("redis")) RedisManager.init(**redis_options) wait_for_redis_data_loaded(RedisManager.get()) # Pepega SE points sync pajbot.models.user.Config.se_sync_token = config["main"].get( "se_sync_token", None) pajbot.models.user.Config.se_channel = config["main"].get( "se_channel", None) self.nickname = config["main"].get("nickname", "pajbot") self.timezone = config["main"].get("timezone", "UTC") if config["main"].getboolean("verified", False): TMI.promote_to_verified() # phrases self.phrases = { "welcome": ["{nickname} {version} running!"], "quit": ["{nickname} {version} shutting down..."] } if "phrases" in config: phrases = config["phrases"] if "welcome" in phrases: self.phrases["welcome"] = phrases["welcome"].splitlines() if "quit" in phrases: self.phrases["quit"] = phrases["quit"].splitlines() TimeManager.init_timezone(self.timezone) # streamer if "streamer" in config["main"]: self.streamer = config["main"]["streamer"] self.channel = "#" + self.streamer elif "target" in config["main"]: self.channel = config["main"]["target"] self.streamer = self.channel[1:] StreamHelper.init_streamer(self.streamer) log.debug("Loaded config") # do this earlier since schema upgrade can depend on the helix api self.api_client_credentials = ClientCredentials( self.config["twitchapi"]["client_id"], self.config["twitchapi"]["client_secret"], self.config["twitchapi"]["redirect_uri"], ) self.twitch_id_api = TwitchIDAPI(self.api_client_credentials) self.app_token_manager = AppAccessTokenManager(self.twitch_id_api, RedisManager.get()) self.twitch_helix_api = TwitchHelixAPI(RedisManager.get(), self.app_token_manager) self.twitch_v5_api = TwitchKrakenV5API(self.api_client_credentials, RedisManager.get()) self.twitch_legacy_api = TwitchLegacyAPI(self.api_client_credentials, RedisManager.get()) self.twitch_tmi_api = TwitchTMIAPI() self.bot_user_id = self.twitch_helix_api.get_user_id(self.nickname) if self.bot_user_id is None: raise ValueError( "The bot login name you entered under [main] does not exist on twitch." ) self.streamer_user_id = self.twitch_helix_api.get_user_id( self.streamer) if self.streamer_user_id is None: raise ValueError( "The streamer login name you entered under [main] does not exist on twitch." ) # SQL migrations sql_conn = DBManager.engine.connect().connection sql_migratable = DatabaseMigratable(sql_conn) sql_migration = Migration(sql_migratable, pajbot.migration_revisions.db, self) sql_migration.run() # Redis migrations redis_migratable = RedisMigratable(redis_options=redis_options, namespace=self.streamer) redis_migration = Migration(redis_migratable, pajbot.migration_revisions.redis, self) redis_migration.run() # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer, self.execute_now) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() # bot access token if "password" in self.config["main"]: log.warning( "DEPRECATED - Using bot password/oauth token from file. " "You should authenticate in web gui using route /bot_login " "and remove password from config file") access_token = self.config["main"]["password"] if access_token.startswith("oauth:"): access_token = access_token[6:] self.bot_token_manager = UserAccessTokenManager( api=None, redis=None, username=self.nickname, user_id=self.bot_user_id, token=UserAccessToken.from_implicit_auth_flow_token( access_token), ) else: self.bot_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.nickname, user_id=self.bot_user_id) self.emote_manager = EmoteManager(self.twitch_v5_api, self.twitch_legacy_api, self.action_queue) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager(socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.websocket_manager = WebSocketManager(self) HandlerManager.trigger("on_managers_loaded") # Commitable managers self.commitable = { "commands": self.commands, "banphrases": self.banphrase_manager } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) # promote the admin to level 2000 admin = None try: admin = self.config["main"]["admin"] except KeyError: log.warning( "No admin user specified. See the [main] section in the example config for its usage." ) if admin is not None: with self.users.get_user_context(admin) as user: user.level = 2000 # silent mode self.silent = ("flags" in config and "silent" in config["flags"] and config["flags"]["silent"] == "1") or args.silent if self.silent: log.info("Silent mode enabled") # dev mode self.dev = "flags" in config and "dev" in config["flags"] and config[ "flags"]["dev"] == "1" if self.dev: self.version_long = extend_version_if_possible(VERSION) else: self.version_long = VERSION self.irc = IRCManager(self) relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is not None or relay_password is not None: log.warning( "DEPRECATED - Relaybroker support is no longer implemented. relay_host and relay_password are ignored" ) self.reactor.add_global_handler("all_events", self.irc._dispatcher, -10) self.data = { "broadcaster": self.streamer, "version": self.version_long, "version_brief": VERSION, "bot_name": self.nickname, } self.data_cb = { "status_length": self.c_status_length, "stream_status": self.c_stream_status, "bot_uptime": self.c_uptime, "current_time": self.c_current_time, "molly_age_in_years": self.c_molly_age_in_years, }
def on_usernotice(self, source, tags, **rest): if "msg-id" not in tags: return if tags["msg-id"] == "resub": num_months = -1 substreak_count = 0 if "msg-param-months" in tags: num_months = int(tags["msg-param-months"]) if "msg-param-cumulative-months" in tags: num_months = int(tags["msg-param-cumulative-months"]) if "msg-param-streak-months" in tags: substreak_count = int(tags["msg-param-streak-months"]) if "msg-param-should-share-streak" in tags: should_share = bool(tags["msg-param-should-share-streak"]) if not should_share: substreak_count = 0 if "msg-param-sub-plan" not in tags: log.debug("subalert msg-id is resub, but missing msg-param-sub-plan: {}".format(tags)) return # log.debug('msg-id resub tags: {}'.format(tags)) # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead self.on_resub(source, num_months, tags["msg-param-sub-plan"], None, substreak_count) HandlerManager.trigger("on_user_resub", user=source, num_months=num_months) elif tags["msg-id"] == "subgift": num_months = 0 substreak_count = 0 if "msg-param-months" in tags: num_months = int(tags["msg-param-months"]) if "msg-param-cumulative-months" in tags: num_months = int(tags["msg-param-cumulative-months"]) if "msg-param-streak-months" in tags: substreak_count = int(tags["msg-param-streak-months"]) if "msg-param-should-share-streak" in tags: should_share = bool(tags["msg-param-should-share-streak"]) if not should_share: substreak_count = 0 if "display-name" not in tags: log.debug("subalert msg-id is subgift, but missing display-name: {}".format(tags)) return with self.bot.users.get_user_context(tags["msg-param-recipient-user-name"]) as receiver: if num_months > 1: # Resub self.on_resub( receiver, num_months, tags["msg-param-sub-plan"], tags["display-name"], substreak_count ) HandlerManager.trigger("on_user_resub", user=receiver, num_months=num_months) else: # New sub self.on_new_sub(receiver, tags["msg-param-sub-plan"], tags["display-name"]) HandlerManager.trigger("on_user_sub", user=receiver) elif tags["msg-id"] == "sub": if "msg-param-sub-plan" not in tags: log.debug("subalert msg-id is sub, but missing msg-param-sub-plan: {}".format(tags)) return self.on_new_sub(source, tags["msg-param-sub-plan"]) HandlerManager.trigger("on_user_sub", user=source) else: log.debug("Unhandled msg-id: {} - tags: {}".format(tags["msg-id"], tags))
def __init__(self, config, args=None): # Load various configuration variables from the given config object # The config object that should be passed through should # come from pajbot.utils.load_config self.load_config(config) # Update the database scheme if necessary using alembic # In case of errors, i.e. if the database is out of sync or the alembic # binary can't be called, we will shut down the bot. pajbot.utils.alembic_upgrade() # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = datetime.datetime.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager( socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.filters = FilterManager().reload() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() self.emotes = EmoteManager(self) self.twitter_manager = TwitterManager(self) HandlerManager.trigger('on_managers_loaded') # Reloadable managers self.reloadable = { 'filters': self.filters, } # Commitable managers self.commitable = { 'commands': self.commands, 'filters': self.filters, 'banphrases': self.banphrase_manager, } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) try: self.admin = self.config['main']['admin'] except KeyError: log.warning('No admin user specified. See the [main] section in config.example.ini for its usage.') if self.admin: with self.users.get_user_context(self.admin) as user: pass # user.level = 2000 self.parse_version() relay_host = self.config['main'].get('relay_host', None) relay_password = self.config['main'].get('relay_password', None) if relay_host is None or relay_password is None: self.irc = MultiIRCManager(self) else: self.irc = SingleIRCManager(self) self.reactor.add_global_handler('all_events', self.irc._dispatcher, -10) twitch_client_id = None twitch_oauth = None if 'twitchapi' in self.config: twitch_client_id = self.config['twitchapi'].get('client_id', None) twitch_oauth = self.config['twitchapi'].get('oauth', None) # A client ID is required for the bot to work properly now, give an error for now if twitch_client_id is None: log.error('MISSING CLIENT ID, SET "client_id" VALUE UNDER [twitchapi] SECTION IN CONFIG FILE') self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data['broadcaster'] = self.streamer self.data['version'] = self.version self.data['version_brief'] = self.version_brief self.data['bot_name'] = self.nickname self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['bot_uptime'] = self.c_uptime self.data_cb['current_time'] = self.c_current_time self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) try: if self.config['twitchapi']['update_subscribers'] == '1': self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass # XXX: TEMPORARY UGLY CODE HandlerManager.add_handler('on_user_gain_tokens', self.on_user_gain_tokens) HandlerManager.add_handler('send_whisper', self.whisper)
def parse_message(self, message, source, event, tags={}, whisper=False): msg_lower = message.lower() emote_tag = None msg_id = None for tag in tags: if tag["key"] == "subscriber" and event.target == self.channel: source.subscriber = tag["value"] == "1" elif tag["key"] == "emotes": emote_tag = tag["value"] elif tag["key"] == "display-name" and tag["value"]: source.username_raw = tag["value"] elif tag["key"] == "user-type": source.moderator = tag[ "value"] == "mod" or source.username == self.streamer elif tag["key"] == "id": msg_id = tag["value"] # source.num_lines += 1 if source is None: log.error("No valid user passed to parse_message") return False if source.banned: self.ban(source.username) return False # If a user types when timed out, we assume he's been unbanned for a good reason and remove his flag. if source.timed_out is True: source.timed_out = False # Parse emotes in the message emote_instances, emote_counts = self.emote_manager.parse_all_emotes( message, emote_tag) if not whisper: # increment epm and ecount self.epm_manager.handle_emotes(emote_counts) self.ecount_manager.handle_emotes(emote_counts) urls = self.find_unique_urls(message) res = HandlerManager.trigger( "on_message", source=source, message=message, emote_instances=emote_instances, emote_counts=emote_counts, whisper=whisper, urls=urls, msg_id=msg_id, event=event, ) if res is False: return False source.last_seen = utils.now() source.last_active = utils.now() if source.ignored: return False if whisper: self.whisper("datguy1", "{} said: {}".format(source.username_raw, message)) if msg_lower[:1] == "!": msg_lower_parts = msg_lower.split(" ") trigger = msg_lower_parts[0][1:] msg_raw_parts = message.split(" ") remaining_message = " ".join( msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if trigger in self.commands: command = self.commands[trigger] extra_args = { "emote_instances": emote_instances, "emote_counts": emote_counts, "trigger": trigger, "msg_id": msg_id, } command.run(self, source, remaining_message, event=event, args=extra_args, whisper=whisper)
def do_tick(self): HandlerManager.trigger('on_tick')
def multi_end_raffle(self): if not self.raffle_running: return False self.raffle_running = False if len(self.raffle_users) == 0: self.bot.me("Wow, no one joined the raffle DansGame") return False # Shuffle the list of participants random.shuffle(self.raffle_users) num_participants = len(self.raffle_users) abs_points = abs(self.raffle_points) max_winners = min(num_participants, 200) min_point_award = 100 negative = self.raffle_points < 0 # Decide how we should pick the winners log.info("Num participants: {}".format(num_participants)) for winner_percentage in [x * 0.01 for x in range(1, 10)]: log.info("Winner percentage: {}".format(winner_percentage)) num_winners = math.ceil(num_participants * winner_percentage) points_per_user = math.ceil(abs_points / num_winners) log.info("nw: {}, ppu: {}".format(num_winners, points_per_user)) if num_winners > max_winners: num_winners = max_winners points_per_user = math.ceil(abs_points / num_winners) break elif points_per_user < min_point_award: num_winners = max( 1, min(math.floor(abs_points / min_point_award), num_participants)) points_per_user = math.ceil(abs_points / num_winners) break log.info("k done. got {} winners".format(num_winners)) winners = self.raffle_users[:num_winners] for winner in winners: if not winner.subscriber and random.randint(1, 10) > 5: winners.remove(winner) continue if not winners: winners = random.choice(self.raffle_users) self.raffle_users = [] points_per_user = math.ceil(abs_points / len(winners)) if negative: self.bot.me( "The multi-raffle has finished! {0} users lost {1} points each! OMEGALUL" .format(len(winners), points_per_user * -1)) else: self.bot.me( "The multi-raffle has finished! {0} users won {1} points each! PogChamp" .format(len(winners), points_per_user)) winners_arr = [] for winner in winners: if negative: winner.points = winner.points - points_per_user else: winner.points = winner.points + points_per_user winners_arr.append(winner) winners_str = generate_winner_list(winners_arr) if len(winners_str) > 300: self.bot.me("{} {} {} points each!".format( winners_str, "lost" if negative else "won", points_per_user)) winners_arr = [] winner.save() if len(winners_arr) > 0: winners_str = generate_winner_list(winners_arr) self.bot.me("{} {} {} points each!".format( winners_str, "lost" if negative else "won", points_per_user)) HandlerManager.trigger("on_multiraffle_win", winners=winners, points_per_user=points_per_user * -1 if negative else points_per_user)
def parse_message(self, message, source, event, tags={}, whisper=False): msg_lower = message.lower() emote_tag = tags["emotes"] msg_id = tags.get("id", None) # None on whispers! badges_string = tags.get("badges", "") badges = dict((badge.split("/") for badge in badges_string.split(",") if badge != "")) if not whisper and event.target == self.channel: # Moderator or broadcaster, both count source.moderator = tags["mod"] == "1" or source.id == self.streamer_user_id # Having the founder badge means that the subscriber tag is set to 0. Therefore it's more stable to just check badges source.subscriber = "founder" in badges or "subscriber" in badges # once they are a founder they are always be a founder, regardless if they are a sub or not. if not source.founder: source.founder = "founder" in badges source.vip = "vip" in badges if not whisper and source.banned: self.ban( source, reason=f"User is on the {self.nickname} banlist. Contact a moderator level 1000 or higher for unban.", ) return False # Parse emotes in the message emote_instances, emote_counts = self.emote_manager.parse_all_emotes(message, emote_tag) now = utils.now() source.last_seen = now source.last_active = now if not whisper: # increment epm and ecount self.epm_manager.handle_emotes(emote_counts) self.ecount_manager.handle_emotes(emote_counts) urls = self.find_unique_urls(message) res = HandlerManager.trigger( "on_message", source=source, message=message, emote_instances=emote_instances, emote_counts=emote_counts, whisper=whisper, urls=urls, msg_id=msg_id, event=event, ) if res is False: return False if source.ignored: return False if msg_lower[:1] == "!": msg_lower_parts = msg_lower.split(" ") trigger = msg_lower_parts[0][1:] msg_raw_parts = message.split(" ") remaining_message = " ".join(msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if trigger in self.commands: command = self.commands[trigger] extra_args = { "emote_instances": emote_instances, "emote_counts": emote_counts, "trigger": trigger, "msg_id": msg_id, } command.run(self, source, remaining_message, event=event, args=extra_args, whisper=whisper)
def on_usernotice(self, source, tags, **rest): if "msg-id" not in tags: return if tags["msg-id"] == "resub": num_months = -1 substreak_count = 0 if "msg-param-months" in tags: num_months = int(tags["msg-param-months"]) if "msg-param-cumulative-months" in tags: num_months = int(tags["msg-param-cumulative-months"]) if "msg-param-streak-months" in tags: substreak_count = int(tags["msg-param-streak-months"]) if "msg-param-should-share-streak" in tags: should_share = bool(tags["msg-param-should-share-streak"]) if not should_share: substreak_count = 0 if "msg-param-sub-plan" not in tags: log.debug( f"subalert msg-id is resub, but missing msg-param-sub-plan: {tags}" ) return # log.debug('msg-id resub tags: {}'.format(tags)) # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead self.on_resub(source, num_months, tags["msg-param-sub-plan"], None, substreak_count) HandlerManager.trigger("on_user_resub", user=source, num_months=num_months) elif tags["msg-id"] == "subgift": num_months = 0 substreak_count = 0 if "msg-param-months" in tags: num_months = int(tags["msg-param-months"]) if "msg-param-cumulative-months" in tags: num_months = int(tags["msg-param-cumulative-months"]) if "msg-param-streak-months" in tags: substreak_count = int(tags["msg-param-streak-months"]) if "msg-param-should-share-streak" in tags: should_share = bool(tags["msg-param-should-share-streak"]) if not should_share: substreak_count = 0 if "display-name" not in tags: log.debug( f"subalert msg-id is subgift, but missing display-name: {tags}" ) return with DBManager.create_session_scope() as db_session: receiver_id = tags["msg-param-recipient-id"] receiver_login = tags["msg-param-recipient-user-name"] receiver_name = tags["msg-param-recipient-display-name"] receiver = User.from_basics( db_session, UserBasics(receiver_id, receiver_login, receiver_name)) if num_months > 1: # Resub self.on_resub(receiver, num_months, tags["msg-param-sub-plan"], tags["display-name"], substreak_count) HandlerManager.trigger("on_user_resub", user=receiver, num_months=num_months) else: # New sub self.on_new_sub(receiver, tags["msg-param-sub-plan"], tags["display-name"]) HandlerManager.trigger("on_user_sub", user=receiver) elif tags["msg-id"] == "sub": if "msg-param-sub-plan" not in tags: log.debug( f"subalert msg-id is sub, but missing msg-param-sub-plan: {tags}" ) return self.on_new_sub(source, tags["msg-param-sub-plan"]) HandlerManager.trigger("on_user_sub", user=source) elif tags["msg-id"] == "giftpaidupgrade": self.on_gift_upgrade(source) elif tags["msg-id"] == "extendsub": self.on_extend_sub(source) else: log.debug(f"Unhandled msg-id: {tags['msg-id']} - tags: {tags}")
def commit_all(self): for key, manager in self.commitable.items(): manager.commit() HandlerManager.trigger("on_commit", stop_on_false=False)
def multi_end_raffle(self): if not self.raffle_running: return False self.raffle_running = False if len(self.raffle_users) == 0: self.bot.me("Wow, no one joined the raffle DansGame") return False num_participants = len(self.raffle_users) # start out with the theoretical maximum: everybody wins num_winners = num_participants # we want to impose three limits on the winner picking: # - a winner should get 100 points at minimum, num_winners = min( num_winners, math.floor( abs(self.raffle_points) / self.MULTI_RAFFLE_MIN_WIN_POINTS_AMOUNT)) # - winner percentage should not be higher than 26%, num_winners = min( num_winners, math.floor(num_participants * self.MULTI_RAFFLE_MAX_WINNERS_RATIO)) # - and we don't want to have more than 200 winners. num_winners = min(num_winners, self.MULTI_RAFFLE_MAX_WINNERS_AMOUNT) # we at least want one person to win (some of these restrictions might calculate a maximum of 0...) num_winners = max(num_winners, 1) # now we can figure out how much each participant should win points_per_user = int(round(self.raffle_points / num_winners)) # and we can pick the winners! winner_ids = random.sample(self.raffle_users, num_winners) with DBManager.create_session_scope() as db_session: winners = db_session.query(User).filter( User.id.in_(winner_ids)).all() # reset self.raffle_users = set() if num_winners == 1: self.bot.me( f"The multi-raffle has finished! 1 user {format_win(points_per_user)} points! PogChamp" ) else: self.bot.me( f"The multi-raffle has finished! {num_winners} users {format_win(points_per_user)} points each! PogChamp" ) winners_arr = [] for winner in winners: winner.points += points_per_user winners_arr.append(winner) winners_str = generate_winner_list(winners_arr) if len(winners_str) > 300: if len(winners_arr) == 1: self.bot.me( f"{winners_str} {format_win(points_per_user)} points!" ) else: self.bot.me( f"{winners_str} {format_win(points_per_user)} points each!" ) winners_arr = [] if len(winners_arr) > 0: winners_str = generate_winner_list(winners_arr) if len(winners_arr) == 1: self.bot.me( f"{winners_str} {format_win(points_per_user)} points!") else: self.bot.me( f"{winners_str} {format_win(points_per_user)} points each!" ) HandlerManager.trigger("on_multiraffle_win", winners=winners, points_per_user=points_per_user)
def pull(self, **options): log.debug('pull xd') message = options['message'] user = options['source'] bot = options['bot'] if message is None: bot.whisper(user.username, 'I didn\'t recognize your bet! Usage: !slotmachine 150 to bet 150 points') return False low_tier_emotes = self.settings['low_tier_emotes'].split() high_tier_emotes = self.settings['high_tier_emotes'].split() if len(low_tier_emotes) == 0 or len(high_tier_emotes) == 0: return False msg_split = message.split(' ') try: bet = pajbot.utils.parse_points_amount(user, msg_split[0]) except pajbot.exc.InvalidPointAmount as e: bot.whisper(user.username, str(e)) return False if not user.can_afford(bet): bot.whisper(user.username, 'You don\'t have enough points to do a slot machine pull for {} points :('.format(bet)) return False if bet < self.settings['min_bet']: bot.whisper(user.username, 'You have to bet at least {} point! :('.format(self.settings['min_bet'])) return False # how much of the users point they're expected to get back (basically how much the house yoinks) expected_return = 1.0 ltsw = self.settings['ltsw'] / 100.0 htsw = self.settings['htsw'] / 100.0 ltbw = self.settings['ltbw'] / 100.0 htbw = self.settings['htbw'] / 100.0 bet_return, randomized_emotes = pull_lol(low_tier_emotes, high_tier_emotes, bet, expected_return, ltsw, htsw, ltbw, htbw) # Calculating the result if bet_return <= 0.0: points = -bet else: points = bet * bet_return user.points += points arguments = { 'bet': bet, 'result': points, 'user': user.username_raw, 'points': user.points_available(), 'win': points > 0, 'emotes': ' '.join(randomized_emotes), } if points > 0: out_message = self.get_phrase('message_won', **arguments) else: out_message = self.get_phrase('message_lost', **arguments) if self.settings['options_output'] == '4. Combine output in chat': if bot.is_online: self.add_message(bot, arguments) else: bot.me(out_message) if self.settings['options_output'] == '1. Show results in chat': bot.me(out_message) if self.settings['options_output'] == '2. Show results in whispers': bot.whisper(user.username, out_message) if self.settings['options_output'] == '3. Show results in chat if it\'s over X points else it will be whispered.': if abs(points) >= self.settings['min_show_points']: bot.me(out_message) else: bot.whisper(user.username, out_message) HandlerManager.trigger('on_slot_machine_finish', user, points)
def accept_duel(self, **options): """ Accepts any active duel requests you've received. How to add: !add funccommand accept accept_duel --cd 0 --usercd 0 How to use: !accept """ bot = options['bot'] source = options['source'] duel_tax = 0.3 # 30% tax if source.username not in self.duel_targets: bot.whisper(source.username, 'You are not being challenged to a duel by anyone.') return requestor = bot.users[self.duel_targets[source.username]] duel_price = self.duel_request_price[self.duel_targets[source.username]] if not source.can_afford(duel_price) or not requestor.can_afford(duel_price): bot.whisper(source.username, 'Your duel request with {} was cancelled due to one of you not having enough points.'.format(requestor.username_raw)) bot.whisper(requestor.username, 'Your duel request with {} was cancelled due to one of you not having enough points.'.format(source.username_raw)) del self.duel_requests[self.duel_targets[source.username]] del self.duel_targets[source.username] return False source.points -= duel_price requestor.points -= duel_price winning_pot = int(duel_price * (1.0 - duel_tax)) participants = [source, requestor] winner = random.choice(participants) participants.remove(winner) loser = participants.pop() winner.points += duel_price winner.points += winning_pot winner.save() loser.save() DuelManager.user_won(winner, winning_pot) DuelManager.user_lost(loser, duel_price) arguments = { 'winner': winner.username, 'loser': loser.username, 'total_pot': duel_price, 'extra_points': winning_pot, } if duel_price > 0: message = self.get_phrase('message_won_points', **arguments) if duel_price >= 500 and self.settings['show_on_clr']: bot.websocket_manager.emit('notification', {'message': '{} won the duel vs {}'.format(winner.username_raw, loser.username_raw)}) else: message = self.get_phrase('message_won', **arguments) bot.say(message) del self.duel_requests[self.duel_targets[source.username]] del self.duel_targets[source.username] HandlerManager.trigger('on_duel_complete', winner, loser, winning_pot, duel_price)