Mirai-js
Mirai-jsは、MiraiのコミュニティSDKであるNode.jsプラットフォームで実行されるQQロボット開発フレームワークです。
Mirai-jsは、mirai-consoleのmirai-api-httpプラグインに基づいています。mirai-api-httpは、httpを介してMiraiの完全なプラットフォームインターフェイスを提供します。
フレームワークを開発するのはこれが初めてです。ロボットの機能はすべてmirai-api-httpで実装されているため、フレームワークの設計に重点を置いています。実際に多くのことを学び、達成感を持っています。
プロジェクトウェアハウス:https://github.com/Drincann/Mirai-js
開発ドキュメント:https://drincann.github.io/Mirai-js
ライブラリ、フレームワーク、SDK
SDKは通常、フレームワーク、ライブラリ、ドキュメント、さらにはハードウェアなど、開発者に提供される一連の開発ツールを指します。
ライブラリやフレームワークに関しては、境界が明確でない場合があります。
一般的に、フレームワークの複雑さはライブラリの複雑さよりも高くなります。これは、フレームワークが開発プロセス全体の主要部分、つまり主要な制御フローを引き継ぐためです。このライブラリは、比較的独立した機能ブロックのカプセル化、わずかに高い粒度でより便利なインターフェイスの提供、および開発効率の向上に重点を置いています。
アプリケーションの開発では、メインの制御フローが1つしかないため、フレームワークを1つしか導入できませんが、複数のライブラリを導入して、複数の部分を担当させることができます。
Mirai-jsのいくつかのモジュール
Mirai-jsには3つのモジュール、、、、がBot
ありMessage
、Middleware
それらを公開して、キャッチされたクラスを開発します。
と呼ばれるモジュールもありますWaiter
。これもクラスを公開しBot
ますが、にのみ公開されます。Bot
内部クラスとして、Bot
パブリックフィールドを介して外部インターフェイスを提供します。
Bot
フレームの中心であり、他のモジュールがBot
拡張を行うために周りにあります。そのインスタンスは、mirai-api-httpサービスのセッションのライフサイクル全体を引き継ぎます。
Message
Bot
データを送信するときにメッセージを生成するための便利なインターフェイスを提供するために使用されます。
Middleware
ミドルウェアは、モードを実装し、一連のミドルウェア関数を提供し、開発者がBot
さまざまなメッセージイベントを簡単に処理できるようにミドルウェアインターフェイスをカスタマイズするためにパッケージ化されたクラスです。
Waiter
達成されたBot
同期メッセージフローio、彼はioの任意の部分からの非同期メッセージフローの妨害を許可し、指定されたユーザーからのリターンを入力します。
カップリングの扱い方
これは頭痛の種です。Javascript自体は動的な型であり、インターフェースがないため、フレームワークの設計に大きな困難をもたらします。
メッセージ
私が作るために、Message
より簡単にメッセージを送信する際に、直接パスを可能にするモジュールを使用してMessage
インスタンスを。
Bot.sendMessage
MessageChain
パラメータを受け取ると、MessageType[]
しばらく時間MessageType
がかかりますが、より複雑になるため、Message
世代をに渡しましたMesageChain
。
初期インターフェースは次のようになります。
bot.sendMessage({
message: new Message().addText('Hello').addAt(1019933576).done(),
// ...
});
Message
addxxx
内部インスタンスを維持するために使用されるメソッドの例は、内部維持のための最後の呼び出しのメソッドをしばらくMessageChain
押します。MessageType
done
MessageChain
しかし、この設計は直感的ではないため、アプリケーションを開発するときに呼び出すのを忘れることがdone
あり、この設計は非常に冗長に見えます。
そこで、インスタンスを直接渡すことができるようにモジュールを結合して作成Bot
します。Message
Bot.sendMessage
Message
bot.sendMessage({
message: new Message().addText('Hello').addAt(1019933576),
// ...
});
後で、Typescript書き込み型宣言を使用するときはMessageChainGetable
、インターフェイスを介してそれらを分離しますが、Bot.sendMessage
簡単に拡張することもできます。
ミドルウェア
このモジュールは、Waiter
いくつかの複雑な変更が行われたときに実行される設計で多くのことを述べ、Typescript
時間を使用して元に戻すと、理解しやすく、論理的な設計とBot
ドッキングのしやすさが向上します。
Middleware
これは次のように使用されます。
bot.on('FriendMessage', new Middleware()
.textProcessor()
.catch(error => console.log(error))
.done(async data => {
// 开发者的消息处理逻辑
});
変更なしでインターフェイスフォームを介していくつかの変更を行った後、ロジックが変更されました。これはWaiter
、コールバック関数の実装がコールバック関数を使用して通常のミドルウェアとミドルウェアを区別する必要があるためですWaiter
。
最初にMiddleware
、done
メソッドはインレットミドルウェアを使用してコールバック関数を返します。
Waiter
これは次のように使用されます。
bot.on('FriendMessage', new Middleware()
.textProcessor()
.done(async data => {
if(data.text?.includes('/unload')){
bot.sendMessage({
message: new Message().addText('请输入 /confirm 确认 /unload 操作')});
// 将异步操作阻塞,等待消息
// wait 方法将等待一次事件,并将回调函数的返回值返回到外层
let userInput = await bot.waiter.wait('FriendMessage' , data => data.messageChain.filter((val) => val.type == 'Plain').map(val => val.text).join(''));
if(userInput?.includes('/confirm')){
// 指定操作
}
}
});
または:
bot.on('FriendMessage', new Middleware()
.textProcessor()
.done(async data => {
if(data.text?.includes('/unload')){
bot.sendMessage({
message: new Message().addText('请输入 /confirm 确认 /unload 操作')});
// 将异步操作阻塞,等待消息
// wait 方法将等待一次事件,并将回调函数的返回值返回到外层
let userInput = await bot.waiter.wait('FriendMessage' , new Middelware().FriendFilter([data.sender.id]).textProcessor().done(data => data.text););
if(userInput?.includes('/confirm')){
// 指定操作
}
}
});
通りwait
の方法最初にすべての私たちが直接呼び出され、通常のコールバック関数に戻り、待っている、コールバックの戻り値を取得する必要があります。ミドルウェアを使用したコールバック関数エントリの場合、彼はnext
ミドルウェア再帰チェーンのパッケージによるものであり、戻り値は何の入り口も意味しません。
そのため、コールバック関数のエントリがパックされてMiddleware
いるdone
ことを返すメソッドがあります。2番目のパラメータの入り口を追加するresolve
と、開発者のコールバック関数が、渡されたresolve
コールバックの戻り値を提供します。
そのWaiter
ため、非同期でミドルウェアパッケージタスクを実行する方法が提供されましたが、共通関数が直接同期されて待機するため、今回はコールバック関数で通常の関数エントリとミドルウェア関数エントリを区別する必要がありますが、ミドルウェア関数エントリはパッケージ化する必要がありますPromise Then wait同期的に、しかしそれらは本質的に機能であり、違いはありません。
現時点では、2つの解決策があります。
まず、関数エントリに渡されるパラメータの数を区別して判断できます。これは、関数エントリの非同期ミドルウェアパッケージの可能性を提供し、resolve
パラメータを追加する一方で、平均的なコールバック関数は1つのパラメータのみである必要があるためです。しかし、これは安定していません。開発者から渡された通常のコールバック関数にパラメーターが1つしかないためです。
第二の溶液を作ることです方法は、公共分野は、これまでの事例では、現在のインスタンス、関数のエントリとメンテナンスを返す必要が関数や判断渡されているあなたがエリアを分離することができるように、インスタンスを、しかし、問題はであり、同時に、メソッドを変更する必要があります。これは、関数エントリではなくインスタンスを返すため、結合の度合いが増すだけでなく、複雑さが増すためですが、よくわからなかったので、彼はこれを行いました。Middleware
done
Waiter
Middleware
Bot
on
その後、私は一瞬を取り、活字体は、型宣言を記述することを決定した後に途中で、見て、私Middleware
とMessage
それぞれ二つのインタフェースを宣言EntryGetable
し、MessageChain
その後、JSでクラスとして適切なインタフェースを実装し、それらを継承させる、でBot
、それはインスタンスであるかどうかを確認しますこれらのインターフェイスクラスの。これは分離の試みです。
最後に、ミドルウェア関数は、インレット、つまりdone
リターンインレット、直接パッケージ化されたPromise、およびresolve
外部へのコールバックの開発者に突然気付く場合があります。このように、通常のコールバック関数とミドルウェアの動作はまったく同じになります。
これを認識して、EntryGetable
インターフェイスを削除し、パックしてMiddleware.done
から、関連するすべての依存関係を変更しました。
ミドルウェアパターン
最初に実装されたミドルウェアは非常にばかげていて、制御なしですべてのミドルウェアを順番にトラバースしただけでしたが、後でこの設計を使用すると、一部のミドルウェアを開発するときに大きな問題が発生しました。
そこで、情報を確認して、最も基本的なミドルウェアモデルを実現しました。
/**
* @description 生成一个带有中间件的事件处理器
* @param {function} callback 事件处理器
*/
done(callback) {
return data => {
return new Promise(resolve => {
try {
// 从右侧递归合并中间件链
this.middleware.reduceRight((next, middleware) => {
return () => middleware(data, next);
}, async () => {
// 最深层递归,即开发者提供的回调函数
let returnVal = callback instanceof Function ? (await callback(data)) : undefined;
// 异步返回
resolve(returnVal);
})();
} catch (error) {
// 优先调用开发者的错误处理器
if (this.catcher) {
this.catcher(error);
} else {
throw error;
}
}
});
};
}
コアロジックは非常にエレガントで、次の3行に抽象化できます。
middlewareArray.reduceRight((next, middleware) => {
return () => middleware(data, next);
}, callback)();
これは実際には再帰的なパッケージングプロセスでありcallback
、外側のパッケージを保持し、最も外側の関数エントリを取得するための最も深いポイントから、より外側の層next
がミドルウェアのより深い層を呼び出します。
非同期
このフレームワークを作成すると、Promisesの理解がわずかに高まり、Promisesの使用能力が大幅に向上します。また、サンクパッケージングによって実現されたジェネレーターの非同期タスク同期アルゴリズムについても触れました。包装方法が非常にエレガントでなく、使用されていないというだけです。
良いデザイン
ネイティブAPIからライブラリやフレームワークに至るまで、インターフェースの粒度はますます大きくなっています。これにより、開発効率がますます高くなり、自由度も低下します。
ですから、良いデザインはプログレッシブデザインであり、プログレッシブフレームワークは開発者により多くの自由を与えることができると思います。この段階的なアプローチは、独立したモジュールによって実現されます。モジュールが独立しているほど、開発者は設計のための余地が大きくなります。
このフレームワークを開発する当初の意図は非常に単純です。Tencentが商用ロボットプラットフォームのバッチを殺した後、私は緊急にポジションをシフトする必要がありました。Miraiは比較的安全です。結局のところ、オープンソースです。しかし、私はJavaをあまり使用していません。ある程度の学習コストがかかり、Java開発の効率が少し悪くなる可能性があります。
私はjsに非常に慣れているので、jsプラットフォームでMiraiコミュニティSDK、node-mirai、mirai-tsなどをいくつか見ました。エクスペリエンスはあまり良くなく、ソースコードは安定していませんでした。そこで、まずmirai-api-httpのインターフェースを理解し、いくつかのライブラリをカプセル化して使用しますが、始めてみると、これができると感じています。良いデザインが頭に浮かび、やりたかったのです。それを書いてください。
奇妙なこと
メッセージプッシュにはmirai-api-httpが提供するWebSocketインターフェースを使用しています。イベント処理セクションの作成中に誤ってバグに遭遇しました。
その夜、イベント処理の簡単なロジックを実装して夕食に行きました。テストコードはまだハングしていて、安定性をテストするときが来ました。食べた後、ロボットは何も呼び出さないことに気づきました。中断すると、サーバーからローカルにニュースがプッシュされていないことがわかりました。
いくつかのテストの後、約3〜5分で、WebSocket接続にデータプッシュがない場合、サーバーはこのリンクを不可解に無視し、メッセージをプッシュしなくなりますが、リンクは切断されていません。サーバーpingこの問題を解決します。
文書を見逃してしまったのではないかと思い、長い間調べても見つかりませんでした。
そのため、問題#255はmirai-api-httpで発生しましたが、開発者は関連するロジックはないと述べました。graia(mirai python sdk)の後、誰かが関連する問題に気づき、通信した後も問題を解決しました。
不思議なことに、後でコミュニティSDKが同様の処理を行わなかったことがわかりました。彼らのロボットは常に数分以内にニュースのプッシュを停止したので、いくつかのPRについても言及しました。それらのいくつかは長い間荒廃していたようです。 、そして今まで誰も合併していません。
開発ツール
mirai-tsの開発者は非常に強力です。WebSocketで無視されたバグを修正すると、プロジェクトにはコミット前にeslintとprettierのチェックが含まれていることがわかりました。調べてみると、gitフックで実現されていることがわかりました。これを行うために使用されるハスキーと呼ばれる成熟したパッケージがあります。そこで、mirai-tsの設定をコピーしました。また、eslintの構成についても学びました。
コミット仕様
今回は実際にgitを適用していましたが、これまではadd、commit、push push、pull pullの順に行っていたのですが、実際に複数のブランチで開発する方法を実際に学んだのは今回が初めてです。同時に。最終マージでコミット履歴を処理する方法。
Mirai-jsの最初のいくつかのコミットを見てみると、それは本当に嫌です。その時、私はいつも一日書いていて、夕方の終わりまで提出せず、それから今日私がしたすべての仕事をメッセージに注意深く書きました。
後で、私は誰もがよりきめ細かい提出を提唱し、メッセージの書き方に関するいくつかの仕様を学んだことに気づきました。
開発の初期作業量は比較的大きく、基本的にはいくつかのコア機能をカプセル化したもので、1日に80回以上渡したのを覚えています。その後、githubの緑色のグリッドの色深度が相対的であることが発見されました。
海抜
Mirai-jsは最近npmにリリースされたばかりで、これは最初のリリースであり、いくつかのバージョン番号制御仕様についても学びました。
今後どうするか
現在、Mirai-jsに基づくアプリケーションの開発は独立しています。つまり、複数のロボットアプリケーションで複数のスクリプトを実行する必要があり、非常に不便です。
現在のアイデアは、プラグインシステムを構築することですが、既存のシステムと結合する必要があるかどうかはまだ検討中です。
結論として
月の初めから次々と開発が始まり、今では半月、現在は2021.02.22で、主な作業は基本的に数日前に完了し、いくつかの調整の背後にあり、Waiter
外部に加えてケーキの上のアイシング。
今までに30個以上の星があり、実用的なものを書くことは確かに達成感です。