Facebook Messenger に URLをシェアする
やりたいこと
掲題の通り URLスキームでちょちょいと飛ばせないかな〜と思った
やったこと
まず
fb://messaging
を試した。
こいつは、facebookアプリを経由して、facebookメッセンジャーアプリを開いてくれるURLスキーム
URLスキームは基本、当該アプリがインストールされていなければ動いてくれないので、
facebookアプリは入ってるけどメッセンジャーアプリは入ってない〜という人が多そうな場合はこちらの方がいいかも。
この経路だと、メッセンジャーアプリがインストールされてなければストアに飛ばしてくれる(多分Facebookアプリ自体の実装)。
ただ、URLなどのシェアをする方法がわからなかった。
次に
fb-messenger://
を試した。
こいつは、直接facebookメッセンジャーアプリを開くURLスキーム
メッセンジャーアプリがインストールされてない場合、ストアに飛ばしてくれたりはしないので、そこは自己実装が必要
こいつでリンクをシェアしたい場合
developerサイトに一応やり方が書いてあった
ウェブ - シェア機能 - ドキュメンテーション - 開発者向けFacebook
fb-messenger://share/?link=https://www.facebook.com
↑これで、メッセンジャーでリンクをシェアする画面に飛ぶ(これは例でfacebookへのリンク)
で、どうなったか
後者のやり方でOKだな!と思ったんですが、確認のために何回も同じURLをシェアしていたら、Facebookにスパム扱いされてしまった。
端末によりますが、メッセージ送信しようとした際に、
FBAPIError Domain error 368
であったり
the action attempted has been deemed abusive or is otherwise disallowed
であったりなどのエラーが発生したりする。
んで次の日こういう表示になっていた。
上のリンク踏むとこう出る
どうも、URLスキームではなく、SDKなどを使っていても、スパム扱いされることもある様子。
URLとか同じ文言のシェアは、Facebookメッセンジャーですることをあまり考えない方が良さそうですね。
完!
参考URL
mac再起動したら、GithubにSSHでアクセスできなくなった
前提
SourcetreeでSSH鍵を作成して、Githubにその鍵を登録してある状態 mac再起動するまでは、正常に使えていた
やりたいこと
macを久々に再起動して、 Sourcetreeでfetchしようとしたら
git@github.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
って出たので解決したい。
やったこと
とりあえず、接続試みつつデバッグしてくれるコマンドで確認
ssh -vT git@github.com
↑のログを見た感じ、Offering public key で、.ssh/id_rsa を見に行っている気配
けどこれ接続できないの当たり前で、ソースツリー&githubに登録してある鍵は別の名前の鍵(仮にid_rsa_githubとする)。
色々やったけど、以下結論。
.ssh/config
に、Sourcetreeがssh鍵を作るときに情報を勝手に書き込んどいてくれるのだが、その情報がダメそうだった。
こうなってたのを
# --- Sourcetree Generated --- Host id_rsa_github HostName github.com User dormouse666 PreferredAuthentications publickey IdentityFile /Users/hogehoge/.ssh/id_rsa_github UseKeychain yes AddKeysToAgent yes # ----------------------------
こうしたら治った。
# --- Sourcetree Generated --- Host github.com HostName github.com User git PreferredAuthentications publickey IdentityFile /Users/hogehoge/.ssh/id_rsa_github UseKeychain yes AddKeysToAgent yes # ----------------------------
memo:
参考URL
ランチ候補をpostしてくれるslackbotを、Google Apps Scriptで作る
やりたいこと
転職したのでランチ場所の情報を蓄積したい
一回行って美味しかった場所&これから行ってみたいと思ってる場所をおすすめしてくれるやつがいい
いい感じにランチを決める Slack ボットを作った
↑こういうのがいいなぁと思った
仕様
・ 「ごはん」と呼んだら候補をslackにpostしてくれる
・ お昼の時間になったら、自動的にも投稿してくれる
・ スプレッドシートの情報を読んで、候補をランダムに選んでくれる
やったこと
Incoming WebHooks を設定する
https://my.slack.com/services/new/incoming-webhook
で Post to Channel でbotに投稿させたいチャンネルを選んで、
Add Incoming WebHooks Integration ボタンを押下
ここで生成された、 Webhook URL を後で使うのでメモっておく。
Outgoing WebHooks を設定する
https://my.slack.com/services/new/outgoing-webhook
で Add Outgoing WebHooks Integration ボタンを押下して追加
Channel: 発言を拾うチャンネル名を設定
Trigger Word(s): トリガーワードを設定できるが今回は使わなかった。使った方が楽
URL(s): 作成したGASのウェブアプリケーションのurlを設定してSaveする。urlについては後述
上記の設定を変更したい時はここ
https://my.slack.com/apps/manage/custom-integrations
スプレッドシート作る
こんな感じにした
店名だけだとわからんので、備考とurlも情報として持つようにする
あと、botが喋るmessageの内容もマスタっぽくシートに持たせておくことにした
こんな感じで
Google Apps Script のファイルを作成してコード書く
書いた
var SLACK_WEBHOOK = 'https://hooks.slack.com/services/~~~~~~'; // Incoming WebHooks -> Webhook URLをここに設定する var SLACK_CHANNEL = '#lunch'; var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/~~~~~~~~~'; // 作成したスプレッドシートのURL var EMOJI_ICON = ':fried_shrimp:'; var BOT_NAME = 'lunchBot'; var LUNCH_HOUR = 12; var LUNCH_MINUTE = 50; var IS_COFFEE = false; var IS_GOHAN = false; var MESSAGE_TYPE_AUTO = 1; var MESSAGE_TYPE_GOHAN = 2; var MESSAGE_TYPE_NEMUI = 3; // お昼になったら自動投稿するためのトリガー function setTrigger() { var triggerDay = new Date(); triggerDay.setHours(LUNCH_HOUR); triggerDay.setMinutes(LUNCH_MINUTE); ScriptApp.newTrigger("lunch").timeBased().at(triggerDay).create(); // lunch関数を動かしたいので、それを指定 } function deleteTrigger() { var triggers = ScriptApp.getProjectTriggers(); for(var i=0; i < triggers.length; i++) { if (triggers[i].getHandlerFunction() == "lunch") { ScriptApp.deleteTrigger(triggers[i]); } } } var getRows = function (range) { return range.getValues().map(function(x) { return x[0]; }).filter(function(x) { return x }); } Array.prototype.random = function () { return this[Math.floor(Math.random() * this.length)] } Array.prototype.randomCount = function () { return Math.floor(Math.random() * this.length); } // 投稿 function postMessage(message, hookPoint) { var payload = { "text": message, "icon_emoji": EMOJI_ICON, "username": BOT_NAME, "channel": SLACK_CHANNEL } var options = { "method" : "POST", "payload" : JSON.stringify(payload), "headers": { "Content-type": "application/json", } } var response = UrlFetchApp.fetch(hookPoint, options); if (response.getResponseCode() == 200) { return response; } return false; } // メイン function lunch() { if(!IS_GOHAN && !IS_COFFEE) { deleteTrigger(); //自動投稿トリガーを消す } var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL); var sheet0 = spreadsheet.getSheets()[0]; //一番左のシート: 店羅列用 var sheet1 = spreadsheet.getSheets()[1]; //次のシート: mesasge用 // message 該当タイプをランダムで var messageAll = sheet1.getRange(3, 2, sheet1.getLastRow(), sheet1.getLastColumn()).getValues(); var messageList = []; var message = ""; var type = MESSAGE_TYPE_AUTO; if(IS_GOHAN) { type = MESSAGE_TYPE_GOHAN; } if(IS_COFFEE) { type = MESSAGE_TYPE_NEMUI; } for (var i in messageAll) { var mtype = messageAll[i][1]; //1つなら数値、カンマ区切りならstring判断になる var typeList = []; if(mtype == null) { break; } if(isNaN(mtype)) { typeList = mtype.split(','); //文字列。カンマ区切りで配列に変換 } else { typeList.push(mtype); //数値なのでそのままぶち込む } for (var ii in typeList) { if (typeList[ii] == type) { messageList.push(messageAll[i][0]); //textだけ格納 } } } message += messageList.random(); if(!IS_GOHAN && !IS_COFFEE) { message += " <!channel>"; //自動投稿の時だけメンション飛ばす } // 3行目〜 // B列: ご飯 // C列: 備考 // D列: url var bs = getRows(sheet0.getRange(3, 2, sheet0.getLastRow())); var cs = getRows(sheet0.getRange(3, 3, sheet0.getLastRow())); var ds = getRows(sheet0.getRange(3, 4, sheet0.getLastRow())); var cnt1 = bs.randomCount(); var b = bs[cnt1]; var c = cs[cnt1]; var d = ds[cnt1]; // F列: 軽食 // G列: 備考 // H列: url var fs = getRows(sheet0.getRange(3, 6, sheet0.getLastRow())); var gs = getRows(sheet0.getRange(3, 7, sheet0.getLastRow())); var hs = getRows(sheet0.getRange(3, 8, sheet0.getLastRow())); var cnt2 = fs.randomCount(); var f = fs[cnt2]; var g = gs[cnt2]; var h = hs[cnt2]; // J列: 遠出 // K列: 備考 // L列: url var js = getRows(sheet0.getRange(3, 10, sheet0.getLastRow())); var ks = getRows(sheet0.getRange(3, 11, sheet0.getLastRow())); var ls = getRows(sheet0.getRange(3, 12, sheet0.getLastRow())); var cnt3 = js.randomCount(); var j = js[cnt3]; var k = ks[cnt3]; var l = ls[cnt3]; if(!IS_COFFEE){ message += "\n\n\n:fried_shrimp: " + "*" + b + "*" + " :fried_shrimp:"; message += "\n:speech_balloon: " + c + "\n" + d; } message += "\n\n\n:coffee: " + "*" + f + "*" + " :coffee:"; message += "\n:speech_balloon: " + g + "\n" + h; if(!IS_COFFEE){ message += "\n\n\n:mushroom: " + "*" + j + "*" + " :mushroom: [遠出枠]"; message += "\n:speech_balloon: " + k + "\n" + l; } message += "\n\n\n候補を編集する → <" + SPREADSHEET_URL + "|:memo:>"; postMessage(message, SLACK_WEBHOOK); } // Outgoing WebHooks を設定すると、ユーザからのpostがあると走る処理 function doPost(e) { // 自分自身ははじく if(e.parameter.user_id == "USLACKBOT"){ return; } // Outgoing WebHooks の Trigger Word(s) でも設定できるんだけど if(e.parameter.text.indexOf("ごはん") > -1){ IS_GOHAN = true; lunch(); } // おまけ if(e.parameter.text.indexOf("ねむい") > -1){ IS_COFFEE = true; lunch(); } }
postMessage
doPost
はGASが持ってる関数。
途中でやらかしたのが、botの返信メッセージに「ごはん」が含まれるとそれに自分で反応して無限ループする。
ので、自分自身には反応しないようにする。
doPost で得ている e.parameter の中身、botからの送信だと
user_id: USLACKBOT user_name: slackbot
で来ているっぽい。それを弾くようにしておく。
e.parameter.text.indexOf("ごはん") > -1
↑これは、部分一致。「ごはん」という言葉が含まれていれば反応する。
書いたコードを実行するために
GASのメニュー -> 公開 -> Webアプリケーションとして導入 -> 新しいバージョンを保存 -> 導入
で、ここで URLが生成されるので、それを先述した Outgoing WebHooks の URL(s) に設定する。
注意点として、上記手順後にコードを変更したら、
GASのメニュー -> 公開 -> Webアプリケーションとして導入 -> プロジェクト バージョン -> 新規作成 -> 更新 ボタン押下
を毎回しないとbotに反映されない。
ちょっとめんどくさい。
自動投稿するならトリガーを設定する
月水金だけお昼の時間になったら自動で投稿してほしいので、そういった設定をする。
var LUNCH_HOUR = 12; var LUNCH_MINUTE = 50;
↑の、自動投稿してほしい時間以前に、 setTrigger
が走るようにしておけばOK
こんな感じ。
こうなる
自動投稿時
この時だけメンションをくれる
「ごはん」で呼んだ時
ねむい時はコーヒーを勧めてくる
以上
参考URL
いい感じにランチを決める Slack ボットを作った
SlackのIncoming Webhooksを使い倒す <-Incoming WebHooks
SlackのOutgoing Webhooksを使って投稿に反応するbotを作る <-Outgoing WebHooks
Google Apps Scriptの日毎のトリガーで時間をもっと細かく設定する <-トリガー
GoogleAppsScriptメモ:トリガーの利用 <-トリガー
Google Apps Script 実践メモ(スプレッドシート) <-スプレッドシートへのアクセス
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
twitterBotで、ぐるなびAPIを使ってリプライする
やりたいこと
上記で作ったtwitterBotに、おすすめのご飯を教えてくれる機能をつけたい。
やったこと
ぐるなびAPIを取得
ここでアカウントが作れます。 api仕様とかも見れる。
書いた処理
- 「ごはん」とリプライすると、渋谷マークシティ近辺の飲食店をぐるなびで探して持ってくる
- 「ごはん hogehoge」とリプライすると、渋谷マークシティ近辺の飲食店をぐるなびでhogehogeで検索して持ってくる
- 複数指定したい場合は「ごはん hogehoge、fugafuga」みたいに「、」で区切って指定
<?php /** * ごはん@ぐるなび */ function replyGohan($TwitterOAuth, $value, $gohanAccesskey, $gohanText) { // エンドポイントとパラメータ設定 $endpoint = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/'; $hitNum = 50; $gohanParams = [ 'keyid' => $gohanAccesskey, 'format' => 'json', 'latitude' => '35.657988', //渋谷マークシティの緯度&経度 エリアコードの方がいいかもしらんが 'longitude' => '139.698056', 'range' => '5', // 緯度/経度からの検索範囲(半径) 5:3000m 'hit_per_page' => $hitNum, // 取得件数 どうも50が上限 'freeword' => $gohanText, //フリーワード検索「,」区切りで複数ワードが検索可能(10個まで) ]; $gohanUrl = $endpoint.'?'.http_build_query($gohanParams, '', '&'); // API実行 $gohanJson = file_get_contents($gohanUrl); // 取得した結果をオブジェクト化 $gonahObj = json_decode($gohanJson); // エラーハンドリング if(empty($gonahObj->rest)) //失敗するとrestキーが存在しない { $errorCode = $gonahObj->error->code; $errorMessage = $gonahObj->error->message; if(!empty($errorMessage)) { $errorMessage = "「".$errorMessage."」とのこと"; } error_log(print_r($gonahObj, true)); $resMessage = '@'.$value->user->screen_name.' '."すません…".$errorCode."エラーです…\n".$errorMessage; $response = $TwitterOAuth->post('statuses/update', array('status' => $resMessage, 'in_reply_to_status_id'=>$value->id_str)); // エラー出力 if($TwitterOAuth->getLastHttpCode() != 200) { error_log(print_r($response, true)); } return; } // 0件の時 $gohanNum = $gonahObj->total_hit_count; if($gohanNum == 0) { $resMessage = '@'.$value->user->screen_name.' '.'0件です(´・ω・`)'; $response = $TwitterOAuth->post('statuses/update', array('status' => $resMessage, 'in_reply_to_status_id'=>$value->id_str)); return; } // 成功してたら1件だけ取得 $i = 0; $rand = rand(0, $hitNum-1); foreach($gonahObj->rest as $restArray) { $gohanName = $restArray->name; $gohanUrl = $restArray->url; if($i >= $rand) { break; } $i++; } if(!empty($gohanText)) { $gohanText = "「".preg_replace("/,/", "、", $gohanText)."」は"; } $credit = 'Supported by ぐるなびWebService(http://api.gnavi.co.jp/api/scope/)'; $gohanStr = $gohanText.$gohanNum."件あった、オススメはこれかな\n".$gohanName."\n".$gohanUrl."\n\n".$credit; $resMessage = '@'.$value->user->screen_name.' '.$gohanStr; $response = $TwitterOAuth->post('statuses/update', array('status' => $resMessage, 'in_reply_to_status_id'=>$value->id_str)); // エラー出力 if($TwitterOAuth->getLastHttpCode() != 200) { error_log(print_r($response, true)); } } ?>
(シンタックスハイライトしてもらおうと思って<?php ?>で囲んだけどなんか色がすごい)
使うとこういう感じ
Github
解決できなかったこと
フリーワード検索自体の精度がどうも今一つであり、「カレー」で検索しても鳥貴族とかを持ってきちゃったりすることが結構多い。
業態マスタ取得API とかも使わないとダメかな。。。
参考URL
TwitterBotをつくった
今更なはなし
4月、世の中がマストドンで盛り上がっている中、ツイッターbotをぺちぺち作った( @sennpai_bot )。
本人の許可は得たが、そのうち消えるかもしれないし消えないかもしれません。
テキストファイルで動くめっちゃ原始的なbot。
テキストファイルじゃなくてDBからマスタデータ読み込むようにしたいと思いつつまだ何もしていません。
詰まったところ
使ってるサーバのPHPが古かった
twitteroauth のPHPの記載が新しくなってて5.3だと使えなかった。
4月にPHPのバージョンを5.3から5.6にあげたのはこれのせい。
Status is a duplicate.
ツイッター民にはおなじみの同じ投稿はできませんエラー。
botだとどうしても引っかかることが多い。
めんどくさかったので投稿頻度を下げる&投稿内容を増やすことで解決(?)
ツイッターからapiが返ってこない時
bot公開してしばらくして、ツイッター自体が落ちたタイミングがあった。
その際に statuses/user_timeline
が返却されてこなかったため、延々とリプライ返しまくるバグが出た。
emptyチェックを入れて解決。
参考URL
PHP+OAuthでTwitterのBotを作ってみる - SDN Project
PHP + OAuthで Twitter botをつくってみよう - PHP入門 - Webkaru
PHPからTwitterツイート(2015年2月版) - Qiita
Twitter メンション(@)に反応してリプライを返すbotを作る vol.2 - Hell Yeah!!
PHPのバージョンを5.3から5.6にアップデートした
やりたいこと
サーバ(CentOS)に入ってるPHPのversionを上げたい。
なんか5.4あたりから配列定義が変わったらしく、使いたいライブラリでエラーが出たため。
いま入ってるやつはボンヤリyum install
したものなので古い。
もともと入ってたやつチェック
# rpm -qa | grep php php-common-5.3.3-46.el6_7.1.x86_64 php-xml-5.3.3-46.el6_7.1.x86_64 php-pear-1.9.4-4.el6.noarch php-cli-5.3.3-46.el6_7.1.x86_64 php-pdo-5.3.3-46.el6_7.1.x86_64 php-gd-5.3.3-46.el6_7.1.x86_64 php-mcrypt-5.3.3-4.el6.x86_64 php-devel-5.3.3-46.el6_7.1.x86_64 php-mbstring-5.3.3-46.el6_7.1.x86_64 php-5.3.3-46.el6_7.1.x86_64 php-mysql-5.3.3-46.el6_7.1.x86_64
やったこと
CentOSのversionをみたところ6.7
# cat /etc/redhat-release CentOS release 6.7 (Final)
CentOS6系のepslリポジトリを入れる
# yum install epel-release
これはでももう入ってた。
CentOS6系のremiリポジトリを入れる
# rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
こっちは入ってなかったのでDLされた。
インストール済みの古いphpを削除する
# yum remove php-*
php5.6をインストールする (無駄なパッケージもあるかもしれない)
# yum install --enablerepo=remi,remi-php56 php php-devel php-mbstring php-pdo php-gd php-mysql php-mcrypt php-xml php-pear
いれたやつのバージョンチェック
# php --version PHP 5.6.30 (cli) (built: Jan 19 2017 08:09:42) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
できた〜
apacheリスタート(php.ini を変更するなら、その後またする)
# service httpd restart
phpの設定ファイルも変更する
場所は以下なので開いて編集
# vi /etc/php.ini
今回変更したのは以下
error_log = /var/log/php_errors.log date.timezone = “Asia/Tokyo” ([Date] 項目) mbstring.language = Japanese ([mbstring] 項目) mbstring.internal_encoding = UTF-8 ([mbstring] 項目)
mbstring.internal_encoding もなんだけど、
mbstring.http_input とかは今後非推奨のようなのでそのまま放っておいた。
PHP: 実行時設定 - Manual
ログファイルの実体がなかったから一応vi
で作っておいたのと、
書き込み権限がないとエラーが書き込まれてくれないとのことだったので、一応chmod 666
をしておいた。
ログは以下で見れる。
less /var/log/php_errors.log
以上
参考URL
CentOS6/CentOS7にPHP5.6/PHP7をyumでインストール - Qiita
PHP: 実行時設定 - Manual
TwitterOAuth.php on line 334 のsyntax errorの原因と修正 - 半地下備忘録
PHP 5.4から配列定義は超簡単に、そして落とし穴も | yohgaki's blog
計算をするときに注意すること
はじめに
初歩的な話なんだが、やってしまったので書いておく。
やらかしたこと
int count = 200; int value = 10; int point = count * (value / 100);
value を百分率にして count にかけたものを最終的な point として扱うということをしたかった。
のだが、 int は小数点以下を切り捨てて扱うため、 (value / 100)
を先に計算しちゃうと、0.1なので0になる。
0を乗算しても0なので、 point は0となる。
やるべきだったこと
int count = 200; int value = 10; int point = (count * value) / 100;
計算の順番を変えて、最後に除算をする。
これだと point は20となる。
もしくは、型を int じゃなくて float とかにしとけば小数点が許容される。
以上