def show_kintai_history(message): """直近40日分の勤怠記録を表示します :param message: slackbotの各種パラメータを保持したclass """ user_id = message.body['user'] today = datetime.date.today() target_day = today - datetime.timedelta(days=40) s = Session() qs = (s.query(KintaiHistory).filter( KintaiHistory.user_id == user_id).filter( KintaiHistory.registered_at >= target_day).order_by( KintaiHistory.registered_at.asc())) kintai = OrderedDict() for q in qs: day_of_week = DAY_OF_WEEK[q.registered_at.date().weekday()] prefix_day = '{:%Y年%m月%d日}({})'.format(q.registered_at, day_of_week) registered_at = '{:%H:%M:%S}'.format(q.registered_at) kind = '出社' if q.is_workon else '退社' kintai.setdefault(prefix_day, []).append('{} {}'.format(kind, registered_at)) rows = [] for prefix, registered_ats in kintai.items(): sorted_times = ' '.join(sorted(registered_ats)) rows.append('{} {}'.format(prefix, sorted_times)) if not rows: rows = ['勤怠記録はありません'] user_name = get_user_name(user_id) botsend(message, '{}の勤怠:\n{}'.format(user_name, '\n'.join(rows)))
def show_uranai_commands(message, birthday): """Uranaiコマンドの結果を表示 :param message: slackbot.dispatcher.Message :param birthday: 4桁の誕生日 """ botsend(message, uranai(birthday))
def show_user_alias_name(message, user_name=None): """ユーザーのエイリアス名一覧を表示する :param message: slackbotの各種パラメータを保持したclass :param str user: Slackのユーザー名 """ if user_name: slack_id = get_slack_id_by_name(user_name) else: slack_id = message.body['user'] user_name = get_user_name(slack_id) if not slack_id: botsend(message, '{}に紐づくSlackのuser_idは存在しません'.format(user_name)) return s = Session() alias_names = [ user.alias_name for user in s.query(UserAliasName).filter( UserAliasName.slack_id == slack_id) ] pt = PrettyTable(['ユーザー名', 'Slack ID', 'エイリアス名']) alias_name = ','.join(alias_names) pt.add_row([user_name, slack_id, alias_name]) botsend(message, '```{}```'.format(pt))
def choice(message, words): """指定したキーワードから一つを選んで返す """ words = words.split() if len(words) == 1: botsend(message, 'キーワードを複数指定してください\n`$choice word1 word2...`') else: botsend(message, random.choice(words))
def shuffle(message, words): """指定したキーワードをシャッフルして返す """ words = words.split() if len(words) == 1: botsend(message, 'キーワードを複数指定してください\n`$shuffle word1 word2...`') else: random.shuffle(words) botsend(message, ' '.join(words))
def update_thx(message): """指定したSlackのユーザーにGJを行う OK: user_name++ hoge user_name ++ hoge user_name ++ hoge @user_name++ hoge user_name user_name ++ hoge NG: user_name+ + hoge user_name+++ hoge user_name++hoge user_name,user_name ++ hoge :param message: slackbot.dispatcher.Message :param str user_name: ++するユーザー名 :param str word: GJの内容 """ # refs #113 ファイルアップロード時のタイトル名に++が含まれると、応答しないようにする if "subtype" in message.body and message.body["subtype"] == "file_share": return from_user_id = message.body['user'] channel_id = message.body['channel'] text = message.body['text'] s = Session() user_dict, hint_names, not_matched = find_thx(s, text) msg = [] if user_dict: for word, users in user_dict.items(): for slack_id, name in users: s.add( ThxHistory(user_id=slack_id, from_user_id=from_user_id, word=word, channel_id=channel_id)) s.commit() count = (s.query(ThxHistory).filter( ThxHistory.channel_id == channel_id).filter( ThxHistory.user_id == slack_id).count()) msg.append('{}({}: {}GJ)'.format(word, name, count)) if hint_names: for hint_name in hint_names: msg.append('もしかして: `{}`'.format(hint_name)) if not_matched: for name in not_matched: msg.append('{}はSlackのユーザーとして存在しません'.format(name)) botsend(message, '\n'.join(msg))
def del_command(message, command_name, command=None): """コマンドを削除する :param message: slackbot.dispatcher.Message :param str create_command: 削除するコマンド名 """ s = Session() s.query(CreateCommand).filter(CreateCommand.id == command.id).delete() s.commit() botsend(message, '`${}`コマンドを削除しました'.format(command_name))
def amesh(message): # ameshでは5分ごとにデータが作成されるため、桁を揃えてからリクエストしたい # 同じ画像をリクエストすることありそうなのでキャッシュいれたほうがよさそうだけど、 # 現状不便ないので特に対応をいれていない n = datetime.now() yyyymmddhh = n.strftime("%Y%m%d%H") mm = "{:02d}".format(n.minute // 5 * 5) # 雑リトライ for i in range(1, MAX_RETRY_NUM + 1): logger.info("amesh:: trying ... {}/{}".format(i, MAX_RETRY_NUM)) try: # 画像の合成 # 000 はエリアごとの固定値で050,100,150があるけど決め打ちで with _get_image("http://tokyo-ame.jwa.or.jp/map/msk000.png") as image_msk, _get_image( "http://tokyo-ame.jwa.or.jp/map/map000.jpg" ) as image_map, _get_image( "http://tokyo-ame.jwa.or.jp/mesh/000/{}{}.gif".format(yyyymmddhh, mm) ) as image_weather: merged = Image.alpha_composite(image_map, image_weather) merged2 = Image.alpha_composite(merged, image_msk) # slack にアップロードするために一時的にtmpfileに書き出す with tempfile.NamedTemporaryFile() as tmp: name = "{}{}.png".format(yyyymmddhh, mm) tmpname = "{}.png".format(tmp.name) merged2.save(tmpname) # せっかくなので天気もみれるようにしてる comment = "時刻: {:%Y年%m月%d日 %H}:{}\n".format(n, mm) + \ "公式: http://tokyo-ame.jwa.or.jp/\n" # 外部サイトに投稿してURLを貼る方法(S3とか)だとaccesskey設定等いるのでslackに直接アップロード sc = message._client.webapi sc.files.upload( file_=tmpname, initial_comment=comment, title=name, filename=name, channels=message.channel._body["name"], ) except Exception: logger.exception("amesh exception") if i == MAX_RETRY_NUM: botsend(message, "{}回目: だめでした…またためしてね".format(i)) else: botsend(message, "{}回目: だめでした…もういちどみてきます".format(i)) # 特定の分数(5で割れる分?)だと取得できないことが多いので、30秒ずつ空けてみてる time.sleep(1 * 60 / 2) else: break
def add_command(message, command_name): """新たにコマンドを作成する :param message: slackbot.dispatcher.Message :param str command: 登録するコマンド名 """ s = Session() s.add(CreateCommand(name=command_name, creator=message.body['user'])) s.commit() botsend(message, '`${}`コマンドを登録しました'.format(command_name))
def count_redbull_stock(message): """現在のRedBullの在庫本数を返すコマンド :param message: slackbotの各種パラメータを保持したclass """ s = Session() q = s.query(func.sum(RedbullHistory.delta).label('stock_number')) stock_number = q.one().stock_number if stock_number is None: stock_number = 0 botsend(message, 'レッドブル残り {} 本'.format(stock_number))
def show_today_cleaning_list(message): """今日の掃除当番を表示する :param message: slackbot.dispatcher.Message """ dow = datetime.datetime.today().weekday() s = Session() users = [get_user_display_name(c.slack_id) for c in s.query(Cleaning).filter(Cleaning.day_of_week == dow)] botsend(message, '今日の掃除当番は{}です'.format('、'.join(users)))
def show_kintai_history_csv(message, time=None): """指定した月の勤怠記録をCSV形式で返す :param message: slackbotの各種パラメータを保持したclass :param str time: `/` 区切りの年月(例: 2016/1) """ user_id = message.body['user'] if time: year_str, month_str = time.split('/') else: now = datetime.datetime.now() year_str, month_str = now.strftime('%Y'), now.strftime('%m') year, month = int(year_str), int(month_str) if not 1 <= month <= 12: botsend(message, '指定した対象月は存在しません') return s = Session() qs = (s.query(KintaiHistory).filter( KintaiHistory.user_id == user_id).filter( func.extract('year', KintaiHistory.registered_at) == year).filter( func.extract('month', KintaiHistory.registered_at) == month)) kintai = defaultdict(list) for q in qs: registered_at = q.registered_at.strftime('%Y-%m-%d') kintai[registered_at].append( (q.is_workon, '{:%H:%M:%S}'.format(q.registered_at))) rows = [] for day in range(1, monthrange(year, month)[1] + 1): aligin_date = '{}-{:02d}-{:02d}'.format(year, month, day) workon, workoff = '', '' for d in sorted(kintai[aligin_date]): if d[0]: workon = d[1] else: workoff = d[1] rows.append([aligin_date, workon, workoff]) output = StringIO() w = csv.writer(output) w.writerows(rows) param = { 'token': settings.API_TOKEN, 'channels': message.body['channel'], 'title': '勤怠記録' } requests.post(settings.FILE_UPLOAD_URL, params=param, files={'file': output.getvalue()})
def show_lunch(message, keyword=None, distance=500): """Lunchコマンドの結果を表示する :param message: slackbot.dispatcher.Message :param keyword: 検索キーワード :param distance: 検索範囲 (default 500m) """ if keyword == 'help': return distance = int(distance) botsend(message, lunch(keyword, distance))
def return_term(message, command_name, command=None): """コマンドに登録されている語録をランダムに返す :param message: slackbot.dispatcher.Message :param str command: 語録が登録されているコマンド名 """ if command_name: if command.terms: words = [term.word for term in command.terms] word = random.choice(words) botsend(message, word) else: botsend(message, '`${}`コマンドにはまだ語録が登録されていません'.format(command_name))
def cleaning_move(message, user_name, day_of_week): """登録された掃除当番のユーザーの掃除曜日を移動させる :param message: slackbot.dispatcher.Message :param str user_name: 掃除当番の曜日を移動させるユーザー名 :param str day_of_week: 移動先の曜日名 """ if day_of_week not in DAY_OF_WEEK: botsend(message, '曜日には `月` 、 `火` 、 `水` 、 `木` 、 `金` のいずれかを指定してください') return s = Session() slack_id = get_slack_id(s, user_name) if slack_id is None: botsend(message, '{}はSlackのユーザーとして存在しません'.format(user_name)) return cleaning_user = (s.query(Cleaning).filter( Cleaning.slack_id == slack_id).one_or_none()) if not cleaning_user: botsend(message, '{}は掃除当番に登録されていません'.format(user_name)) return cleaning_user.day_of_week = DAY_OF_WEEK.index(day_of_week) s.commit() botsend(message, '{}の掃除当番の曜日を{}曜日に変更しました'.format(user_name, day_of_week))
def cleaning_del(message, user_name, day_of_week): """指定した曜日の掃除当番からユーザーを削除する :param message: slackbot.dispatcher.Message :param str user_name: 掃除当番から削除するユーザー名 :param str day_of_week: 削除する掃除当番が登録されている曜日 """ if day_of_week not in DAY_OF_WEEK: botsend(message, '曜日には `月` 、 `火` 、 `水` 、 `木` 、 `金` のいずれかを指定してください') return s = Session() slack_id = get_slack_id(s, user_name) if not slack_id: botsend(message, '{}はSlackのユーザーとして存在しません'.format(user_name)) return cleaning_user = (s.query(Cleaning).filter( Cleaning.slack_id == slack_id).filter( Cleaning.day_of_week == DAY_OF_WEEK.index( day_of_week)).one_or_none()) if cleaning_user: s.delete(cleaning_user) s.commit() botsend(message, '{}を{}曜日の掃除当番から削除しました'.format(user_name, day_of_week)) else: botsend(message, '{}は{}曜日の掃除当番に登録されていません'.format(user_name, day_of_week))
def cleaning_add(message, user_name, day_of_week): """指定した曜日の掃除当番にユーザーを追加する :param message: slackbot.dispatcher.Message :param str user_name: 掃除当番に登録するユーザー名 :param str day_of_week: 追加する掃除曜日 """ if day_of_week not in DAY_OF_WEEK: botsend(message, '曜日には `月` 、 `火` 、 `水` 、 `木` 、 `金` のいずれかを指定してください') return s = Session() slack_id = get_slack_id(s, user_name) if not slack_id: botsend(message, '{}はSlackのユーザーとして存在しません'.format(user_name)) return q = s.query(Cleaning).filter(Cleaning.slack_id == slack_id) if s.query(q.exists()).scalar(): botsend(message, '{}は既に登録されています'.format(user_name, day_of_week)) return s.add( Cleaning(slack_id=slack_id, day_of_week=DAY_OF_WEEK.index(day_of_week))) s.commit() botsend(message, '{}を{}曜日の掃除当番に登録しました'.format(user_name, day_of_week))
def random_command(message, subcommand=None): """ チャンネルにいるメンバーからランダムに一人を選んで返す - https://github.com/os/slacker - https://api.slack.com/methods/channels.info - https://api.slack.com/methods/users.getPresence - https://api.slack.com/methods/users.info """ if subcommand == 'help': botsend(message, HELP) return # チャンネルのメンバー一覧を取得 channel = message.body['channel'] webapi = slacker.Slacker(settings.API_TOKEN) try: cinfo = webapi.channels.info(channel) members = cinfo.body['channel']['members'] except slacker.Error: try: cinfo = webapi.groups.info(channel) members = cinfo.body['group']['members'] except slacker.Error: # TODO: 例外で判定しないように修正する # チャンネルに紐付かない場合はreturn return # bot の id は除く bot_id = message._client.login_data['self']['id'] members.remove(bot_id) member_id = None if subcommand != 'active': member_id = random.choice(members) else: # active が指定されている場合は presence を確認する random.shuffle(members) for member in members: presence = webapi.users.get_presence(member_id) if presence.body['presence'] == 'active': member_id = member break user_info = webapi.users.info(member_id) name = user_info.body['user']['name'] botsend(message, '{} さん、君に決めた!'.format(name))
def get_term(message, command): """コマンドに登録されている語録の一覧を返す :param message: slackbot.dispatcher.Message :param str command: 登録済のコマンド名 """ name = command.name if command.terms: msg = ['コマンド `${}` の語録は {} 件あります'.format(name, len(command.terms))] for t in command.terms: msg.append(t.word) botsend(message, '\n'.join(msg)) else: msg = ('コマンド `${0}` には語録が登録されていません\n' '`${0} add (語録)` で語録を登録してください'.format(name)) botsend(message, msg)
def del_term(message, command, word): """コマンドから語録を削除する :param message: slackbot.dispatcher.Message :param str command: 登録済のコマンド名 :param str word: 削除する語録 """ s = Session() term = (s.query(Term).filter(Term.create_command == command.id).filter( Term.word == word).one_or_none()) name = command.name if term: s.delete(term) s.commit() botsend(message, 'コマンド `${}` から「{}」を削除しました'.format(name, word)) else: botsend(message, 'コマンド `${}` に「{}」は登録されていません'.format(name, word))
def update_kudo(message, names): """ 指定された名前に対して ++ する OK: name++、name ++、name ++、@name++、name1 name2++ NG: name+ +、name++hoge、 name1,name2++ :param message: slackbot.dispatcher.Message :param name str: ++する対象の名前 """ slack_id = message.body['user'] name_list = [] for name in [x for x in names.split(' ') if x]: # slackのsuggest機能でユーザーを++した場合(例: @wan++)、name引数は # `<@{slack_id}>` というstr型で渡ってくるので対応 if get_user_name(name.lstrip('<@').rstrip('>')): name = get_user_name(name.lstrip('<@').rstrip('>')) s = Session() kudo = (s.query(KudoHistory) .filter(KudoHistory.name == name) .filter(KudoHistory.from_user_id == slack_id) .one_or_none()) if kudo is None: # name ×from_user_id の組み合わせが存在していない -> 新規登録 s.add(KudoHistory(name=name, from_user_id=slack_id, delta=1)) s.commit() else: # name ×from_user_id の組み合わせが存在 -> 更新 kudo.delta = kudo.delta + 1 s.commit() q = (s.query( func.sum(KudoHistory.delta).label('total_count')) .filter(KudoHistory.name == name)) total_count = q.one().total_count name_list.append((name, total_count)) msg = ['({}: 通算 {})'.format(n, tc) for n, tc in name_list] botsend(message, '\n'.join(msg))
def show_cleaning_list(message): """掃除当番の一覧を表示する :param message: slackbot.dispatcher.Message """ s = Session() dow2users = OrderedDict() cleaning = s.query(Cleaning).order_by(Cleaning.day_of_week.asc(), Cleaning.id.asc()) for c in cleaning: user = get_user_display_name(c.slack_id) dow2users.setdefault(c.day_of_week, []).append(user) pt = PrettyTable(['曜日', '掃除当番']) pt.align['掃除当番'] = 'l' for day_of_week, users in dow2users.items(): dow = DAY_OF_WEEK[day_of_week] str_users = ', '.join(users) pt.add_row([dow, str_users]) botsend(message, '```{}```'.format(pt))
def pop_term(message, command): """用語コマンドで最後に登録された応答を削除する :param message: slackbot.dispatcher.Message :param str command: 登録済のコマンド名 """ s = Session() term = (s.query(Term).filter(Term.create_command == command.id).filter( Term.creator == message.body['user']).order_by(Term.id.desc()).first()) name = command.name if term: s.delete(term) s.commit() botsend(message, 'コマンド `${}` から「{}」を削除しました'.format(name, term.word)) else: msg = ('コマンド `${0}` にあなたは語録を登録していません\n' '`${0} add (語録)` で語録を登録してください'.format(name)) botsend(message, msg)
def manage_redbull_stock(message, delta): """RedBullの本数の増減を行うコマンド :param message: slackbotの各種パラメータを保持したclass :param str delta: POSTされた増減する本数 UserからPOSTされるdeltaの値は投入の場合は負数、消費の場合は正数 DBは投入の場合正数、消費の場合は負数を記録する """ delta = -int(delta) user_id = message.body['user'] user_name = get_user_name(user_id) s = Session() s.add(RedbullHistory(user_id=user_id, delta=delta)) s.commit() if delta > 0: botsend(message, 'レッドブルが{}により{}本投入されました'.format(user_name, delta)) else: botsend(message, 'レッドブルが{}により{}本消費されました'.format(user_name, -delta))
def show_user_redbull_history(message): """RedBullのUserごとの消費履歴を返すコマンド :param message: slackbotの各種パラメータを保持したclass """ user_id = message.body['user'] user_name = get_user_name(user_id) s = Session() qs = (s.query(RedbullHistory).filter(RedbullHistory.user_id == user_id, RedbullHistory.delta < 0).order_by( RedbullHistory.id.asc())) tmp = [] for line in qs: tmp.append('[{:%Y年%m月%d日}] {}本'.format(line.ctime, -line.delta)) ret = '消費履歴はありません' if tmp: ret = '\n'.join(tmp) botsend(message, '{}の消費したレッドブル:\n{}'.format(user_name, ret))
def count_water_stock(message): """現在の水の在庫本数を返すコマンド :param message: slackbotの各種パラメータを保持したclass """ s = Session() stock_number, latest_ctime = (s.query( func.sum(WaterHistory.delta), func.max( case(whens=((WaterHistory.delta != 0, WaterHistory.ctime), ), else_=None))).first()) if stock_number: # SQLiteの場合文字列で渡ってくるので対応 if not isinstance(latest_ctime, datetime.datetime): latest_ctime = datetime.datetime.strptime(latest_ctime, '%Y-%m-%d %H:%M:%S') botsend(message, '残数: {}本 ({:%Y年%m月%d日} 追加)'.format(stock_number, latest_ctime)) else: botsend(message, '管理履歴はありません')
def show_water_history(message, limit='10'): """水の管理履歴を返すコマンド :param message: slackbotの各種パラメータを保持したclass """ s = Session() qs = (s.query(WaterHistory).order_by(WaterHistory.id.desc()).limit(limit)) tmp = [] for line in qs: if line.delta > 0: tmp.append('[{:%Y年%m月%d日}] {}本 追加'.format(line.ctime, line.delta)) else: tmp.append('[{:%Y年%m月%d日}] {}本 取替'.format(line.ctime, -line.delta)) ret = '管理履歴はありません' if tmp: ret = '\n'.join(tmp) botsend(message, '水の管理履歴:\n{}'.format(ret))
def add_term(message, command, word): """コマンドに語録を追加する :param message: slackbot.dispatcher.Message :param str command: 登録済のコマンド名 :param str word: 登録する語録 """ s = Session() term = (s.query(Term) .select_from(join(Term, CreateCommand)) .filter(CreateCommand.id == command.id) .filter(Term.word == word) .one_or_none()) name = command.name if term: botsend(message, 'コマンド `${}` に「{}」は登録済みです'.format(name, word)) else: s.add(Term(create_command=command.id, creator=message.body['user'], word=word)) s.commit() botsend(message, 'コマンド `${}` に「{}」を追加しました'.format(name, word))
def unalias_name(message, user_name, alias_name=None): """ユーザーに紐づくエイリアス名を削除する :param message: slackbotの各種パラメータを保持したclass :param str user_name: 削除するエイリアス名を持つSlackユーザー :param str alias_name: 削除するエイリアス名 alias_nameがNoneの場合、user_nameをalias_nameとして扱う 上記の場合user_nameは投稿者となる """ if alias_name: # ユーザー名とエイリアス名が指定されているパターン slack_id = get_slack_id_by_name(user_name) else: # 投稿者のエイリアス名を更新するパターン alias_name = user_name slack_id = message.body['user'] user_name = get_user_name(slack_id) if not slack_id: botsend(message, '{}に紐づくSlackのuser_idは存在しません'.format(user_name)) return s = Session() alias_user_name = (s.query(UserAliasName).filter( UserAliasName.slack_id == slack_id).filter( UserAliasName.alias_name == alias_name).one_or_none()) if alias_user_name: s.delete(alias_user_name) s.commit() botsend(message, '{}のエイリアス名から `{}` を削除しました'.format(user_name, alias_name)) else: botsend(message, '{}のエイリアス名 `{}` は登録されていません'.format(user_name, alias_name))
def clear_redbull_history(message, token=None): """RedBullの履歴データを削除するコマンド `$redbull clear` のみだと削除するためのトークンを生成して表示する `$redbull clear <表示されたトークン>` をPOSTすると redbull_historyテーブルのレコード全削除を行う :param message: slackbotの各種パラメータを保持したclass :param str token: `$redbull clear` の後に入力されたトークン """ if token is None: _cache['token'] = ''.join(choice(ascii_letters) for i in range(16)) botsend( message, '履歴をDBからすべてクリアします。' '続けるには\n`$redbull clear {}`\nと書いてください'.format(_cache['token'])) return if token == _cache['token']: _cache['token'] = None s = Session() s.query(RedbullHistory).delete() s.commit() botsend(message, '履歴をクリアしました') else: botsend(message, 'コマンドが一致しないため履歴をクリアできませんでした')