KARTE Talkのオペレーター(2チーム)のオンライン人数により、接客のON/OFFを制御したいと思い、KARTE API v2を試していた。しかし、POST値の送信がうまくいかず、”Unexpected token o in JSON at position 0” と “400: should have required property ‘operator_id’. path: operator_id” という2つのエラーに苦しめられた。リファレンスにPOST値の送信方法は明記されておらず、「技術者ならそれくらいわかって当然」なのかもしれないが、かなり苦戦してしまった。試行錯誤の結果を残しておく。

基本となるAPIのリファレンスは以下である。
■API v2 概要
https://developers.karte.io/reference#api-v2-overview
前提として、APIが使えるようになっており、かつ対象のAPIが使えるよう管理画面でそのAPIを追加していることが必要。
目次
KARTE Talk のオペレーター一覧情報を取得
まずは、KARTE Talkのオペレータ一覧を取得するために以下を参照した。
■Operator
https://developers.karte.io/reference#post_v2beta-talk-operator-list
Macのターミナルを起動し、cURLで試してみる(※ ”ACCESS_TOKEN” は、API発行時に取得したアクセストークンの文字列に差し替え要)。
curl --request POST \
--url https://api.karte.io/v2beta/talk/operator/list \
--header 'Accept: application/json' \
--header 'Authorization: Bearer ACCESS_TOKEN' \
--header 'Content-Type: application/json; charset=utf-8'
なぜか謎の411 Length Required エラーが発生する。
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>411 Length Required</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Length Required</h1>
<h2>POST requests require a <code>Content-length</code> header.</h2>
<h2></h2>
</body></html>

