UnicodeをひたすらつぶやくTwitter botを作ったよ(AWS Lambda)

# やったこと
  - [@botunicode](https://twitter.com/botunicode) というTwitter-botを作った(フォローしてね)。
  - 3分に1回、ランダムにunicodeのいずれかのコードポイントをつぶやく。
  - 元データは[unicode.org](https://unicode.org)から。
  - Python3 / Node.js 8.0 on AWS Lambda。
  - 色々と雑で汚くてすみません。趣味プログラマ&&初投稿なんで。指摘を頂ければ改善します。

# もう少し詳しく
## Twitter準備
  - Twitterでアカウント作成
  - メアドだけでいけるかと思ったが、後述のAPI KEY取得で電話番号(SMS認証)が必要になった
  - アカウント作成後、[developer.twitter.com](https://developer.twitter.com/)でアプリの登録をするとKEYが払い出せる。詳細は→[Qiita:Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ　※2018年9月時点の情報](https://qiita.com/kngsym2018/items/2524d21455aac111cdee)

## AWS Lambda
  - python3のライブラリを使いたいので、一旦ローカルでライブラリを揃えzipでLambdaにアップすることに。
  - ローカルでの適当な場所で `pip install requests requests-oauthlib -t ./` 
  - そのフォルダをzipで固めておく
  - Lambdaで新規関数の作成。言語はPython3.6、ロールは最低限で良い。
  - 先ほどのzipを上げる。3分ごとの定期実行含め、詳細は→[AWS LambdaにPythonコードをzipでアップして実行する方法を詳しく](https://www.suzu6.net/posts/83-lambda-zip/)
  - lambdaのコードは下記。TwitterのKEYを環境変数に設定するほか、後述のAPIエンドポイントも。

```python
import os
import json
import requests
from requests_oauthlib import OAuth1Session

CK = os.environ['CONSUMER_KEY']
CS = os.environ['CONSUMER_SECRET']
AT = os.environ['ACCESS_TOKEN']
AS = os.environ['ACCESS_TOKEN_SECRET']

URL = 'https://api.twitter.com/1.1/statuses/update.json'

def lambda_handler(event, context):
    r = requests.get(os.environ['API_ENDPOINT']+"?rand")
    data = r.json()
    tweet = "\n".join([data['char'], "code:"+data['code'], data['name']+"("+data['block']+")", data['desc']])
    session = OAuth1Session(CK, CS, AT, AS)

    params = {"status": tweet }
    session.post(URL, params = params)
```

## APIの作成
  - 別建てにする必要ないが、汎用性を考えてunicodeうんぬんはAPIに分離した
  - 下記のLambdaを作成する(node.js8)

```javascript
const u = require('./unicode.json');

exports.handler = async (event) => {
    // functions
    const remove0 = (x)=>{return x.replace(/^0+/,"")};
    const emoji2unicode = (emoji)=> {
        var comp;
        if (emoji.length === 1) {
            comp = emoji.charCodeAt(0);
        }else{
            comp = (
                (emoji.charCodeAt(0) - 0xD800) * 0x400
              + (emoji.charCodeAt(1) - 0xDC00) + 0x10000
            );
            if (comp < 0) {
                comp = emoji.charCodeAt(0);
            }
        }
        return comp.toString("16");
    };
    const help = ()=>{
        return "GET Parameter\n example:\n  code=1f431\n or\n  char=A\n or\n  rand";
    };

    // process
    var response = {
        statusCode: 200,
    };
    if (event.queryStringParameters){
        if (event.queryStringParameters.code){
            var code = remove0(((event.queryStringParameters.code).toUpperCase()).replace(/[^0-9A-F]/,""));
            var r = u.filter((a)=>{return remove0(a.code) == code})[0];
            response.body = JSON.stringify(r);
        }else if (event.queryStringParameters.char){
            var code = emoji2unicode(event.queryStringParameters.char).toUpperCase();
            var r = u.filter((a)=>{return remove0(a.code) == code})[0];
            response.body = JSON.stringify(r);
        }else if ("rand" in event.queryStringParameters){
            response.body = JSON.stringify(u[Math.floor(Math.random() * u.length)]);
        }else{
            response.body = help();
        }
    }else{
        response.body = help();
    }
    return response;
};
```
  - 後述する方法で作る `unicode.json` ファイルを `index.js` と同じ階層に置く。
  - AWS API GatewayでAPI作成、GETアクションを追加して、前述Lambdaを指定。`Lambda プロキシ統合の使用` にチェックも忘れずに。
  - APIをデプロイして発行されたエンドポイントURLを最初のLambda(python)の環境変数に指定
  - なお、このAPIは本稿で使うパラメータ `rand` 以外にも、`code=1f431` とか `char=🐱` などのパラメータも受け付けるため、これはこれで便利。

## unicode.jsonを作る
  - 下記の体裁のjsonを作りたい。

```javascript
[
    {
        "name":  "\u003ccontrol\u003e",
        "desc":  "= NULL",
        "char":  "",
        "code":  "0000",
        "block":  "Basic Latin"
    },
    {
        "name":  "\u003ccontrol\u003e",
        "desc":  "= START OF HEADING",
        "char":  "",
        "code":  "0001",
        "block":  "Basic Latin"
    },
....
]
```
  - 元ネタはunicode.orgから[Blocks.txt](https://unicode.org/Public/UNIDATA/Blocks.txt)と[NamesList.txt](https://unicode.org/Public/UNIDATA/NamesList.txt)
  - ダウンロードして良いとこどりすればよい。bashでやりたかったのだが、NamesList.txtが微妙に複数行が混在するなど面倒だったので(awkで出来そうだが)、Powershell(Win10)でやった。

```powershell
$fn_blocks = ".\Blocks.txt"
$fn_namels = ".\NamesList.txt"
$os_blocks = (select-string -Path $fn_blocks -List '^0000').linenumber - 1
$os_namels = (select-string -Path $fn_namels -List '^0000').linenumber - 1

$db = New-Object System.Collections.ArrayList;
gc -Encoding utf8 .\Blocks.txt | select -skip $os_blocks | ?{$_ -match "^[0-9A-F]{4}"} | %{
  $t0 = $_ -split "; ";
  $t1 = $t0[0] -split "\.\.";
  [void]$db.Add(@{
    "start" = $t1[0];
    "end"   = $t1[1];
    "name"  = $t0[1];
  });
}

$dn = @{};
$tmp = $null;
gc -Encoding utf8 .\NamesList.txt | select -skip $os_namels | %{
  if($_ -match "^[0-9A-F]"){
    if($tmp -ne $null){$tmp.desc = $tmp.desc -join " "; $dn[$($tmp.code)] = $tmp}
    $tmp = @{};
    $t0 = $_ -split "\t"
    $tmp.code = $t0[0];
    $tmp.char = "";
    if(-Not ($t0[1] -match "^<")){$tmp.char=[char]::ConvertFromUtf32([int]"0x$($tmp.code)")}
    $blk = ($db | ?{([int]("0x"+$_.start) -le [int]("0x"+$tmp.code)) -and ([int]("0x"+$tmp.code) -le [int]("0x"+$_.end))} | select -first 1)
    if($blk){
      $tmp.block = $blk.name;
    }
    $tmp.name = $t0[1];
    $tmp.desc = @();
  }else{
    $tmp.desc += ($_ -replace "\t","")
  }
}
$dn.Keys | sort {[Convert]::ToInt64($_,16)}| %{New-Object PSObject -prop $dn[$($_)]} | ConvertTo-Json | sc -encoding utf8 unicode.json
```