Help us understand the problem. What is going on with this article?

社内slackにVIPチャンネルを作った話

ABEJA Advent Calendarの1日目です。

はじめに

昨年はABEJA Platformに関するAdvent Calendarでしたが、今年はプラットフォームに限らず幅広い技術を扱おう、ということで縛りを作らずに様々な技術を紹介していきます。

さて、皆さん、社内でのコミュニケーションツールは何をお使いでしょうか。色々なツールがあると思いますが、Slackを使っている所が多いのではないかと思います。Slackはとても良いツールなのですが、使いこなす会社側にその運用ルールが委ねられています。中でも、DMやプライベートチャンネルでの秘密の会話による情報格差などが発生することが問題になり、オープンチャンネルに限定している会社も多いのではないでしょうか。しかしながら、オープンに会話をすれば、皆が平等かつ平和に会話ができるか?というと、全くそんなことはありません。オープンにすると下記のような問題が発生します

問題1:心理的安全性が確保されない

様々な技術力、ポジションの人がひしめく社内において、HTMLって何?なんて聞いた日には、こいつマジ技術ないわ、みたいに思われてしまうのではないでしょうか。その結果、自身のブランドを守るために、知ったかぶりをしつつ、後ろで一生懸命検索をする、そんなことはないでしょうか。また、大多数に向けて責任を持って発言する必要があり、いい天気ですねばりの当たり障りのない会話になってしまうと思います。

問題2:会話の参加者の立ち位置によるパワーバランス

社内の議論の中で、急にスーパー偉い人が参加してきて、「これがいいのでは?」なんて言ったら、多くの人が従ってしまうのではないでしょうか(弊社では、そんなことはない)。もちろんポジションだけではなく技術面での強さによっても発生します。僕もきっとJeff Deanに「この方法が良いんじゃね?」なんて言われたら反論できない。

問題3:素の自分を出せない

自身の社内におけるイメージというものがあるから、オブラートに包みまくった会話しかできず、あれ?これをいいたかったんだっけ?みたいに変わってしまったりすることがありますよね。

Slackに匿名チャンネルを開設してみた

上記を解決するべく、下記のような匿名チャンネルを作りました。Slackコマンドで"/2ch hogehoge"と入れると、匿名でhogehogeと投稿されます。チャンネル内で打つとtypingって出て匿名性が失われるので、別のチャンネルでコマンドを打てば完全に匿名化出来るぞ。なお、セキュリティとかは全く考えてないので、その辺はよしなにやって下さい。

image.png

懸念ポイント

匿名チャンネルを作ると治安が悪くなるのでは?という懸念があるかと思います。これについては面白いもので、今の所全く荒れないで現状運用できています。理由としてはいくつか考えられますが、

  • 社内の管理人(僕)がその気になれば発言者を特定できる
  • 相手が社内のメンバーなので、むやみに荒らしたくはない
  • 荒らしたら負け

などでしょうか。とにかく平和です。

作ってみよう!

全体の仕組み

仕組み自体は非常にシンプルでコマンドからメッセージが送られると、Lambdaの関数が実行され、関数内でpostMessage APIでメッセージを投稿するというものです。この際にLambdaの関数自体は何もメッセージを返さない事で匿名の投稿が実現できます。

botの作成

Slack APIのサイトからBotを作っていきましょう!

image.png

Slack Bot自体の詳細の作り方は色々な所に解説があるので、ここでは割愛しますが、

  • Add features and functionality
    • Slash Commands
      • Command: /2ch
      • Request URL: 後ほど作るLambdaのAPI Gateway
    • Permissions
      • Scopes
        • chat:write:bot: On
        • commands: On

のように設定します。

Lambdaの準備

こちらも、色々な所に解説が乗っているので、Lambda+API Gatewayの使い方は割愛します。Slackとの接続も、POSTメソッドを受け付けるようにするだけですので、特に特殊なことはしません。唯一の注意点としては、BotからSlack APIを叩くために、requestsモジュールを使えるようにしたいです。Lambda Layerを使ってrequests入りのレイヤーを作っておきましょう。

最もシンプルなバージョン

はい、ドーン。

import os
import json
import urllib.parse
import requests

def lambda_handler(event, context):
    qs_d = urllib.parse.parse_qs(event['body'])

    access_token = os.environ['ACCESSTOKEN']
    channel_id = os.environ['CHANNEL_ID']

    headers = {
        'Authorization': 'Bearer {}'.format(access_token),
        'Content-Type': 'application/json; charset=utf-8'
    }

    text = qs_d['text'][0]

    data = {
        "channel": channel_id,
        "text": text,
        "username": '以下、名無しにかわりましてVIPがお送りします'
    }

    json_data = json.dumps(data).encode("utf-8")

    ret = requests.post('https://slack.com/api/chat.postMessage', 
                        data=json_data, headers=headers)
    status_code = ret.status_code
    body = ret.content.decode('utf-8')

    return {
        'statusCode': status_code,
    }

bodyにクエリ文字列(google検索した時にURLの後ろについているやつ)に、各種情報が埋め込まれているので、urllibを使ってパースしましょう。textフィールドに本文が入っています。ACCESSTOKENにはBotのトークン情報を、CHANNEL_IDにはチャンネルIDを入れます。あとは、SlackAPIのchat.postMessageを叩くだけ。まずは匿名チャットができたぞ。

