async def handle(message: SlackMessage, bot: SlackAdapter): if not bot.addressed_by(message): return if not bot.understands( message, with_pattern=re.compile('card prices?[?]?$', re.I)): return res = bot.reply(message, text='Here are the current prices for creating cards:', blocks=price_blocks()) if not res['ok']: print(res)
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): pattern = re.compile('balance', re.IGNORECASE) if not bot.addressed_by(message) or not bot.understands( message, with_pattern=pattern): return with db.session_scope() as session: user = User.get_by_slack_id(session, message.user) balance = get_kkred_balance(user, session) pluralized_kkreds = 'kkred' if balance == 1 else 'kkreds' return bot.reply(message, f'your balance is {balance} {pluralized_kkreds}')
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): pattern = "|".join( [".*(?:gibbe|give) money.*", ".*pay me.*", ".*:watermelon:.*"]) trigger = re.compile(pattern, re.IGNORECASE) message_ts = arrow.get(message.ts, 'X') if not is_payable(message_ts) or not bot.understands( message, with_pattern=trigger): return user_id = message.user with db.session_scope() as session: user = User.get_by_slack_id(session, user_id) if not user: return latest_mine = session \ .query(KKredsTransaction) \ .filter(KKredsTransaction.to_user_id == user.id) \ .filter(KKredsTransaction.is_mined) \ .order_by(KKredsTransaction.created_at.desc()) \ .first() if latest_mine and latest_mine.created_at: message_ts_stripped = strip_date(message_ts) latest_mine_time_stripped = strip_date(latest_mine.created_at) if latest_mine_time_stripped >= message_ts_stripped: return amount = 1 if should_2020_04_mega_pay(message_ts): amount = randint(1, 10_000) kaori_user = User.get_by_slack_id(session, bot.id) mined_kkred = KKredsTransaction(from_user=kaori_user, to_user=user, amount=amount, is_mined=True, created_at=message_ts.datetime) session.add(mined_kkred) pluralized = '1 kkred' if amount == 1 else f'{amount} kkreds' bot.reply(message, f'successfully mined {pluralized}')
async def handle(message: SlackMessage, bot: SlackAdapter): if not bot.addressed_by(message): return asking_help = re.compile(r'(?:cards|gacha) ?(?:help|-h|--help)?', re.I) question = re.compile(r'(?:what|why|how|explain).+(?:cards?|gacha)', re.I) if not (bot.understands(message, with_pattern=asking_help) or bot.understands(message, with_pattern=question)): return bot.reply(message, blocks=help_blocks(), create_thread=True, reply_broadcast=True)
async def test_help_command(fake_slack_msg_factory, fake_slack_adapter: SlackAdapter): bogus_message = fake_slack_msg_factory(text=f'@kaori asldfkjslkdjf') fake_slack_adapter.reply = MagicMock(return_value={'ok': True}) await CardHelpCommand.handle(bogus_message, bot=fake_slack_adapter) await CardPriceCommand.handle(bogus_message, bot=fake_slack_adapter) fake_slack_adapter.reply.assert_not_called() await CardHelpCommand.handle(fake_slack_msg_factory( text='@kaori cards help', bot=fake_slack_adapter, ), bot=fake_slack_adapter) ehhhh = ujson.dumps(fake_slack_adapter.reply.call_args[1]) assert 'kaori show cards' in ehhhh assert 'kaori battle NAME vs. NAME' in ehhhh await CardPriceCommand.handle(fake_slack_msg_factory( text='@kaori card prices', bot=fake_slack_adapter, ), bot=fake_slack_adapter) ehhhh = ujson.dumps(fake_slack_adapter.reply.call_args[1]) assert 'price breakdown' in ehhhh
async def handle(message: SlackMessage, bot: SlackAdapter): if not bot.addressed_by(message): return match = bot.understands(message, with_pattern=re.compile('clap (.*)')) if not match: return try: args = clap_parser.parse_args(match.group(1).split()) except SlackArgumentParserException as err: # lol commented out for max sass # send(str(err)) return bot.respond(message, random_insult()) if args.help: return bot.respond(message, clap_parser.get_help()) if not args.message: return bot.respond(message, random_insult()) new_message = ' {} '.format(args.separator).join(args.message) if args.at: new_message = '{} {}'.format(args.at, new_message) bot.respond(message, new_message)
def refresh_card_preview(card: Card, bot: SlackAdapter): res = bot.edit( SlackMessage({ 'event': { 'ts': card.draft_message_ts, 'channel': card.creation_thread_channel } }), **render_card(card, preview_header=True)) if not res['ok']: print(res)
async def handle(message: SlackMessage, bot: SlackAdapter): if message.user == bot.id: return text = message.text.strip() tokens = text.split() if not text: return if bot.mentioned.directly(tokens[0]) and tokens[0].endswith('?'): bot.reply(message, random.choice(im_here_response)) if interrogative_greeting.search(text) and bot.mentioned.anywhere( text): return bot.reply(message, random.choice(interrogative_greeting_response)) if greeting.search(text) and bot.mentioned.anywhere(text): return bot.reply(message, random.choice(greeting_response))
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): if not bot.addressed_by(message): return match = bot.understands(message, with_pattern=re.compile('refresh users')) if not match: return res = bot.client.api_call('users.list') if not res['ok']: raise RuntimeError('call to users.list failed') slack_members = [ member for member in res['members'] if not member['deleted'] ] with db.session_scope() as session: def maybe_create(slack_id): return User.maybe_create_user_from_slack_id( slack_id, bot.client, session) kaori_members = [ maybe_create(member['id']) for member in slack_members ] for member in kaori_members: el = [x for x in slack_members if x['id'] == member.slack_id] if not el: continue el = el[0] if member.name != el['name']: member.name = el['name'] bot.reply(message, 'Refreshed users. :^)')
async def handle(message: SlackMessage, bot: SlackAdapter): if not bot.addressed_by(message): return if bot.understands(message, with_pattern=re.compile('ping$', re.I)): bot.reply(message, 'pong') return if bot.understands(message, with_pattern=re.compile('bing$', re.I)): bot.reply(message, 'BONG', create_thread=True) return
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB, battler: CardBattler): if not bot.addressed_by(message): return requested_battle = _user_requesting_battle(message=message, bot=bot) if not requested_battle: return attacker_search = requested_battle[1] defender_search = requested_battle[2] with db.session_scope() as session: attacker = Card.search_for_one(session, attacker_search) if not attacker: bot.reply(message, f'no card named "{attacker_search}"', create_thread=True) return defender = Card.search_for_one(session, defender_search) if not defender: bot.reply(message, f'no card named "{defender_search}"', create_thread=True) return battle_url = battler.get_battle_url(attacker.engine, defender.engine) bot.reply(message, create_thread=True, blocks=battle_blocks(attacker=attacker, defender=defender, battle_url=battle_url))
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): if not bot.addressed_by(message): return show_card_pattern = '|'.join([ '(?:get|show|find) card' ]) show_card_pattern = f'(?:{show_card_pattern})' pattern = re.compile(f'{show_card_pattern} (.+)', re.I) search = bot.understands(message, with_pattern=pattern) if not search: return with db.session_scope() as session: card = Card.search_for_one(session, search[1]) if not card: bot.reply(message, 'no card with that name', create_thread=True) return bot.reply(message, **render_card(card))
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): if not bot.addressed_by(message): return show_card_pattern = '|'.join([ 'cards? (?:stats|statistics)' ]) if not bot.understands(message, with_pattern=re.compile(show_card_pattern, re.I)): return with db.session_scope() as session: game_cards = get_game_cards(session) if not game_cards: bot.reply(message, "No cards", create_thread=True) return bot.reply(message, blocks=card_stats_blocks(card_total=len(game_cards)), create_thread=True) report = generate_report_charts(game_cards) bot.client.api_call( 'files.upload', channels=message.channel, thread_ts=message.ts, filename='rarity.png', file=report['rarity']) bot.client.api_call( 'files.upload', channels=message.channel, thread_ts=message.ts, filename='natures.png', file=report['natures'])
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): if not bot.addressed_by(message): return show_card_pattern = '|'.join([ '(?:get|gimme|show|find|list) ?(?:my|muh)? cardo?s' ]) if not bot.understands(message, with_pattern=re.compile(show_card_pattern, re.I)): return with db.session_scope() as session: cards = session.query(Card) \ .join(User) \ .filter(User.slack_id == message.user) \ .filter(Card.published == True) \ .limit(10) \ .all() if not cards: bot.reply(message, "You don't have any cards yet.", create_thread=True) return bot.reply(message, create_thread=True, blocks=card_index_blocks(cards))
environment=config.KIZUNA_ENV) rabbitmq_broker = RabbitmqBroker(url=config.RABBITMQ_URL) dramatiq.set_broker(rabbitmq_broker) if not config.SLACK_API_TOKEN: raise ValueError('You are missing a slack token! Please set the SLACK_API_TOKEN environment variable in your ' '.env file or in the system environment') sc = SlackClient(config.SLACK_API_TOKEN) db_engine = create_engine(config.DATABASE_URL) make_session = sessionmaker(bind=db_engine, autoflush=False) k = Kaori() k.adapters['slack'] = SlackAdapter(slack_client=sc) k.skills |= { DB(make_session=make_session), } if hasattr(config, 'USE_GCLOUD_STORAGE') and config.USE_GCLOUD_STORAGE: creds = service_account.Credentials.from_service_account_info(config.GCLOUD_SERVICE_ACCOUNT_INFO) bucket = storage.Client(project=creds.project_id, credentials=creds).bucket(config.IMAGES_BUCKET_GCLOUD) k.skills.add(GCloudStorageUploader(bucket=bucket, base_path=config.IMAGES_BUCKET_PATH)) elif config.KIZUNA_ENV == 'development': k.skills.add(LocalFileUploader()) else:
def test_card_creation_state_happy(make_fake_user, grant_kkreds): config = test_config db_engine = create_engine(config.DATABASE_URL) make_session = sessionmaker(bind=db_engine, autoflush=False) k = Kaori() initial_ts = str(time()) slack = Mock(spec=SlackClient) slack.api_call = MagicMock(return_value={ 'ok': True, 'ts': initial_ts, }) adapter = SlackAdapter(slack_client=slack) adapter._cached_bot_id = token_hex(2) k.adapters['slack'] = adapter db = DB(make_session=make_session) k.skills |= { db, CardBattler(player_url_base='https://battle.kaori.io/') } k.skills.add(LocalFileUploader()) k.plugins |= { gacha_plugin, } u: User = make_fake_user() slack_id = u.slack_id user_id = u.id grant_kkreds(u, 1e10) def handle(msg): k.handle('slack', msg) channel = "CXXXXXX" def user_message(**kwargs): ts = time() return { "team_id": "TXXXX", "event": { "type": "message", "user": slack_id, "ts": ts, "channel": channel, "event_ts": ts, "channel_type": "channel", **kwargs, }, "type": "event_callback", } with db.session_scope() as session: handle(user_message(text='@kaori create card', ts=initial_ts, event_ts=initial_ts)) card = session.query(Card) \ .join(User) \ .filter(Card.creation_thread_ts == initial_ts) \ .first() assert card.owner == user_id assert card.creation_cursor == 'set_name' name = f'Matt Morgan {token_hex(2)}' handle(user_message(text=f'@kaori {name}', thread_ts=initial_ts)) session.refresh(card) assert card.name == name assert card.creation_cursor == 'set_image' # TODO skipping over image uploading lmao card.creation_cursor = 'set_description' session.commit() handle(user_message(text=f'@kaori ubu uwu', thread_ts=initial_ts)) session.refresh(card) assert card.description == 'ubu uwu' handle(user_message(text=f'@kaori stupid feral', thread_ts=initial_ts)) session.refresh(card) assert card.primary_nature == 'stupid' assert card.secondary_nature == 'feral' handle(user_message(text=f'@kaori S', thread_ts=initial_ts)) session.refresh(card) assert card.rarity_string() == 'S' assert card.published is False handle(user_message(text=f'@kaori yes', thread_ts=initial_ts)) session.refresh(card) assert card.published is True assert card.creation_cursor == 'done'
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB, file_uploader: FileUploader): if not bot.addressed_by(message): return # start a conversation if not bot.understands(message, with_pattern=re.compile( r'create\s+card|card\s+create$', re.I)): return if message.is_thread_reply: return try: with db.session_scope() as session: user = User.get_by_slack_id(session, message.user) if not user: raise UserNotFound('cannot find user') # allow creation to recover from errors card = resume_card(session, thread_ts=message.thread_ts, user=message.user) if not card: card = initialize_card(message, user) session.add(card) session.commit() draft_message = bot.reply(message, **render_card(card=card, preview_header=True), create_thread=True, reply_broadcast=True) bot.reply( message, blocks=instructions_blocks(bot_name=bot.mention_string), create_thread=True) if not draft_message.get('ok'): print(draft_message) return card.draft_message_ts = draft_message.get('ts') session.merge(card) except UserNotFound as e: bot.reply( message, "Something is wrong...cannot find your user. Try 'kaori refresh users'" ) return # fake thread # todo: this is kinda dumb message = copy.deepcopy(message) message.is_thread = True message.thread_ts = message.ts await UpdateCardCommand.handle(message=message, bot=bot, db=db, file_uploader=file_uploader)
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB, file_uploader: FileUploader): if not bot.addressed_by(message) or not message.is_thread: return try: session: Session with db.session_scope() as session: card = resume_card(session, thread_ts=message.thread_ts, user=message.user) # this thread is not related to a card creation, ignore if not card: return # lol god this is quickly getting away from me # just let me finish this and I'll refactor later user_input = '' catch_all_pattern = re.compile(r'(.*)', re.IGNORECASE) matches = bot.understands(message, with_pattern=catch_all_pattern) if not matches: return user_input = matches[1].strip() if user_input == 'refresh preview': refresh_card_preview(card, bot) return if card.creation_cursor == 'set_image': if message.files: img = Image.from_slack_message(message=message, session=session, slack_adapter=bot, uploader=file_uploader) card.image = img card.creation_cursor = 'query_description' bot.react(message, 'thumbsup') else: bot.reply(message, 'upload an image to slack') return card, replies = next_card_creation_step( card=card, user_input=user_input, session=session, kaori_user=User.get_by_slack_id(session, bot.id)) refresh_card_preview(card, bot) session.merge(card) session.commit() for reply in replies: if reply == ':+1:': bot.react(message, 'thumbsup') else: if isinstance(reply, dict): bot.reply(message, create_thread=True, **reply) else: bot.reply(message, reply, create_thread=True) except InvalidCardName as e: bot.reply(message, str(e)) except IntegrityError as e: bot.reply( message, f"Something is wrong with that input...try again or ask Austin to fix it. Code {e.code}" ) print(e)
async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): pattern = re.compile(r'(?:pay|tip|give|send)\s+(\S*)\s+(\S*)', re.IGNORECASE) if not bot.addressed_by(message): return matches = bot.understands(message, with_pattern=pattern) if not matches: return message_ts = arrow.get(message.ts, 'X') sending_user_id = message.user with db.session_scope() as session: sending_user = User.get_by_slack_id(session, sending_user_id) if not sending_user: return receiving_user_raw = matches[1] if not is_user_mention(receiving_user_raw): return bot.reply( message, 'User has to be an `@` mention. Like it has to be a real blue `@` mention.' ) receiving_user = User.get_by_slack_id( session, extract_user_id_from_mention(receiving_user_raw)) if not receiving_user: return bot.reply(message, 'Could not find that user') if sending_user.id == receiving_user.id: return bot.reply(message, 'You can’t send money to yourself.') amount_raw = matches[2] try: amount = Decimal(amount_raw) except InvalidOperation: return bot.reply( message, 'That amount is invalid. Try a decimal or integer value') if amount <= 0: return bot.reply(message, 'Amount has to be non-zero') if amount > get_kkred_balance(sending_user, session): return bot.reply(message, 'You don’t have enough kkreds') transaction = KKredsTransaction(from_user=sending_user, to_user=receiving_user, amount=amount, created_at=message_ts.datetime) session.add(transaction) bot.reply(message, f'successfully sent {amount} to {receiving_user.name}')
def _user_requesting_battle(message: SlackMessage, bot: SlackAdapter) -> Optional[Match]: pattern = re.compile(r'battle\s+(.+)\s+vs?\.?\s+(.+)', re.I) return bot.understands(message, with_pattern=pattern)