Edited at

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


やったこと



  • @botunicode というTwitter-botを作った(フォローしてね)。

  • 3分に1回、ランダムにunicodeのいずれかのコードポイントをつぶやく。

  • 元データはunicode.orgから。

  • Python3 / Node.js 8.0 on AWS Lambda。

  • 色々と雑で汚くてすみません。趣味プログラマ&&初投稿なんで。指摘を頂ければ改善します。


もう少し詳しく


Twitter準備


AWS Lambda


  • python3のライブラリを使いたいので、一旦ローカルでライブラリを揃えzipでLambdaにアップすることに。

  • ローカルでの適当な場所で pip install requests requests-oauthlib -t ./

  • そのフォルダをzipで固めておく

  • Lambdaで新規関数の作成。言語はPython3.6、ロールは最低限で良い。

  • 先ほどのzipを上げる。3分ごとの定期実行含め、詳細は→AWS LambdaにPythonコードをzipでアップして実行する方法を詳しく

  • lambdaのコードは下記。TwitterのKEYを環境変数に設定するほか、後述のAPIエンドポイントも。

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)

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を作りたい。

[

{
"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.txtNamesList.txt

  • ダウンロードして良いとこどりすればよい。bashでやりたかったのだが、NamesList.txtが微妙に複数行が混在するなど面倒だったので(awkで出来そうだが)、Powershell(Win10)でやった。

$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