結果

image.png

メッセージに番号とIDを付けよう

さて、これだけでは前のメッセージとつながってしまい、読みづらいですし、特定のメッセージへのレスポンスが難しいです。それに2chといえば、やっぱ番号がないとね。

しかしながらLambdaはステートを持たないため、書き込み番号を持つことが出来ません。そこで今回は、
Systems Manager パラメータストアを利用してみました。

使い方としては、下記のようにcalc_countを呼び出すと、counterキーで保持された書き込み番号が読み込まれ、インクリメントして保存し直します。これを名前に入れましょう。

ssm = boto3.client('ssm')

def get_param(key: str) -> str:
    global ssm
    try:
        return ssm.get_parameter(Name=key)['Parameter']['Value']
    except ssm.exceptions.ParameterNotFound:
        return None

def set_param(key: str, value: str):
    global ssm
    ssm.put_parameter(Name=key, Value=value, Type='String', Overwrite=True)

def calc_count():
    key = 'counter'
    count = int(get_param(key) or 0)
    if count > 1000:
        count = 0
    count += 1
    set_param(key, str(count))
    return count

さらに、誰が発言したかが分からないと会話が難しいということで、2chの仕様に従い、日毎にIDを振りましょう。IDは、Slackのユーザ名と日付からハッシュ値を作成するだけ。

user_id = qs_d['user_id'][0]
hashed_id = hashlib.md5((str(datetime.date.today()) + user_id).encode()).hexdigest()[:9]

最後にメッセージを作成します。

message_id = calc_count()

title_template = "{message_id} 名前:以下、VIPがお送りします :{date} ID:{hashed_id}"
username = title_template.format(message_id=message_id, date=datetime.datetime.today(), hashed_id=hashed_id)

結果

image.png

コテハン&トリップ

次にやはり必要なのはコテハンとトリップでしょう(下図)。詳細についてはググって下さい。

image.png

2chであれば、名前の欄に「コテハン#トリップ」と入れれば良かったけど、ここはSlack、とりあえず、コメントを[コテハン#トリップ]で開始すると、コテハンが作られる事にしよう。苦手な正規表現で頑張ってみる。下記のようにすれば、上記がパースできそう(もっと良い方法ないかな)。

ptn = '(\[([^#]+)(\#([a-zA-Z0-9]+))?\])?(.*)'
ret = re.match(ptn, text, flags=(re.MULTILINE | re.DOTALL))

username = ret.group(2)
password = ret.group(4)
main_text = ret.group(5)

さらに、トリップ偽装する輩がいないとも限らないので、対策も入れて下記のように名前を実装します。

if username is not None and password is not None:
    hashed_name = hashlib.md5(password.encode()).hexdigest()[:8]
    default_name = username[:8] + '◆' + hashed_name
    hashed_id = '????????'
elif username is not None:
    default_name = username[:16].replace('◆', '◇')

username = title_template.format(message_id=message_id, 
                                 name=default_name, 
                                 date=now_str,
                                 hashed_id=hashed_id)

結果

こんな感じにコテハンとトリップが入れられて・・・

image.png

こんな感じで偽装対策がされています。

image.png

fusianasan

やはり、2chといえばフシアナさんでしょう。これで多くのユーザを嵌め込みましょう。作り方は簡単。ユーザ名がfusianasanだったら、user_idを表示してやります。

if username == 'fusianasan':
    default_name = user_id

結果

さすがにIDは公開出来ないけど・・・。

image.png

ヘルプ機能

やはり使い方が分からなくなることも多く、管理人としてヘルプを作ることにしました。helpと入力すると・・・。

image.png

↑こんな感じになる。裏サイトへ飛ぼうとする人がいたらIDがバレる仕組みです。なお、1ヶ月運用しているけど誰も裏サイトに飛んだ人はいない。

その他

色々やっているけど、

  • 書き込んだユーザーのログ情報はCloudWatchに一応保存している
    • DynamoDBに入れようかと思ったけど、こんなんで課金されても勿体ないのでやっていない
  • 自動デプロイ
    • GithubにコードをプッシュするとApexで自動的にデプロイされるぞ、便利!

運用中の事故?

ログが保存されない!

Apexに切り替えた時に、設定をミスったのでCloudWatchにログが保存されなかったようで、急に2chになった。

image.png

攻撃される

社内で遊びでサービスを作ると必ず何かを仕掛けてくる奴がいる。

image.png

・・・というわけでお陰様で平和に治安維持されています。

他社比較

世の中には似たものを考える人がいるようで、匿名チャンネルはあるようでした。このチャンネルではメッセージをチャンネルに入れると、削除されてbotから再投稿される仕組みのようです。しかしながら、typingが見えてしまう、一瞬書き込みが見えるなど、我々のような匿名性を担保することは出来ません。

image.png

ということで、機能を比較すると下記のようになります!圧倒的ではないか我軍は。

image.png

おわりに

アドベントカレンダー遅れて申し訳ありませんでした。かくなる上は半年ROMります。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away