はじめに
こんにちは、
皆さん仕事であったりプライベートであったり、普段からSlackを利用していますか?
筆者もSlackを仕事で利用することが多いです。
そんなSlackユーザになると利用したくなるのがカスタム絵文字ですね。
筆者の会社でも以下のように、さまざまなカスタム絵文字が日常的に利用されています。
しかし、ときには心無いユーザによって不愉快なカスタム絵文字が作成されているかもしれません!
皆さんもそんな経験ありませんか?
そこで今回はそんな荒れたSlackカスタム絵文字社会の秩序をGoogle Cloud Vision APIを利用して正すことを考えました。
Google Cloud Vision API
Google Cloud Vision APIは、言わずと知れたGoogle CloudのAPIサービスになります。APIを利用することによって画像内のオブジェクトを検出したり、画像内のテキスト情報を抽出したりすることができます。
実際にQuickstartでサンプル画像に対してAPIを実行すると以下のように解析してくれます。画像内の犬を検出してくれているのがわかります。
さて、上記のようにVision APIでは画像に対していろいろな解析をすることができますが、その中でも**セーフサーチ検出**という機能があります。セーフサーチ検出ではアダルトコンテンツや暴力的コンテンツなど、画像に含まれる不適切なコンテンツを検出してくれる機能となります。
実際にAPIで問い合わせを行うと以下のように、adult,violenceなどの観点に関して、対象の画像が不正コンテンツであるかの度合いを返します。
{
"responses": [
{
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "VERY_LIKELY",
"medical": "UNLIKELY",
"violence": "UNLIKELY"
}
}
]
}
不正コンテンツの度合い(Likelihood)は以下の6段階に分類されます(以下Vision API Document参照)。
レベル | 説明 |
---|---|
UNKNOWN | Unknown likelihood. |
VERY_UNLIKELY | It is very unlikely that the image belongs to the specified vertical. |
UNLIKELY | It is unlikely that the image belongs to the specified vertical. |
POSSIBLE | It is possible that the image belongs to the specified vertical. |
LIKELY | It is likely that the image belongs to the specified vertical. |
VERY_LIKELY | It is very likely that the image belongs to the specified vertical. |
今回は、このセーフサーチ検出の機能をカスタム絵文字に適用することによって、不正な絵文字を検出することを考えていきます。
事前準備
今回はPythonを利用して実装を行なっていきます。事前にSlack APIのトークン情報と、Vision APIのクライアントライブラリの取得を行います。
Slack APIのトークン
Slack APIトークンは以下を参考に生成しておきます。
API トークンの生成と再生成
Vision APIクライアント
PythonのVision APIクライアントは以下のサイトの手順を参考にセットアップしておきます。
Vision API Client Libraries
実装
今回の実装は以下を参考に、Vision APIの問い合わせ機能を追加しながら実装しました。
https://github.com/jkloo/slack-emojis
以下が実装コード(https://github.com/fuku68/slack_emoji_auditor)。
# coding:utf-8
import os
import argparse
import requests
import logging
from google.cloud import vision
from google.cloud.vision import types
logging.basicConfig(level=logging.INFO, format='%(message)s')
SLACK_URL = 'https://{slack}.slack.com/api/emoji.list?token={token}'
RESPONSE_TYPE = ('adult', 'spoof', 'medical', 'violence', 'racy')
LIKEHOOD_NAME = ('UNKNOWN', 'VERY_UNLIKELY', 'UNLIKELY', 'POSSIBLE','LIKELY', 'VERY_LIKELY')
def main(args):
# formatting result
result = {}
for type in RESPONSE_TYPE:
result[type] = {}
for name in LIKEHOOD_NAME:
result[type][name] = []
result[type][name + '_COUNT'] = 0
res = requests.get(SLACK_URL.format(slack=args.slack, token=args.token))
# Google Cloud Vision API Client
client = vision.ImageAnnotatorClient()
image = types.Image()
if res.status_code == 200:
emoji = res.json().get('emoji', {})
for k, v in emoji.items():
image.source.image_uri = v
logging.info('requesting... safe_search emoji: ' + k)
gooleo_res = client.safe_search_detection(image=image)
safe = gooleo_res.safe_search_annotation
result['adult'][LIKEHOOD_NAME[safe.adult]].append(k)
result['adult'][LIKEHOOD_NAME[safe.adult] + "_COUNT"] += 1
result['spoof'][LIKEHOOD_NAME[safe.spoof]].append(k)
result['spoof'][LIKEHOOD_NAME[safe.spoof] + "_COUNT"] += 1
result['medical'][LIKEHOOD_NAME[safe.medical]].append(k)
result['medical'][LIKEHOOD_NAME[safe.medical] + "_COUNT"] += 1
result['violence'][LIKEHOOD_NAME[safe.violence]].append(k)
result['violence'][LIKEHOOD_NAME[safe.violence] + "_COUNT"] += 1
result['racy'][LIKEHOOD_NAME[safe.racy]].append(k)
result['racy'][LIKEHOOD_NAME[safe.racy] + "_COUNT"] += 1
result_print(result)
else:
logging.warning("can't get emoji list")
def result_print(result):
print('----------- result ---------------')
for type in RESPONSE_TYPE:
print(type + ':')
print(' VERY_LIKELY: ' + unicode_array_to_str(result[type]['VERY_LIKELY']))
print(' LIKELY: ' + unicode_array_to_str(result[type]['LIKELY']))
print(' POSSIBLE: ' + unicode_array_to_str(result[type]['POSSIBLE']))
print('')
print(' statistics:')
for name in LIKEHOOD_NAME:
print(' ' + '{0:>13}'.format(name) + ': ' + str( result[type][name + '_COUNT']))
print('')
def unicode_array_to_str(array):
str = ', '.join(array)
return '[' + str + ']'
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('slack', help='name of your slack group.')
parser.add_argument('token', help='auth token for slack.')
args = parser.parse_args()
main(args)
それぞれの観点に関してPOSSIBLE、LIKELY、VERY_LIKELYに対応する絵文字と、それぞれのLikelihoodの統計情報を出力するように実装しました。
コマンドラインから以下の記述で実行します。
python auditor.py <Slackワークスペース名> <Slack APIトークン>
出力結果は以下。
---------- result ---------------
adult:
VERY_LIKELY: []
LIKELY: []
POSSIBLE: []
statistics:
UNKNOWN: 4
VERY_UNLIKELY: 364
UNLIKELY: 20
POSSIBLE: 0
LIKELY: 0
VERY_LIKELY: 0
spoof:
VERY_LIKELY: [trollface, idobata_02, dodo_02, miyata_02]
LIKELY: [utsunomiya_01, yakyu_01, eyes2]
POSSIBLE: [utsunomiya_02, panda, tagawa, gudetama_03, ryoukai, tennen, daijobu, misawa_04, misawa_05, siba01, taiko_01, kurara]
statistics:
UNKNOWN: 4
VERY_UNLIKELY: 319
UNLIKELY: 46
POSSIBLE: 12
LIKELY: 3
VERY_LIKELY: 4
medical:
VERY_LIKELY: []
LIKELY: []
POSSIBLE: [thumbsup_all, fu, ee, kesseki, maguro_04, metal, funa, wakaru, kupo, elixir]
statistics:
UNKNOWN: 4
VERY_UNLIKELY: 331
UNLIKELY: 43
POSSIBLE: 10
LIKELY: 0
VERY_LIKELY: 0
violence:
VERY_LIKELY: []
LIKELY: []
POSSIBLE: []
statistics:
UNKNOWN: 4
VERY_UNLIKELY: 360
UNLIKELY: 24
POSSIBLE: 0
LIKELY: 0
VERY_LIKELY: 0
racy:
VERY_LIKELY: [yamamoto_maika, yoga_2, ee, kusu, maguro_04, haggar2, kotowaru]
LIKELY: [vega, fu, haggar, yuriko, dodo_08, piggy, ogawa, misawa_03, misawa_05, elixir, kurara]
POSSIBLE: [negi, cody, 天馬くん, bowtie, miyata_01, bdash2, dodo_05, metal, nandato, arai_01, fs-e-hiroaki-idobata, kesu, penguins, funa, kupo, yareyare, glitch_crab, dead, misawa_04, masaru-san, char, conan, matsuda_01, dusty_stick, はると]
statistics:
UNKNOWN: 4
VERY_UNLIKELY: 243
UNLIKELY: 98
POSSIBLE: 25
LIKELY: 11
VERY_LIKELY: 7
ヒットしたら面白そうなadult
やviolence
では対応する絵文字がありませんでした...(期待はずれ)。
ちなみにspoof
のVERY_LIKELYと認識されたのは以下の絵文字たち
なんか顔が多い...
またracy
のVERY_LIKELYと認識されたのは以下の絵文字たち
こちらの基準はさっぱり...
最後に
今回はSlackのカスタム絵文字に対してVision APIを利用して、コンテンツとして不正なものが無いかのチェックを行いました。
皆さんも是非、自分のSlackのワークスペースで試してみてください。そして不正なカスタム絵文字の魔の手から、Slackの秩序を守りましょう!
...こうして業務時間を浪費することによって会社の秩序は乱れていくのであった。