イケてる絵文字に飢えている貴方のための Slackカスタム絵文字 [Emoji Kitchen][絵文字キッチン]

**※[おまけが本編](#おまけ)です**
![nyan_bowing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/254d2dad-9032-0016-9792-a720a258d519.png)

# 本記事の概要

こんな感じのイケてる絵文字がSlackに欲しいなあ～と思ったら、本記事を読もう！
→　[Emoji Kitchen](https://emojikitchen.dev/)

本記事ではGitHub上に公開されているEmoji Kitchenのサンプルコードをローカルで実行し、これをSeleniumで操作して絵文字を組み合わせた画像ファイルをダウンロードし、Slackで検索できるようファイル名を絵文字の名前に変更します。その後、その画像ファイルを手作業でSlackワークスペースに登録します。

# ソースコード

```Python:emoji_kitchen.py
import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager
import requests

service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)

# レスポンシブするまで画面の横幅を狭める
driver.set_window_size(650, 768)

SAVE_DIRECTORY=r'./emoji_kitchen'

driver.get("http://localhost:5173")

driver.implicitly_wait(15)

actions = ActionChains(driver)

# フローティングボタンのせいでボタンが押せないよエラーが出るので、
# フローティングボタンを消す
floatingButton = driver.find_element(By.CSS_SELECTOR, "#root > div > div.MuiContainer-root.MuiContainer-maxWidthXl.css-15f64v6-MuiContainer-root > div > button")
driver.execute_script("arguments[0].style.display='none';", floatingButton)

button1 = driver.find_element(By.CSS_SELECTOR, "#root > div > div.MuiContainer-root.MuiContainer-maxWidthXl.css-15f64v6-MuiContainer-root > div > div.MuiPaper-root.MuiPaper-elevation.MuiPaper-rounded.MuiPaper-elevation1.css-b27thi-MuiPaper-root > div > div.MuiGrid2-root.MuiGrid2-container.MuiGrid2-direction-xs-row.MuiGrid2-spacing-xs-2.css-s356kp-MuiGrid2-root > div:nth-child(1)")
button1img = button1.find_element(By.CSS_SELECTOR, "img")
button1.click()

cat_element = driver.find_element(By.CSS_SELECTOR, 'div.MuiBox-root img[alt="cat"]')
cat_element.click()

button2 = driver.find_element(By.CSS_SELECTOR, "#root > div > div.MuiContainer-root.MuiContainer-maxWidthXl.css-15f64v6-MuiContainer-root > div > div.MuiPaper-root.MuiPaper-elevation.MuiPaper-rounded.MuiPaper-elevation1.css-b27thi-MuiPaper-root > div > div.MuiGrid2-root.MuiGrid2-container.MuiGrid2-direction-xs-row.MuiGrid2-spacing-xs-2.css-s356kp-MuiGrid2-root > div:nth-child(3)")
button2img = button2.find_element(By.CSS_SELECTOR, "img")
button2.click()

button3 = driver.find_element(By.CSS_SELECTOR, "#root > div > div.MuiContainer-root.MuiContainer-maxWidthXl.css-15f64v6-MuiContainer-root > div > div.MuiPaper-root.MuiPaper-elevation.MuiPaper-rounded.MuiPaper-elevation1.css-b27thi-MuiPaper-root > div > div.MuiGrid2-root.MuiGrid2-container.MuiGrid2-direction-xs-row.MuiGrid2-spacing-xs-2.css-s356kp-MuiGrid2-root > div:nth-child(5)")
button3img = button3.find_element(By.CSS_SELECTOR, "img")

for element in driver.find_elements(By.CLASS_NAME, "css-xdcm7u > div"):
  if element.get_attribute("class") == "css-1yyqevv-MuiImageListItem-root":
    continue
  actions.move_to_element(element)
  actions.perform()
  element.click()

  time.sleep(0.1)
  src = button3img.get_attribute('src')
  filename = src.split('/')[-1].split('?')[0]
  with open(f'{SAVE_DIRECTORY}/{filename}', 'wb') as file:
    response = requests.get(src)
    file.write(response.content)
  with open(f'{SAVE_DIRECTORY}/filename_to_alt.txt', 'a') as file:
    file.write(f'{filename}\t{button1img.get_attribute("alt")}\t{button2img.get_attribute("alt")}\t{button3img.get_attribute("alt")}\n')


print('finished. Press any key to exit.')
input()

driver.quit()
```

```Python:set_filename.py

import os

SRC_DIRECTORY=r'./emoji_kitchen'
SAVE_DIRECTORY=r'./emoji_kitchen_output'

os.makedirs(SAVE_DIRECTORY, exist_ok=True)

new_file_name = {}
with open(f'{SRC_DIRECTORY}/filename_to_alt.txt', 'r') as f:
  for line in f:
    parts = line.strip().split('\t')
    new_file_name[parts[0]] = f'{parts[1]}_{parts[2]}.png'

for root, dirs, files in os.walk(SRC_DIRECTORY):
  for file in files:
    if not file.endswith(".png"):
      continue
    src_path = os.path.join(root, file)
    dst_path = os.path.join(SAVE_DIRECTORY, new_file_name[file])
    with open(src_path, 'rb') as src_file:
      with open(dst_path, 'wb') as dst_file:
        dst_file.write(src_file.read())
```

# 実施手順

### 画像の取り込み

[Emoji Kitchenのページ](https://emojikitchen.dev/)に、GitHubへのリンクが張られている。
[emoji-kitchen](https://github.com/xsalazar/emoji-kitchen)

これをgit cloneかDownload ZIPでダウンロードし、解凍先へchange directoryののち以下コマンドを実行

```plaintext:上記GitHubより転載
curl -L --compressed https://raw.githubusercontent.com/xsalazar/emoji-kitchen-backend/main/app/metadata.json -o src/Components/metadata.json
npm install && npm start
```

このコマンドにより、localhost:5173でEmoji KitchenのページのWebサーバが起動する。
これをSeleniumでスクレイピングする。

`emoji_kitchen.py`のあるフォルダへchange directoryののち、以下コマンドを実行する。

```plaintext:スクレイピングコマンド
pip install selenium
pip install webdriver_manager
python ./emoji_kitchen.py
```

Seleum制御下のChromeが起動し、ぽちぽち押しながら./emoji_kitchenディレクトリへ画像が保存される。

### 取り込んだ絵文字の名前

併せて`./emoji_kitchen/filename_to_alt.txt`に、ファイル名と、imgタグのalt属性との対応表がタブ区切りで出力される。

```plaintext:file_name_to_alt.txt
u1fa84_u1f431.png	cat	magic_wand	magic_wand-cat
u1f600_u1f431.png	cat	grinning	grinning-cat
u1f603_u1f431.png	cat	smiley	smiley-cat
u1f604_u1f431.png	cat	smile	smile-cat
u1f601_u1f431.png	cat	grin	grin-cat
:
:
```
1列目はファイル名、2列目は組み合わせの1つ目の絵文字の名称、3列目は組み合わせの2つ目の絵文字の名称。4列目は組み合わせ絵文字の名称である。

### 絵文字のファイル名

ファイル名はアンダースコア区切りで、

- 1つ目は組み合わせ1つ目の絵文字の文字コード
- 2つ目は組み合わせ2つ目の絵文字の文字コード

である。
Unicodeであることを示すために、それぞれの文字コードには「u」が付加されている。実際の文字コードはuを外した残りの桁なので、それでGoogle検索などをすると該当する絵文字について説明したWebページが出てきたりする。

一部、以下のように、文字コードがハイフン区切りで長くなっているものがある。
```
u1f62e-u200d-u1f4a8_u1f431.png
```
このうち`u200d`の部分は「[ゼロ幅接合子](https://0g0.org/topic/zero-width-joiner/)」と呼ばれるものであり、絵文字を組み合わせて合体するための制御文字（？）である。

なのでこの絵文字は「`1f62e 200d 1f4a8`」で1つの絵文字である点に注意する。

### Slackにアップロードする準備

`u1fa84_u1f431.png`などの形式でSlackのカスタム絵文字へアップロードすると、以下のようにファイル名が絵文字の名前として自動入力され、実際に絵文字として使う際に名前で検索できなくて不便である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/da76e946-e522-9898-4c75-1c47d174bde6.png)

ローカルで実行したEmoji Kitchenのページにおいて絵文字の名前がAlt属性に埋め込まれていることに着目し、これをファイル名として利用する。

以下コマンドを実行し、`filename_to_alt.txt`の対応表に従ってファイル名を変更する。

```plaintext:ファイル名を変えて別フォルダへ出力するコマンド
python ./set_filename.py
```

すると、`emoji_kitchen_output`フォルダ以下にファイル名が変更された状態で出力されているはず。
これをSlackカスタム絵文字追加ページへアップロードすると、そのファイル名で絵文字の名前が自動入力される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/3c7e7399-41bc-2533-56ef-65fea541e217.png)

### その他

`cat_magic_wand_猫_魔法の杖`のような名前にしたい場合は、
ファイル名から組み合わせもう一方の文字コードを抽出し、何らかの手段でこれに応じたja emoji nameを引き当ててファイル名に組み込むことができる。

### Slackにアップロード

[ワークスペースにカスタム絵文字とエイリアスを追加する](https://slack.com/intl/ja-jp/help/articles/206870177-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AB%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%B5%B5%E6%96%87%E5%AD%97%E3%81%A8%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)

# 失敗1：カスタム絵文字一括登録

以下ソースコードで登録しようとしたけど、エラーが出てしまい
技能上の都合でどうにもならなかったので、
カスタム絵文字への登録は全部手作業で実施した。

```Python:add_many_emoji.py
import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager
import requests
import os
import pickle

service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)

action = webdriver.common.action_chains.ActionChains(driver)

SRC_DIRECTORY=r'./emoji_kitchen_output'

def login(driver):
  # ログインしてから、コンソールでEnterキーを押す
  input()

  cookies = driver.get_cookies() # クッキーを取得する
  pickle.dump(cookies,open('./add_many_emoji_cookie.pickle','wb'))
  exit(0)

driver.get(f"https://*******.slack.com/customize/emoji?utm_source=in-prod&utm_medium=inprod-customize_link-slack_menu-click")

if os.path.exists('./add_many_emoji_cookie.pickle'):
  cookies = pickle.load(open('./add_many_emoji_cookie.pickle', 'rb'))
  for cookie in cookies:
    driver.add_cookie(cookie)
  driver.refresh()
else:
  login(driver)

driver.implicitly_wait(10)

def clickforcible(action, element):
  action = webdriver.common.action_chains.ActionChains(driver)
  action.move_to_element_with_offset(element, 5, 5)
  action.click()
  action.perform()

def upload_emoji(driver: webdriver.Chrome, filename):
  button1 = driver.find_element(By.CLASS_NAME, 'p-customize_emoji_wrapper__custom_button_short')
  button1.click()
  driver.implicitly_wait(1)

  # button1 = driver.find_element(By.CSS_SELECTOR, '[data-qa="customize_emoji_add_dialog_upload"]')
  # button1.click()
  # driver.implicitly_wait(1)

  upload_input = driver.find_element(By.CSS_SELECTOR, '[data-qa="customize_emoji_add_dialog_file_input"]')
  upload_input.send_keys(os.path.join(SRC_DIRECTORY, filename))
  driver.implicitly_wait(1)

  print('ok')
  input()
  quit()

upload_emoji(driver, 'cat_two_hearts.png')
```

# 結果

かわいい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/ae6e7e98-f942-9cd1-44e8-a021137f5220.png)

# 不満

猫絵文字が多すぎて、絵文字を検索するときに`:cat`ですべての猫絵文字が出なくなってしまった。（特に元から手書きで書いたやつ）
こんなにかわいいのだから、Slackはデフォルトの絵文字にEmoji Kitchenを追加するべき。

# 特筆すべき点

Emoji Kitchenでは100,000以上の組み合わせ絵文字をGoogleのデザインチームが愛情込めて手作業で作ったらしい。
マジ？

# おまけ

Emoji Kitchenに出会うまでに筆者が手作業で作成した、ねこの絵文字をどうぞ；

black_cat_face
![black_cat_face.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/d0fed277-f2eb-fc56-aee8-e1c9bd3950d5.png)
clap_cat
![clap-cat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/5830c4ae-3077-a3b9-7d10-291f9f234892.png)
angle_cat
![angel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/d19f436a-edef-eb6d-e265-bebc6190d9ff.png)
melting_cat
![melting_cat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/842492e8-2b38-a507-34ae-0ed1dae01da1.png)
nyan_bowing
![nyan_bowing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/a494b46f-ce42-b19c-825b-4d7eadd20614.png)
nyan_gesturing_ok
![nyan_gesturing_ok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/3eda80dd-b4f2-e1b7-f36b-bab8edbc7034.png)
pray_cat
![pray-cat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/4c271124-4d69-1b4c-9f8d-8034fab6ce23.png)
cat_hand
![cat_hand.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/d4fd33a9-b824-5283-697d-420b3191bdaa.png)
black_cat_hand
![black_cat_hand.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653543/c5e27c5e-ae3a-fa08-3451-62477754370e.png)

もっとつくるわよ～～（1個作るのに何時間もかかるので期待しないでお待ちください）