忍者ブログ
RoboCup Junior Japan Rescue Kanto OB

             2005~2013
[1136]  [1135]  [1134]  [1133]  [1132]  [1131]  [1130]  [1129]  [1128]  [1127]  [1126
前回に引き続き、第4回の記事です。

今回はタイトルの通り、対話botを作る話です。

Misskeyのインスタンスを建てた話。①(Misskey紹介編)
Misskeyのインスタンスを建てた話。②(GCPでの建ち上げ編)
Misskeyのインスタンスを建てた話。③(OCIでの建ち上げ編)

普通はMisskeyの立ち上げと来たら設定とか、オブジェクトストレーシとか

そういう記事が次に来るべきだとは思うのですが、

そういうの、もうあるじゃん。

インターネットの海にアホほど転がっている情報を、今更ここで焼き鈍すことに価値があるのか?

と言うことで、ネットの海になさそうな情報を記事として書くことにしました。

 Tips

 ・Misskeyの設定は基本的に管理画面からできます。

 ・~/misskey/.config/default.yml を編集することでサーバ側の設定ができます。

 ・特に個人サーバの場合はCloudflareによる保護を検討しましょう。

 ・オブジェクトストレージはウチはCloudflare R2を使ってます。おススメです。


以上終わり!多分この辺の単語でググればいろいろ出てきます!

まぁ暇だったらウチでちゃんと記事を書くかもしれませんが、余り暇じゃないので多分書かないです。


で、本題なのですが、Twitterではbotと言えば、Streamをでツイートを取得してから自分宛てのツイートを拾って、

それに対して返信をしていく・・・みたいな処理になったかと思うのですが、

何故か今回は違う方法を使っています。

MisskeyにもStream機能はあって実際に使ってみたりもしたのですが、受信はできた

MisskeyのストリーミングAPIはドキュメントが雑にしか書かれていなくて使い方が殆どわからなく、

ネットにも特に新しい情報がないのでよくわからなかったのと、
inputパラメータに何があるかくらいは書いて欲しいんですよね・・・

全てのノートをストリーミングする処理はぼっと用サーバにかかる負担が大きくなることから、


今回は別の手法としてMisskeyに実装されているWebhookを採用しました。

Webhookというのは簡単に言うとネットワークに対して発信できる通知機能のようなものですね。

今回はMisskeyのbotアカウントへのメンションをトリガにして、Webhookを飛ばします。
飛んでくるデータ形式はjsonですので、受け取りさえできれば扱いは簡単です。

それを受けて返信を書いてやろうという魂胆です。





ただ、Webhookは受信用のサーバを建てる必要があります。pythonのモジュールで簡単に建つは建つのですが、

今回はオンプレサーバは使いたくないし、これ以上OCIにもGCPにも負荷をかけたくなかったので

Makeというノーコードツールを導入することにしました。

これはノーコードでアプリケーションパイプラインを構築することができるツールで

月間1000回までなら無料で利用することができます。


まずアカウントを作ってたら左サイドバーのScenariosをクリックしてから右上のCreate a new scenarioを選択します。




そしたらシナリオ画面に行くので真ん中の丸を押して新しいノードを作ります。

今回はWebhookの受信をするので、下の検索欄にwebと打ってWebhooksを選択します。


3つ出てくるのでCustom Webhookを選択します。



作成されたノードでCreate WebhookをクリックするとWebhookのURLが生成されるのでコピーしておきましょう。


できたら背景をクリックした後Add another moduleをクリックし、受信した情報を処理するためのノードを作成します。





ここでGCPのPub/subsとかに送信すれば、GCPを経由してPythonで処理ができたりします。

Make上では直接コードが書けないので、ソースコードを利用したい場合は

何らかの手段でコードが実行できる環境にデータを飛ばさないといけません。
ノーコードツールにコードは邪道!ということらしい?


で、今回はどうしたかというと、GoogleSheetに飛ばしました。

GoogleSheetsというノードがあるので作成します。



一番上のconnetctionのAddを選択することでGoogleアカウントとの紐づけを行うことができます。

そうしたらGoogleDrive上にデータを受信するためのスプレッドシートを作成し、

そのスプレッドシートIDをシート名、転記先のセルをそれぞれ入力します。

最後のValueにセルに記載するデータを入れます。

jsonデータを一度Webhookに流すことでMake側がjsonの形式を認識してくれるので、

特定のキーの情報のみを取得することも可能です。


今回はリプライを行うのでノートのユーザIDとノートのID、@から始まるユーザーネーム、

そしてテキスト本文を取得します。


これでMake側は終了です。シナリオを保存しましょう。

先ほどのScenariosの画面に戻って、作成したシナリオがOFFになっていればONにします。




次にMisskeyの設定です。ここではWebhookの設定とAPIの作成をします。

Misskieyのbotアカウントを作成、ログインしたら

設定->その他の設定からWebhookを選択して「Webhookを作成」をクリックします。

名前は適当に、シークレットも任意で設定しておきましょう。

URLの部分に先ほどMakeでWebhookを作成した時にコピーしたURLを入力します。

あと「Webhookを実行するタイミング」では「メンションされたとき」のみを選択します。

「返信された時」だと既存のノートに対するリプライしか拾えないので注意です。



次にAPIです。

先ほどのように、設定->その他の設定からAPIを選択して「アクセストークンを発行」をクリックします。

Misskeyのアクセストークンはかなり複雑な権限設定が可能です。

今回は「ノートを作成・削除する」だけで十分でしょう。権限は少なければ少ないほどいいです。

設定が終わったら左上のチェックボタンをクリックで完了です。

最近の流行りらしく、アクセストークンはこの時一度しか表示されないので、確実にコピーしてください。
まぁ紛失したら消して作り直そう



で、最後。GoogleSheet側です。今回はGASを使ってなんとかしていきます。

拡張機能→AppsScriptからスクリプトをクリックしてスクリプトを作っていきます。

今回はこんな感じで応答システムを作ってます。
function postToMisskey(text, options) {
  return UrlFetchApp.fetch(
    `https://${options.server}/api/notes/create`, 
    {
      'method': 'POST',
      'headers' : {'Content-Type': 'application/json'},
      'payload':JSON.stringify({i : options.token, text: text})
    }
  );
}

function postToMisskeyWithFile(text, file_ids, options) {
  return UrlFetchApp.fetch(
    `https://${options.server}/api/notes/create`, 
    {
      'method': 'POST',
      'headers' : {'Content-Type': 'application/json'},
      'payload':JSON.stringify({i : options.token, text: text, fileIds: file_ids})
    }
  );
}

function replyToMisskey(text, note_id, options) {
  return UrlFetchApp.fetch(
    `https://${options.server}/api/notes/create`, 
    {
      'method': 'POST',
      'headers' : {'Content-Type': 'application/json'},
      'payload':JSON.stringify({i : options.token, text: text, replyId: note_id})
    }
  );
}

function replyToMisskeyWithFile(text, file_ids, note_id, options) {
  return UrlFetchApp.fetch(
    `https://${options.server}/api/notes/create`, 
    {
      'method': 'POST',
      'headers' : {'Content-Type': 'application/json'},
      'payload':JSON.stringify({i : options.token, text: text, fileIds: file_ids, replyId: note_id})
    }
  );
}

function dmToMisskey(text, usr,options) {
  return UrlFetchApp.fetch(
    `https://${options.server}/api/notes/create`, 
    {
      'method': 'POST',
      'headers' : {'Content-Type': 'application/json'},
      'payload':JSON.stringify({i : options.token, text: text,visibility: 'specified',visibleUserIds:[usr]})
    }
  );
}

function get_question() {
  const options={server: 'サーバ名', token: 'アクセストークン'};
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh = ss.getSheetByName("question");
  if(sh.getRange(1,1).isBlank()){
    console.log("blank");
    return 0;
  }
  let celldata=sh.getRange(1,1).getValue();
  let mention =celldata.split("[/]");
  let re_scrname=/(^@.*?) /;
  let question = mention[3].replace(re_scrname,"");
  sh.getRange(1,1).clear();
}


function search_reply(note_id,question) {
  const options={server: 'サーバ名', token: 'アクセストークン'};
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh = ss.getSheetByName("reply");
  key=sh.getRange(1,1,sh.getLastRow(),1).getValues();
  text=sh.getRange(1,2,sh.getLastRow(),1).getValues();
  file_ids=sh.getRange(1,3,sh.getLastRow(),1).getValues();
  let ans=false;
  for(let i=0;i<sh.getLastRow();i++){
    if(key[i][0]==""){
      continue;
    }

    let re_key=key[i][0].toLowerCase();
    let res = question.match(re_key);
    if(res){
      ans=true;
      if(file_ids[i][0]!=""){
        replyToMisskeyWithFile(text[i][0], file_ids[i], note_id, options);
      }
      else{
        replyToMisskey(text[i][0], note_id, options);
      }
    }
  }

  return 0;
}
上の方は単純に投稿する系の関数ですね。

GASには当然MisskeyAPIは存在しないのでUrlFetchApp.fetchで直接叩いてる感じです。


今回はソースの機能を明確にするために

画像なしノート、画像ありノート、画像なしリプ、画像ありリプ、DMで関数名を分けています。

PythonのMisskeyAPIみたいに、ひとつの関数に集約してもいいと思いますが、今回はソースのわかりやすさ重視です。


処理としてはまず、get_question()で受け取った情報をパースしています。

Make側で要素ごとにノードを作ってバラバラのセルに転記する方法も考えたのですが、後述するトリガの都合上、

Make側のノードの数だけget_question()が走ってしまうのでこのような構成になっています。


次に、パースした情報をsearch_reply()に投げています。

このソースだけではわからない情報なのですが、

別のシートにWebhookで飛んできた質問の単語とそれに対する回答の一覧がありまして、

質問内容に登録した質問の単語があるかを検索し、

それに対応する回答のセルの内容をリプライするような流れになっています。


画像つきノートをする場合にはMisskeyの場合、事前に画像をアップロードした上で

アップロード時の戻り値から、画像IDを拾ってそれをfileIdsとして付与するのですが、

GoogleとMisskeyのセキュリティポリシーの違いから、

GAS上ではMisskieyからの戻りを受けることができないようで、

しょうがなく事前に画像をアップロードしてから、手動で画像IDを確認してシートに貼って参照しています。



で、このソースを稼働させる方法ですが、GASのトリガーを利用します。


左サイドバーのトリガーを選択、画面右下の「トリガーを追加」から新しいトリガーを作ります。


このように設定してスプレッドシートの変更時にget_question()関数が走るように設定します。

これでbotにリプライが来たらWebhookがmakeのシナリオに飛び、

それを受けてMakeのシナリオによってGoogleSheetに内容が転記され、

スプレッドシートが変更されるのでそれをトリガにGASが走ってbotが返信する

という一連のシステムを作ることができました。



但し、勘の言い方は気づくかと思いますが、スプレッドシート変更がトリガになっているため

同一スプレッドシート内のシートを手動で変更すると、そのたびにget_question()関数が走ってしまいます。

実はそれを回避するように今回のソースは作られているのですが、

冷静に考えると、変なソース作らなくても受け答えの対応表を別のスプレッドシートに置くだけで解決しましたねコレ

まぁとりあえず動いているのでヨシ!ということで(おい



と、まぁ最後駆け足でしたが、だいたいこんな感じです。


そんなに難しい事はしていないつもりなので、Misskeyで対話型botを作りたい方は参考にして頂ければと思います。


因みにMakeはChatGPTが使えるので、APIトークンをお持ちの方はbotに簡単に実装ができるはずなのでおススメです。

ではでは~~~~

(^・ω・)ノ RadiumProduction at curonet
Comments
※コメントは内容確認後に手動で公開するようにしております。反映までしばらくお待ちください。
Your Name
Title
color
Comment
 

カレンダー
10 2024/11 12
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
最新CM
[05/09 ONE RoboCuper]
[05/07 HDD ほしいよー]
[04/21 ブラック3辛]
[12/26 bols-blue]
[06/08 ONE RoboCuper]
かうんた
カウンター カウンター
らじぷろ目次
らじぷろ検索機
プロフィール
HN:
Luz
性別:
男性

PR

忍者ブログ 2007-2021,Powered by Radium-Luz-Lα+-Rescatar in RadiumProduction [PR]


Related Posts Plugin for WordPress, Blogger...