別のMacでは動作した。よく見ると、Macのターミナルのシェルが違っており、bashはだめで、zshはOKだった。
以下のページを参考に、
■Macのターミナル(シェル)でbashやzsh を切り替える方法 | Hirooooo’s Labo
https://hirooooo-lab.com/development/change-mac-shell/
zshに切り替える。
chsh -s /bin/zsh
実行後、OSのパスワードを入力し、ターミナルを閉じて開き直す。そうすると、オペレーターのリストがJSON形式で返ってきた。
(余談:スピード重視型か、じっくり理解型か)
ここで、それほどプログラムに詳しくない私は、「bashとかzshって何?」と思った。気になる人は調べるのかもしれないが、今回はKARTEのAPIを実行できればよいのでターミナルのシェルがなんであろうと構わないと思いこのまま進めた。問題解決を優先するスピード重視形か(寄り道していてはなかなか目的を達成できずに挫折してしまう不安)、自分の理解を着実に積み上げるじっくり理解型(理解が中途半端だとかえって時間がかかるのではないかと不安)か、個人の資質が問われるところである。
KARTE Talk のオペレーター一人の情報を取得
次に、KARTE Talkのオペレーター情報を取得してみる。リファレンスは以下である。
■Operator
https://developers.karte.io/reference#post_v2-talk-operator-get
curl --request POST \
--url https://api.karte.io/v2/talk/operator/get \
--header 'Accept: application/json' \
--header 'Authorization: Bearer xxxxxxxxxx' \
--header 'Content-Type: application/json'
先ほど実行したコマンドを修正してPOST値を追加して実行した。
curl --request POST \
--url https://api.karte.io/v2/talk/operator/get \
--header 'Accept: application/json' \
--header 'Authorization: Bearer xxxxxxxxxx' \
--header 'Content-Type: application/json;'
--data 'operator_id=aaa'
すると以下のエラーが発生。
{"error":"400: should have required property 'operator_id'. path: operator_id"}
どうやら、Content-Type の最後の「;」をカットし忘れたことが原因のようである。あらためて以下を実行する。
curl --request POST \
--url https://api.karte.io/v2/talk/operator/get \
--header 'Accept: application/json' \
--header 'Authorization: Bearer xxxxxxxxxx' \
--header 'Content-Type: application/json'
--data 'operator_id=aaa'
今度は以下のエラーが出た。
{"error":"Unexpected token o in JSON at position 0"}
POST値の渡し方がおかしいようである。試行錯誤の結果、以下を実行するとうまくJSONが返ってきた。
curl --request POST \
--url https://api.karte.io/v2/talk/operator/get \
--header 'Accept: application/json' \
--header 'Authorization: Bearer xxxxxxxxxx' \
--header 'Content-Type: application/json'
--data '{"operator_id":"xxx"}'
PHP+Guzzle7を使ってHTTPリクエストを送信
ある案件でGuzzleを使ってBoxのAPIを使ったことがあるので、その資産を流用する。
PHP8.1をインストール(Mac)
以下を参考にした。
■Mac に php8 インストールする手順 – 1 Minute Tech Tips
https://sumito.jp/2021/11/11/mac-php8-install/
berw を最新にする。
brew update
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow
php 8.1が表示されインストールできることを確認。
brew search php@8.1
↓
==> Formulae
php@8.1 php@8.0 php@7.4 php@7.3 php@7.2
インストールする。
以下でバージョンが表示されればOK。
インストールする。
php -v
↓
PHP 8.1.4 (cli) (built: Mar 18 2022 09:45:20) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.4, Copyright (c) Zend Technologies
with Zend OPcache v8.1.4, Copyright (c), by Zend Technologies
Composerのインストール
以下を参考にした。
■Composer
https://getcomposer.org/download/
以下を実行し、composerをインストールする。
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
名前を composer にしてパスが通っているディレクトリへ移動。
sudo mv composer.phar /usr/local/bin/composer
以下を実行し、結果がずらずら返ってきたら成功。
composer -v
Guzzleのインストール
PHPを実行するフォルダに移動し、以下を実行し、Guzzleをインストールする。
composer require guzzlehttp/guzzle
composer.json、composer.lock、vendor フォルダが作成される。
Guzzleを使ってAPIを実行
以下を参考にした。
■Guzzle、PHP HTTPクライアント—Guzzleドキュメント
https://docs.guzzlephp.org/en/stable/
またしても以下のエラーに苦しめられた。
Unexpected token i in JSON at position 0
原因は、request()時の ‘form_params’ がNGだった。 ‘json’ にしないといけない。
$response = $client->request('POST', $url, array(
'headers' => $headers,
'form_params' => $params,
));
オペレーターリストを取得し、ある条件を満たした場合に、接客を停止(開始)するプログラムソースは以下になる。
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
$url_list = 'https://api.karte.io/v2beta/talk/operator/list';
$accessToken = 'xxxxxxxxxx';
try {
$headers = array(
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json; charset=utf-8',
);
$client = new Client();
$response = $client->request('POST', $url_list, array(
'headers' => $headers,
));
$contents = json_decode($response->getBody()->getContents(), true);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>オペレーターリスト</title>
</head>
<body>
<?php
if (!$contents) {
print('データの取得に失敗しました。');
die;
}
$is_online = 0;
$ids = '';
foreach ($contents['operators'] as $content) {
// オンラインの場合
if ($content['is_online'] == 1) {
$is_online++;
// チャットオペレーター名が設定されていれば名称を取得できる。
$ids = $ids . "[" . $content['id'] . "] (" . $content['profile_name'] . ')、';
}
}
print("現在オンライン中メンバーは、" . $is_online . "名です。<br>\n");
if ($is_online > 0) {
print("そのメンバーのidは、" . $ids . "です。<br>\n");
}
// 何らかの条件にマッチしたら・・・
if ($is_online >= 0) {
$url_onoff = 'https://api.karte.io/v2beta/action/campaign/toggleEnabled';
$enabled = false; // true:接客開始 or false:接客停止
$json = array(
'id' => 'xxxxxxxxxx',
'enabled' => $enabled
);
$client = new Client();
$response = $client->request('POST', $url_onoff, array(
'headers' => $headers,
'json' => $json,
));
$result = json_decode($response->getBody()->getContents(), true);
if ($enabled) {
print("接客を開始しました!<br>\n");
} else {
print("接客を停止しました!<br>\n");
}
}
?>
</body>
</html>
<?php
}catch (Exception $e) {
print('APIの実行に失敗。。');
$errMsg = json_decode($e->getResponse()->getBody()->getContents(), true);
print_r($errMsg);
exit();
}
おまけ:Python+API Gatewayで実装
Pythonでも同様に実装を試してみる。以下と同様、Requestsをレイヤーとして紐つけて作る。
■AWS Lambdaで定期的にスクレイピングする方法(その3:Lambda・DynamoDB・Simple Notification Service編) – Twin kangaroos
https://twinkangaroos.com/how-to-use-aws-lambda-for-periodic-scraping-3.html
import json
import requests
def lambda_handler(event, context):
statusCode = "500"
result_body = ""
access_token = 'xxxxxxxxxxxxxxxxxxxx';
# 1.オペレーターリスト(オンライン)の取得
url_list = "https://api.karte.io/v2beta/talk/operator/list"
headers = {
"Accept": "application/json",
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json; charset=utf-8"
}
try:
response = requests.request("POST", url_list, headers=headers)
except Exception as e:
return {
'statusCode': 501,
'body': json.dumps({
'error':'Failed to get operator list.'
}),
}
json_data = json.loads(response.text)
onlines = []
operators = json_data["operators"]
for operator in operators:
# online
if operator['is_online'] == 1:
onlines.append(operator['id'])
#2.オペレーター(オンライン)のチーム分け
online_operator_a = []
online_operator_b = []
#Aチーム(Max3)
member_a = ['aaaaa1', 'aaaa2', 'aaaa3']
for online in onlines:
for member in member_a:
if online == member:
online_operator_a.append(member)
#Bチーム(Max2)
member_b = ['bbbb1', 'bbbb2']
for online in onlines:
for member in member_b:
if online == member:
online_operator_b.append(member)
result_a = ",".join(online_operator_a)
result_b = ",".join(online_operator_b)
result_body = "TeamA=[" + result_a + "] TeamB=[" + result_b + "] "
online_a = len(online_operator_a)
online_b = len(online_operator_b)
# 3.各チームの接客開始・停止
url_onoff = 'https://api.karte.io/v2beta/action/campaign/toggleEnabled';
enabled_a = True #True:接客開始
enabled_b = True
id_a = 'sekkyakua'
id_b = 'sekkyakub'
if online_a >= 3:
enabled_a = False #False:接客停止
if online_b >= 2:
enabled_b = False
#接客A開始・停止
json_a = {
'id': id_a,
'enabled': enabled_a,
}
try:
response_a = requests.request("POST", url_onoff, headers=headers, data=json.dumps(json_a))
except Exception as e:
return {
'statusCode': 502,
'body': json.dumps({
'error':'Failed to toggleEnabled-A.'
}),
}
json_a = json.loads(response_a.text)
if enabled_a:
msg_a = "TeamA=[Start] "
else:
msg_a = "TeamA=[Stop] "
result_body += msg_a
#接客B開始・停止
:
(接客Aと同様)
:
result_body += msg_b
statusCode = response_b.status_code
return {
'statusCode': statusCode,
'body': json.dumps(result_body)
}
API Gatewayは以下と同様にして作成。
■AWS Lambda・API Gatewayでいいね機能を自前で作る – Twin kangaroos
https://twinkangaroos.com/aws-lambda-apigateway-to-create-like-function.html
まとめ
まだ発展途上のためかKARTEのAPIでエラーが発生した場合のトラブルシュートに時間がかかる。エラーコードについては以下に記載があるが、
■API v2 概要
https://developers.karte.io/reference#request-headers-v2
400 Bad Request リクエストの形式に異常がある場合
と、そっけない。掲載されている以外のエラーが出た場合、なぜそのエラーが発生したのか理解ができなかったため、もう少し詳しいエラー情報を返してもらえると嬉しいのだが。
以前、BoxのAPIを使ったプログラムを書いたが、APIの使い方としては大きく変わらなかったので、どれか一つでもパターンを覚えておけば融通が効くように感じた。
今回想定の検索ワード
”KARTE Unexpected token”
“KARTE 400: should have required property”