mirror of
https://github.com/nullnyat/NullcatChan.git
synced 2025-04-29 07:47:20 +09:00
Nullcatchan!
This commit is contained in:
parent
eb853be7e8
commit
8bf9550139
@ -1,6 +1,6 @@
|
|||||||
config.json
|
config.json
|
||||||
font.ttf
|
font.ttf
|
||||||
ai.*
|
nullcat.*
|
||||||
*.md
|
*.md
|
||||||
*.png
|
*.png
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
26
Dockerfile_development
Normal file
26
Dockerfile_development
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
FROM node:lts-bullseye
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y tini
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG enable_mecab=1
|
||||||
|
|
||||||
|
RUN if [ $enable_mecab -ne 0 ]; then apt-get update \
|
||||||
|
&& apt-get install mecab libmecab-dev mecab-ipadic-utf8 make curl xz-utils file sudo tzdata --no-install-recommends -y \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt-get/lists/* \
|
||||||
|
&& cd /opt \
|
||||||
|
&& git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git \
|
||||||
|
&& cd /opt/mecab-ipadic-neologd \
|
||||||
|
&& ./bin/install-mecab-ipadic-neologd -n -y \
|
||||||
|
&& rm -rf /opt/mecab-ipadic-neologd \
|
||||||
|
&& echo "dicdir = /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd/" > /etc/mecabrc \
|
||||||
|
&& apt-get purge git make curl xz-utils file -y; fi
|
||||||
|
|
||||||
|
COPY ../NullcatChan-old /nullcat-chan
|
||||||
|
|
||||||
|
WORKDIR /nullcat-chan
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
|
CMD npm run dev
|
26
Dockerfile_production
Normal file
26
Dockerfile_production
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
FROM node:lts-bullseye
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y tini
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG enable_mecab=1
|
||||||
|
|
||||||
|
RUN if [ $enable_mecab -ne 0 ]; then apt-get update \
|
||||||
|
&& apt-get install mecab libmecab-dev mecab-ipadic-utf8 make curl xz-utils file sudo tzdata --no-install-recommends -y \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt-get/lists/* \
|
||||||
|
&& cd /opt \
|
||||||
|
&& git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git \
|
||||||
|
&& cd /opt/mecab-ipadic-neologd \
|
||||||
|
&& ./bin/install-mecab-ipadic-neologd -n -y \
|
||||||
|
&& rm -rf /opt/mecab-ipadic-neologd \
|
||||||
|
&& echo "dicdir = /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd/" > /etc/mecabrc \
|
||||||
|
&& apt-get purge git make curl xz-utils file -y; fi
|
||||||
|
|
||||||
|
COPY ../NullcatChan-old /nullcat-chan
|
||||||
|
|
||||||
|
WORKDIR /nullcat-chan
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
|
CMD npm start
|
80
README.md
80
README.md
@ -1,61 +1,67 @@
|
|||||||
<h1><p align="center"><img src="./ai.svg" alt="藍" height="200"></p></h1>
|
<p align="center">
|
||||||
<p align="center">An Ai for Misskey. <a href="./torisetu.md">About Ai</a></p>
|
<img src="./nullcatchan.png" alt="nullcatchan!" height="200">
|
||||||
|
</p>
|
||||||
|
|
||||||
## これなに
|
|
||||||
Misskey用の日本語Botです。
|
|
||||||
|
|
||||||
## インストール
|
# これってなに?
|
||||||
> Node.js と npm と MeCab (オプション) がインストールされている必要があります。
|
|
||||||
|
Misskey用の[Aiベース](https://github.com/syuilo/ai)の日本語Botです。
|
||||||
|
|
||||||
|
## 変更点
|
||||||
|
|
||||||
|
- 自動投稿や自動返信、pingに対する返答の内容
|
||||||
|
- ゴママヨに反応([ここ](https://github.com/ThinaticSystem/gomamayo.js)から持ってきた)
|
||||||
|
- ゲーム機能と絵文字を自動生成するやつの削除
|
||||||
|
- GitHubのStatusを教えてくれる機能
|
||||||
|
- CloudflareのStatusを教えてくれる機能
|
||||||
|
- やることを決めてくれる
|
||||||
|
- 気圧の状況を教えてくれる
|
||||||
|
- 時報機能
|
||||||
|
- シェル芸機能([ここ](https://github.com/sim1222/shellgei-misskey)から持ってきた)
|
||||||
|
- 怪レい曰本语に変換してくれる機能
|
||||||
|
|
||||||
|
## 導入方法
|
||||||
|
|
||||||
|
> Node.js と npm と MeCab がインストールされている必要があります。
|
||||||
|
|
||||||
まず適当なディレクトリに `git clone` します。
|
まず適当なディレクトリに `git clone` します。
|
||||||
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
||||||
``` json
|
``` json
|
||||||
{
|
{
|
||||||
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
||||||
"i": "藍として動かしたいアカウントのアクセストークン",
|
"i": "ぬるきゃっとちゃん!として動かしたいアカウントのアクセストークン",
|
||||||
"master": "管理者のユーザー名(オプション)",
|
"master": "管理者のユーザー名(オプション)",
|
||||||
"notingEnabled": "ランダムにノートを投稿する機能を無効にする場合は false を入れる",
|
"notingEnabled": "ランダムにノートを投稿する機能。true(on) or false(off)",
|
||||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) を有効にする場合は true を入れる (無効にする場合は false)",
|
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) true or false",
|
||||||
"chartEnabled": "チャート機能を無効化する場合は false を入れてください",
|
"serverMonitoring": "サーバー監視の機能(重かったりすると教えてくれるよ。)true or false",
|
||||||
"reversiEnabled": "藍とリバーシで対局できる機能を有効にする場合は true を入れる (無効にする場合は false)",
|
"mecab": "MeCab のインストールパス (ソースからインストールした場合、大体は /usr/local/bin/mecab) true or false",
|
||||||
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
|
"mecabDic": "MeCab の辞書ファイルパス",
|
||||||
"mecab": "MeCab のインストールパス (ソースからインストールした場合、大体は /usr/local/bin/mecab)",
|
"memoryDir": "memory.jsonの保存先(オプション、デフォルトは'.'(レポジトリのルートです))",
|
||||||
"mecabDic": "MeCab の辞書ファイルパス (オプション)",
|
"shellgeiUrl": "シェル芸BotのAPIのURLです(デフォルトではhttps://websh.jiro4989.com/api/shellgei)"
|
||||||
"memoryDir": "memory.jsonの保存先(オプション、デフォルトは'.'(レポジトリのルートです))"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
`npm install` して `npm run build` して `npm start` すれば起動できます
|
`npm install` して `npm run build` して `npm start` すれば起動できます。
|
||||||
|
|
||||||
## Dockerで動かす
|
### Dockerで動かす
|
||||||
まず適当なディレクトリに `git clone` します。
|
|
||||||
|
まず適当なディレクトリに `git clone` します。<br>
|
||||||
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
||||||
(MeCabの設定、memoryDirについては触らないでください)
|
(MeCabの設定、memoryDirについては触らないでください)
|
||||||
``` json
|
``` json
|
||||||
{
|
{
|
||||||
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
||||||
"i": "藍として動かしたいアカウントのアクセストークン",
|
"i": "ぬるきゃっとちゃん!として動かしたいアカウントのアクセストークン",
|
||||||
"master": "管理者のユーザー名(オプション)",
|
"master": "管理者のユーザー名(オプション)",
|
||||||
"notingEnabled": "ランダムにノートを投稿する機能を無効にする場合は false を入れる",
|
"notingEnabled": "ランダムにノートを投稿する機能。true(on) or false(off)",
|
||||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) を有効にする場合は true を入れる (無効にする場合は false)",
|
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) true or false",
|
||||||
"chartEnabled": "チャート機能を無効化する場合は false を入れてください",
|
|
||||||
"reversiEnabled": "藍とリバーシで対局できる機能を有効にする場合は true を入れる (無効にする場合は false)",
|
|
||||||
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
|
|
||||||
"mecab": "/usr/bin/mecab",
|
"mecab": "/usr/bin/mecab",
|
||||||
"mecabDic": "/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd/",
|
"mecabDic": "/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd/",
|
||||||
"memoryDir": "data"
|
"memoryDir": "data",
|
||||||
|
"shellgeiUrl": "シェル芸BotのAPIのURLです(デフォルトではhttps://websh.jiro4989.com/api/shellgei)"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
`docker-compose build` して `docker-compose up` すれば起動できます。
|
`npm install` して `npm run docker` すれば起動できます。<br>
|
||||||
`docker-compose.yml` の `enable_mecab` を `0` にすると、MeCabをインストールしないようにもできます。(メモリが少ない環境など)
|
`docker-compose.yml` の `enable_mecab` を `0` にすると、MeCabをインストールしないようにもできます。(メモリが少ない環境など)
|
||||||
|
|
||||||
## フォント
|
#### 一部の機能にはフォントが必要です。NullcatChan!にはフォントは同梱されていないので、ご自身でフォントをインストールしてそのフォントを`font.ttf`という名前でインストールディレクトリに設置してください。
|
||||||
一部の機能にはフォントが必要です。藍にはフォントは同梱されていないので、ご自身でフォントをインストールディレクトリに`font.ttf`という名前で設置してください。
|
#### NullcatChan!は記憶の保持にインメモリデータベースを使用しており、僕のインストールディレクトリに `memory.json` という名前で永続化されます。
|
||||||
|
|
||||||
## 記憶
|
|
||||||
藍は記憶の保持にインメモリデータベースを使用しており、藍のインストールディレクトリに `memory.json` という名前で永続化されます。
|
|
||||||
|
|
||||||
## ライセンス
|
|
||||||
MIT
|
|
||||||
|
|
||||||
## Awards
|
|
||||||
<img src="./WorksOnMyMachine.png" alt="Works on my machine" height="120">
|
|
||||||
|
@ -2,11 +2,14 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
dockerfile: Dockerfile_production
|
||||||
|
context: ../NullcatChan-old
|
||||||
args:
|
args:
|
||||||
- enable_mecab=1
|
- enable_mecab=1
|
||||||
volumes:
|
volumes:
|
||||||
- './config.json:/ai/config.json:ro'
|
- './config.json:/nullcat-chan/config.json:ro'
|
||||||
- './font.ttf:/ai/font.ttf:ro'
|
- './font.ttf:/nullcat-chan/font.ttf:ro'
|
||||||
- './data:/ai/data'
|
- './data:/nullcat-chan/data'
|
||||||
restart: always
|
restart: always
|
||||||
|
environment:
|
||||||
|
TZ: Asia/Tokyo
|
||||||
|
5
docker-compose_development.yml
Normal file
5
docker-compose_development.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile_development
|
184
ngwords.txt
Normal file
184
ngwords.txt
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
オーガズム
|
||||||
|
オルガスムス
|
||||||
|
メスイキ
|
||||||
|
ポルノ
|
||||||
|
-ポルノグラフィティ
|
||||||
|
にょろり
|
||||||
|
オナホ
|
||||||
|
アクメ
|
||||||
|
淫夢
|
||||||
|
チンカス
|
||||||
|
ふたなり
|
||||||
|
マラ
|
||||||
|
-ガテマラ
|
||||||
|
-グァテマラ
|
||||||
|
TENGA
|
||||||
|
種付
|
||||||
|
たねつ
|
||||||
|
たねづ
|
||||||
|
アスペ
|
||||||
|
-アスペクト
|
||||||
|
クリトリス
|
||||||
|
dick
|
||||||
|
セックス
|
||||||
|
セクロス
|
||||||
|
-ぱちんこ
|
||||||
|
-がちんこ
|
||||||
|
-喉ちんこ
|
||||||
|
-のどちんこ
|
||||||
|
ちんこ
|
||||||
|
ちんぽ
|
||||||
|
chinko
|
||||||
|
chinpo
|
||||||
|
tinko
|
||||||
|
tinpo
|
||||||
|
-ビックリマンコラボ
|
||||||
|
-ウルトラマンコスモス
|
||||||
|
まんこ
|
||||||
|
manko
|
||||||
|
ペニス
|
||||||
|
penis
|
||||||
|
vagina
|
||||||
|
ヴァギナ
|
||||||
|
バギナ
|
||||||
|
肉棒
|
||||||
|
勃起
|
||||||
|
ぼっき
|
||||||
|
精子
|
||||||
|
精液
|
||||||
|
射精
|
||||||
|
ザーメン
|
||||||
|
ザー汁
|
||||||
|
放射性
|
||||||
|
金玉
|
||||||
|
キンタマ
|
||||||
|
semen
|
||||||
|
体位
|
||||||
|
淫乱
|
||||||
|
アナル
|
||||||
|
anus
|
||||||
|
おっぱい
|
||||||
|
巨乳
|
||||||
|
貧乳
|
||||||
|
爆乳
|
||||||
|
虚乳
|
||||||
|
普乳
|
||||||
|
適乳
|
||||||
|
美乳
|
||||||
|
豊乳
|
||||||
|
超乳
|
||||||
|
魔乳
|
||||||
|
きょにゅう
|
||||||
|
きょにゅー
|
||||||
|
ひんにゅう
|
||||||
|
ひんにゅー
|
||||||
|
ばくにゅう
|
||||||
|
ばくにゅー
|
||||||
|
ふにゅう
|
||||||
|
ふにゅー
|
||||||
|
てきにゅう
|
||||||
|
てきにゅー
|
||||||
|
びにゅう
|
||||||
|
びにゅー
|
||||||
|
ほうにゅう
|
||||||
|
ほうにゅー
|
||||||
|
ちょうにゅう
|
||||||
|
ちょうにゅー
|
||||||
|
まにゅう
|
||||||
|
まにゅー
|
||||||
|
何カップ
|
||||||
|
乳首
|
||||||
|
ちくび
|
||||||
|
ビーチク
|
||||||
|
自慰
|
||||||
|
オナニ
|
||||||
|
オナ二
|
||||||
|
オナヌ
|
||||||
|
マスターベーション
|
||||||
|
マスタベーション
|
||||||
|
シコい
|
||||||
|
シコっ
|
||||||
|
脱げ
|
||||||
|
ぬげ
|
||||||
|
脱いで
|
||||||
|
ぬいで
|
||||||
|
脱ごう
|
||||||
|
ぬごう
|
||||||
|
喘いで
|
||||||
|
あえいで
|
||||||
|
クンニ
|
||||||
|
-フェラーリ
|
||||||
|
-カフェラテ
|
||||||
|
-フェライト
|
||||||
|
-フェラガモ
|
||||||
|
-フェラーラ
|
||||||
|
-フェライニ
|
||||||
|
-フェラーズ
|
||||||
|
-フェラリア
|
||||||
|
フェラ
|
||||||
|
デリヘル
|
||||||
|
-姦し
|
||||||
|
姦
|
||||||
|
犯す
|
||||||
|
ヤリマン
|
||||||
|
ヤリチン
|
||||||
|
パイパン
|
||||||
|
中出し
|
||||||
|
中で出
|
||||||
|
スカトロ
|
||||||
|
ケツ
|
||||||
|
コキ
|
||||||
|
手マン
|
||||||
|
潮吹
|
||||||
|
下乳
|
||||||
|
横乳
|
||||||
|
指マン
|
||||||
|
パイズリ
|
||||||
|
ペェズリ
|
||||||
|
-スレイプニル
|
||||||
|
レイプ
|
||||||
|
オフパコ
|
||||||
|
パコる
|
||||||
|
ドピュ
|
||||||
|
ブリュ
|
||||||
|
-ちんちん電車
|
||||||
|
ちんちん
|
||||||
|
ぽこちん
|
||||||
|
マン汁
|
||||||
|
膣
|
||||||
|
下の口
|
||||||
|
コンドーム
|
||||||
|
ハメ撮り
|
||||||
|
ちん毛
|
||||||
|
まん毛
|
||||||
|
陰毛
|
||||||
|
インポ
|
||||||
|
童貞もらって
|
||||||
|
童貞貰
|
||||||
|
童貞をもらって
|
||||||
|
童貞を貰
|
||||||
|
ケツの穴
|
||||||
|
糞を出
|
||||||
|
糞が出
|
||||||
|
ヨツンヴァイン
|
||||||
|
しゃぶれ
|
||||||
|
邪淫
|
||||||
|
処女
|
||||||
|
早漏
|
||||||
|
オナホ
|
||||||
|
アクメ
|
||||||
|
淫夢
|
||||||
|
チンカス
|
||||||
|
ふたなり
|
||||||
|
マラ
|
||||||
|
-ガテマラ
|
||||||
|
-グァテマラ
|
||||||
|
TENGA
|
||||||
|
種付
|
||||||
|
たねつ
|
||||||
|
たねづ
|
||||||
|
アスペ
|
||||||
|
-アスペクト
|
||||||
|
クリトリス
|
||||||
|
dick
|
||||||
|
おしっこ
|
23
package.json
23
package.json
@ -1,14 +1,20 @@
|
|||||||
{
|
{
|
||||||
"_v": "1.5.0",
|
"version": "2.1.0",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./built",
|
"docker:dev": "cross-env DOCKER_ENV=development docker-compose -f docker-compose.yml -f docker-compose_development.yml up -d --build && docker-compose logs -f",
|
||||||
|
"docker": "cross-env DOCKER_ENV=production docker-compose up -d --build && docker-compose logs -f",
|
||||||
|
"dev": "cross-env NODE_ENV=development node ./built",
|
||||||
|
"start": "cross-env NODE_ENV=production node ./built",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/accurate-interval": "1.0.0",
|
||||||
"@types/chalk": "2.2.0",
|
"@types/chalk": "2.2.0",
|
||||||
|
"@types/humanize-duration": "3.27.1",
|
||||||
"@types/lokijs": "1.5.4",
|
"@types/lokijs": "1.5.4",
|
||||||
|
"@types/moji": "0.5.0",
|
||||||
"@types/node": "16.0.1",
|
"@types/node": "16.0.1",
|
||||||
"@types/promise-retry": "1.1.3",
|
"@types/promise-retry": "1.1.3",
|
||||||
"@types/random-seed": "0.3.3",
|
"@types/random-seed": "0.3.3",
|
||||||
@ -17,13 +23,19 @@
|
|||||||
"@types/twemoji-parser": "13.1.1",
|
"@types/twemoji-parser": "13.1.1",
|
||||||
"@types/uuid": "8.3.1",
|
"@types/uuid": "8.3.1",
|
||||||
"@types/ws": "7.4.6",
|
"@types/ws": "7.4.6",
|
||||||
|
"accurate-interval": "1.0.9",
|
||||||
"autobind-decorator": "2.4.0",
|
"autobind-decorator": "2.4.0",
|
||||||
"canvas": "2.8.0",
|
"canvas": "2.8.0",
|
||||||
"chalk": "4.1.1",
|
"chalk": "4.1.1",
|
||||||
|
"cjp": "1.2.3",
|
||||||
|
"gomamayo-js": "0.2.1",
|
||||||
|
"humanize-duration": "3.27.1",
|
||||||
"lokijs": "1.5.12",
|
"lokijs": "1.5.12",
|
||||||
"memory-streams": "0.1.3",
|
"memory-streams": "0.1.3",
|
||||||
"misskey-reversi": "0.0.5",
|
"misskey-reversi": "0.0.5",
|
||||||
"module-alias": "2.2.2",
|
"module-alias": "2.2.2",
|
||||||
|
"moji": "0.5.1",
|
||||||
|
"node-fetch": "2.6.7",
|
||||||
"promise-retry": "2.0.1",
|
"promise-retry": "2.0.1",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"reconnecting-websocket": "4.4.0",
|
"reconnecting-websocket": "4.4.0",
|
||||||
@ -33,9 +45,10 @@
|
|||||||
"timeout-as-promise": "1.0.0",
|
"timeout-as-promise": "1.0.0",
|
||||||
"ts-node": "10.0.0",
|
"ts-node": "10.0.0",
|
||||||
"twemoji-parser": "13.1.0",
|
"twemoji-parser": "13.1.0",
|
||||||
"typescript": "4.3.5",
|
"typescript": "4.5.5",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"ws": "7.5.2"
|
"ws": "7.5.2",
|
||||||
|
"zod": "3.11.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@koa/router": "9.4.0",
|
"@koa/router": "9.4.0",
|
||||||
@ -43,9 +56,11 @@
|
|||||||
"@types/koa": "2.13.1",
|
"@types/koa": "2.13.1",
|
||||||
"@types/koa__router": "8.0.4",
|
"@types/koa__router": "8.0.4",
|
||||||
"@types/websocket": "1.0.2",
|
"@types/websocket": "1.0.2",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"jest": "26.6.3",
|
"jest": "26.6.3",
|
||||||
"koa": "2.13.1",
|
"koa": "2.13.1",
|
||||||
"koa-json-body": "5.3.0",
|
"koa-json-body": "5.3.0",
|
||||||
|
"prettier": "2.5.1",
|
||||||
"ts-jest": "26.5.6",
|
"ts-jest": "26.5.6",
|
||||||
"websocket": "1.0.34"
|
"websocket": "1.0.34"
|
||||||
},
|
},
|
||||||
|
@ -5,13 +5,12 @@ type Config = {
|
|||||||
wsUrl: string;
|
wsUrl: string;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
keywordEnabled: boolean;
|
keywordEnabled: boolean;
|
||||||
reversiEnabled: boolean;
|
|
||||||
notingEnabled: boolean;
|
notingEnabled: boolean;
|
||||||
chartEnabled: boolean;
|
|
||||||
serverMonitoring: boolean;
|
serverMonitoring: boolean;
|
||||||
mecab?: string;
|
mecab?: string;
|
||||||
mecabDic?: string;
|
mecabDic?: string;
|
||||||
memoryDir?: string;
|
memoryDir?: string;
|
||||||
|
shellgeiUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = require('../config.json');
|
const config = require('../config.json');
|
||||||
|
164
src/friend.ts
164
src/friend.ts
@ -1,71 +1,71 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import { User } from "./misskey/user"
|
||||||
import 藍 from '@/ai';
|
import IModule from "../../NullcatChan-old/src/module"
|
||||||
import IModule from '@/module';
|
import NullcatChan from "../../NullcatChan-old/src/nullcat-chan"
|
||||||
import getDate from '@/utils/get-date';
|
import getDate from "../../NullcatChan-old/src/utils/get-date"
|
||||||
import { User } from '@/misskey/user';
|
import { genItem } from "../../NullcatChan-old/src/vocabulary"
|
||||||
import { genItem } from '@/vocabulary';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export type FriendDoc = {
|
export type FriendDoc = {
|
||||||
userId: string;
|
userId: string
|
||||||
user: User;
|
user: User
|
||||||
name?: string | null;
|
name?: string | null
|
||||||
love?: number;
|
love?: number
|
||||||
lastLoveIncrementedAt?: string;
|
lastLoveIncrementedAt?: string
|
||||||
todayLoveIncrements?: number;
|
todayLoveIncrements?: number
|
||||||
perModulesData?: any;
|
perModulesData?: any
|
||||||
married?: boolean;
|
married?: boolean
|
||||||
transferCode?: string;
|
transferCode?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class Friend {
|
export default class Friend {
|
||||||
private ai: 藍;
|
private nullcatChan: NullcatChan
|
||||||
|
|
||||||
public get userId() {
|
public get userId() {
|
||||||
return this.doc.userId;
|
return this.doc.userId
|
||||||
}
|
}
|
||||||
|
|
||||||
public get name() {
|
public get name() {
|
||||||
return this.doc.name;
|
return this.doc.name
|
||||||
}
|
}
|
||||||
|
|
||||||
public get love() {
|
public get love() {
|
||||||
return this.doc.love || 0;
|
return this.doc.love || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public get married() {
|
public get married() {
|
||||||
return this.doc.married;
|
return this.doc.married
|
||||||
}
|
}
|
||||||
|
|
||||||
public doc: FriendDoc;
|
public doc: FriendDoc
|
||||||
|
|
||||||
constructor(ai: 藍, opts: { user?: User, doc?: FriendDoc }) {
|
constructor(nullcatChan: NullcatChan, opts: { user?: User; doc?: FriendDoc }) {
|
||||||
this.ai = ai;
|
this.nullcatChan = nullcatChan
|
||||||
|
|
||||||
if (opts.user) {
|
if (opts.user) {
|
||||||
const exist = this.ai.friends.findOne({
|
const exist = this.nullcatChan.friends.findOne({
|
||||||
userId: opts.user.id
|
userId: opts.user.id,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (exist == null) {
|
if (exist == null) {
|
||||||
const inserted = this.ai.friends.insertOne({
|
const inserted = this.nullcatChan.friends.insertOne({
|
||||||
userId: opts.user.id,
|
userId: opts.user.id,
|
||||||
user: opts.user
|
user: opts.user,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (inserted == null) {
|
if (inserted == null) {
|
||||||
throw new Error('Failed to insert friend doc');
|
throw new Error("Failed to insert friend doc")
|
||||||
}
|
}
|
||||||
|
|
||||||
this.doc = inserted;
|
this.doc = inserted
|
||||||
} else {
|
} else {
|
||||||
this.doc = exist;
|
this.doc = exist
|
||||||
this.doc.user = { ...this.doc.user, ...opts.user };
|
this.doc.user = { ...this.doc.user, ...opts.user }
|
||||||
this.save();
|
this.save()
|
||||||
}
|
}
|
||||||
} else if (opts.doc) {
|
} else if (opts.doc) {
|
||||||
this.doc = opts.doc;
|
this.doc = opts.doc
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No friend info specified');
|
throw new Error("No friend info specified")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,117 +74,117 @@ export default class Friend {
|
|||||||
this.doc.user = {
|
this.doc.user = {
|
||||||
...this.doc.user,
|
...this.doc.user,
|
||||||
...user,
|
...user,
|
||||||
};
|
}
|
||||||
this.save();
|
this.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public getPerModulesData(module: IModule) {
|
public getPerModulesData(module: IModule) {
|
||||||
if (this.doc.perModulesData == null) {
|
if (this.doc.perModulesData == null) {
|
||||||
this.doc.perModulesData = {};
|
this.doc.perModulesData = {}
|
||||||
this.doc.perModulesData[module.name] = {};
|
this.doc.perModulesData[module.name] = {}
|
||||||
this.save();
|
this.save()
|
||||||
} else if (this.doc.perModulesData[module.name] == null) {
|
} else if (this.doc.perModulesData[module.name] == null) {
|
||||||
this.doc.perModulesData[module.name] = {};
|
this.doc.perModulesData[module.name] = {}
|
||||||
this.save();
|
this.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.doc.perModulesData[module.name];
|
return this.doc.perModulesData[module.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public setPerModulesData(module: IModule, data: any) {
|
public setPerModulesData(module: IModule, data: any) {
|
||||||
if (this.doc.perModulesData == null) {
|
if (this.doc.perModulesData == null) {
|
||||||
this.doc.perModulesData = {};
|
this.doc.perModulesData = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.doc.perModulesData[module.name] = data;
|
this.doc.perModulesData[module.name] = data
|
||||||
|
|
||||||
this.save();
|
this.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public incLove(amount = 1) {
|
public incLove(amount = 1) {
|
||||||
const today = getDate();
|
const today = getDate()
|
||||||
|
|
||||||
if (this.doc.lastLoveIncrementedAt != today) {
|
if (this.doc.lastLoveIncrementedAt != today) {
|
||||||
this.doc.todayLoveIncrements = 0;
|
this.doc.todayLoveIncrements = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1日に上げられる親愛度は最大3
|
// 1日に上げられる親愛度は最大3
|
||||||
if (this.doc.lastLoveIncrementedAt == today && (this.doc.todayLoveIncrements || 0) >= 3) return;
|
if (this.doc.lastLoveIncrementedAt == today && (this.doc.todayLoveIncrements || 0) >= 3) return
|
||||||
|
|
||||||
if (this.doc.love == null) this.doc.love = 0;
|
if (this.doc.love == null) this.doc.love = 0
|
||||||
this.doc.love += amount;
|
this.doc.love += amount
|
||||||
|
|
||||||
// 最大 100
|
// 最大 100
|
||||||
if (this.doc.love > 100) this.doc.love = 100;
|
if (this.doc.love > 100) this.doc.love = 100
|
||||||
|
|
||||||
this.doc.lastLoveIncrementedAt = today;
|
this.doc.lastLoveIncrementedAt = today
|
||||||
this.doc.todayLoveIncrements = (this.doc.todayLoveIncrements || 0) + amount;
|
this.doc.todayLoveIncrements = (this.doc.todayLoveIncrements || 0) + amount
|
||||||
this.save();
|
this.save()
|
||||||
|
|
||||||
this.ai.log(`💗 ${this.userId} +${amount}`);
|
this.nullcatChan.log(`💗 ${this.userId} +${amount}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public decLove(amount = 1) {
|
public decLove(amount = 1) {
|
||||||
// 親愛度MAXなら下げない
|
// 親愛度MAXなら下げない
|
||||||
if (this.doc.love === 100) return;
|
if (this.doc.love === 100) return
|
||||||
|
|
||||||
if (this.doc.love == null) this.doc.love = 0;
|
if (this.doc.love == null) this.doc.love = 0
|
||||||
this.doc.love -= amount;
|
this.doc.love -= amount
|
||||||
|
|
||||||
// 最低 -30
|
// 最低 -30
|
||||||
if (this.doc.love < -30) this.doc.love = -30;
|
if (this.doc.love < -30) this.doc.love = -30
|
||||||
|
|
||||||
// 親愛度マイナスなら名前を忘れる
|
// 親愛度マイナスなら名前を忘れる
|
||||||
if (this.doc.love < 0) {
|
if (this.doc.love < 0) {
|
||||||
this.doc.name = null;
|
this.doc.name = null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save();
|
this.save()
|
||||||
|
|
||||||
this.ai.log(`💢 ${this.userId} -${amount}`);
|
this.nullcatChan.log(`💢 ${this.userId} -${amount}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public updateName(name: string) {
|
public updateName(name: string) {
|
||||||
this.doc.name = name;
|
this.doc.name = name
|
||||||
this.save();
|
this.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public save() {
|
public save() {
|
||||||
this.ai.friends.update(this.doc);
|
this.nullcatChan.friends.update(this.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public generateTransferCode(): string {
|
public generateTransferCode(): string {
|
||||||
const code = genItem();
|
const code = genItem()
|
||||||
|
|
||||||
this.doc.transferCode = code;
|
this.doc.transferCode = code
|
||||||
this.save();
|
this.save()
|
||||||
|
|
||||||
return code;
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public transferMemory(code: string): boolean {
|
public transferMemory(code: string): boolean {
|
||||||
const src = this.ai.friends.findOne({
|
const src = this.nullcatChan.friends.findOne({
|
||||||
transferCode: code
|
transferCode: code,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (src == null) return false;
|
if (src == null) return false
|
||||||
|
|
||||||
this.doc.name = src.name;
|
this.doc.name = src.name
|
||||||
this.doc.love = src.love;
|
this.doc.love = src.love
|
||||||
this.doc.married = src.married;
|
this.doc.married = src.married
|
||||||
this.doc.perModulesData = src.perModulesData;
|
this.doc.perModulesData = src.perModulesData
|
||||||
this.save();
|
this.save()
|
||||||
|
|
||||||
// TODO: 合言葉を忘れる
|
// TODO: 合言葉を忘れる
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
149
src/index.ts
149
src/index.ts
@ -1,94 +1,109 @@
|
|||||||
// AiOS bootstrapper
|
// Nullcat chan! bootstrapper
|
||||||
|
|
||||||
import 'module-alias/register';
|
import * as chalk from "chalk"
|
||||||
|
import "module-alias/register"
|
||||||
|
import * as request from "request-promise-native"
|
||||||
|
import config from "./config"
|
||||||
|
import BirthdayModule from "../../NullcatChan-old/src/modules/birthday"
|
||||||
|
import CoreModule from "../../NullcatChan-old/src/modules/core"
|
||||||
|
import EmojiReactModule from "../../NullcatChan-old/src/modules/emoji-react"
|
||||||
|
import FeelingModule from "../../NullcatChan-old/src/modules/feeling"
|
||||||
|
import FollowModule from "../../NullcatChan-old/src/modules/follow"
|
||||||
|
import FortuneModule from "../../NullcatChan-old/src/modules/fortune"
|
||||||
|
import GitHubStatusModule from "../../NullcatChan-old/src/modules/github-status"
|
||||||
|
import CloudflareStatus from "../../NullcatChan-old/src/modules/cloudflare-status";
|
||||||
|
import GomamayoModule from "../../NullcatChan-old/src/modules/gomamayo"
|
||||||
|
import JihouModule from "../../NullcatChan-old/src/modules/jihou"
|
||||||
|
import KeywordModule from "../../NullcatChan-old/src/modules/keyword"
|
||||||
|
import KiatsuModule from "../../NullcatChan-old/src/modules/kiatsu"
|
||||||
|
import NotingModule from "../../NullcatChan-old/src/modules/noting"
|
||||||
|
import PingModule from "../../NullcatChan-old/src/modules/ping"
|
||||||
|
import ReminderModule from "../../NullcatChan-old/src/modules/reminder"
|
||||||
|
import RoguboModule from "../../NullcatChan-old/src/modules/rogubo"
|
||||||
|
import ServerModule from "../../NullcatChan-old/src/modules/server"
|
||||||
|
import SleepReportModule from "../../NullcatChan-old/src/modules/sleep-report"
|
||||||
|
import TalkModule from "../../NullcatChan-old/src/modules/talk"
|
||||||
|
import TimerModule from "../../NullcatChan-old/src/modules/timer"
|
||||||
|
import TraceMoeModule from "../../NullcatChan-old/src/modules/trace-moe"
|
||||||
|
import ValentineModule from "../../NullcatChan-old/src/modules/valentine"
|
||||||
|
import WhatModule from "../../NullcatChan-old/src/modules/what"
|
||||||
|
import YarukotoModule from "../../NullcatChan-old/src/modules/yarukoto"
|
||||||
|
import NullcatChan from "../../NullcatChan-old/src/nullcat-chan"
|
||||||
|
import _log from "../../NullcatChan-old/src/utils/log"
|
||||||
|
import ShellGeiModule from "../../NullcatChan-old/src/modules/shellgei"
|
||||||
|
import SversionModule from "../../NullcatChan-old/src/modules/Sversion"
|
||||||
|
import AyashiiModule from "../../NullcatChan-old/src/modules/ayashii"
|
||||||
|
|
||||||
import * as chalk from 'chalk';
|
const promiseRetry = require("promise-retry")
|
||||||
import * as request from 'request-promise-native';
|
|
||||||
const promiseRetry = require('promise-retry');
|
|
||||||
|
|
||||||
import 藍 from './ai';
|
const pkg = require("../../NullcatChan-old/package.json")
|
||||||
import config from './config';
|
|
||||||
import _log from './utils/log';
|
|
||||||
const pkg = require('../package.json');
|
|
||||||
|
|
||||||
import CoreModule from './modules/core';
|
console.log(" _ __ ____ __ ________ __ ")
|
||||||
import TalkModule from './modules/talk';
|
console.log(" / | / /_ __/ / /________ _/ /_/ ____/ /_ ____ _____ / / ")
|
||||||
import BirthdayModule from './modules/birthday';
|
console.log(" / |/ / / / / / / ___/ __ `/ __/ / / __ \\/ __ `/ __ \\/ / ")
|
||||||
import ReversiModule from './modules/reversi';
|
console.log(" / /| / /_/ / / / /__/ /_/ / /_/ /___/ / / / /_/ / / / /_/ ")
|
||||||
import PingModule from './modules/ping';
|
console.log("/_/ |_/\\__,_/_/_/\\___/\\__,_/\\__/\\____/_/ /_/\\__,_/_/ /_(_)\n")
|
||||||
import EmojiModule from './modules/emoji';
|
|
||||||
import EmojiReactModule from './modules/emoji-react';
|
|
||||||
import FortuneModule from './modules/fortune';
|
|
||||||
import GuessingGameModule from './modules/guessing-game';
|
|
||||||
import KazutoriModule from './modules/kazutori';
|
|
||||||
import KeywordModule from './modules/keyword';
|
|
||||||
import WelcomeModule from './modules/welcome';
|
|
||||||
import TimerModule from './modules/timer';
|
|
||||||
import DiceModule from './modules/dice';
|
|
||||||
import ServerModule from './modules/server';
|
|
||||||
import FollowModule from './modules/follow';
|
|
||||||
import ValentineModule from './modules/valentine';
|
|
||||||
import MazeModule from './modules/maze';
|
|
||||||
import ChartModule from './modules/chart';
|
|
||||||
import SleepReportModule from './modules/sleep-report';
|
|
||||||
import NotingModule from './modules/noting';
|
|
||||||
import PollModule from './modules/poll';
|
|
||||||
import ReminderModule from './modules/reminder';
|
|
||||||
|
|
||||||
console.log(' __ ____ _____ ___ ');
|
|
||||||
console.log(' /__\\ (_ _)( _ )/ __)');
|
|
||||||
console.log(' /(__)\\ _)(_ )(_)( \\__ \\');
|
|
||||||
console.log('(__)(__)(____)(_____)(___/\n');
|
|
||||||
|
|
||||||
function log(msg: string): void {
|
function log(msg: string): void {
|
||||||
_log(`[Boot]: ${msg}`);
|
_log(`[Boot]: ${msg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
log(chalk.bold(`Ai v${pkg._v}`));
|
log(chalk.bold(`Nullcat chan! v${pkg._v}`))
|
||||||
|
|
||||||
promiseRetry(retry => {
|
promiseRetry(
|
||||||
log(`Account fetching... ${chalk.gray(config.host)}`);
|
(retry) => {
|
||||||
|
log(`Account fetching... ${chalk.gray(config.host)}`)
|
||||||
|
|
||||||
// アカウントをフェッチ
|
// アカウントをフェッチ
|
||||||
return request.post(`${config.apiUrl}/i`, {
|
return request
|
||||||
|
.post(`${config.apiUrl}/i`, {
|
||||||
json: {
|
json: {
|
||||||
i: config.i
|
i: config.i,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(retry)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
retries: 3,
|
||||||
}
|
}
|
||||||
}).catch(retry);
|
)
|
||||||
}, {
|
.then((account) => {
|
||||||
retries: 3
|
const acct = `@${account.username}`
|
||||||
}).then(account => {
|
log(chalk.green(`Account fetched successfully: ${chalk.underline(acct)}`))
|
||||||
const acct = `@${account.username}`;
|
|
||||||
log(chalk.green(`Account fetched successfully: ${chalk.underline(acct)}`));
|
|
||||||
|
|
||||||
log('Starting AiOS...');
|
log("Starting Nullcat chan...")
|
||||||
|
|
||||||
// 藍起動
|
// ぬるきゃっとちゃん起動
|
||||||
new 藍(account, [
|
new NullcatChan(account, [
|
||||||
new CoreModule(),
|
new CoreModule(),
|
||||||
new EmojiModule(),
|
|
||||||
new EmojiReactModule(),
|
new EmojiReactModule(),
|
||||||
new FortuneModule(),
|
new FortuneModule(),
|
||||||
new GuessingGameModule(),
|
|
||||||
new KazutoriModule(),
|
|
||||||
new ReversiModule(),
|
|
||||||
new TimerModule(),
|
new TimerModule(),
|
||||||
new DiceModule(),
|
|
||||||
new TalkModule(),
|
new TalkModule(),
|
||||||
new PingModule(),
|
|
||||||
new WelcomeModule(),
|
|
||||||
new ServerModule(),
|
|
||||||
new FollowModule(),
|
new FollowModule(),
|
||||||
new BirthdayModule(),
|
new BirthdayModule(),
|
||||||
new ValentineModule(),
|
new ValentineModule(),
|
||||||
new KeywordModule(),
|
new KeywordModule(),
|
||||||
new MazeModule(),
|
|
||||||
new ChartModule(),
|
|
||||||
new SleepReportModule(),
|
new SleepReportModule(),
|
||||||
new NotingModule(),
|
new NotingModule(),
|
||||||
new PollModule(),
|
|
||||||
new ReminderModule(),
|
new ReminderModule(),
|
||||||
]);
|
new GomamayoModule(),
|
||||||
}).catch(e => {
|
new GitHubStatusModule(),
|
||||||
log(chalk.red('Failed to fetch the account'));
|
new CloudflareStatus(),
|
||||||
});
|
new YarukotoModule(),
|
||||||
|
new RoguboModule(),
|
||||||
|
new KiatsuModule(),
|
||||||
|
new JihouModule(),
|
||||||
|
new WhatModule(),
|
||||||
|
new FeelingModule(),
|
||||||
|
new TraceMoeModule(),
|
||||||
|
new ServerModule(),
|
||||||
|
new ShellGeiModule(),
|
||||||
|
new SversionModule(),
|
||||||
|
new AyashiiModule(),
|
||||||
|
new PingModule(),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
log(chalk.red("Failed to fetch the account"))
|
||||||
|
})
|
||||||
|
135
src/message.ts
135
src/message.ts
@ -1,113 +1,142 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import config from "@/config"
|
||||||
import * as chalk from 'chalk';
|
import Friend from "@/friend"
|
||||||
const delay = require('timeout-as-promise');
|
import { User } from "./misskey/user"
|
||||||
|
import NullcatChan from "../../NullcatChan-old/src/nullcat-chan"
|
||||||
|
import includes from "../../NullcatChan-old/src/utils/includes"
|
||||||
|
import or from "../../NullcatChan-old/src/utils/or"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import * as chalk from "chalk"
|
||||||
|
const delay = require("timeout-as-promise")
|
||||||
|
|
||||||
import 藍 from '@/ai';
|
interface MisskeyFile {
|
||||||
import Friend from '@/friend';
|
id: string
|
||||||
import { User } from '@/misskey/user';
|
createdAt: string
|
||||||
import includes from '@/utils/includes';
|
name: string
|
||||||
import or from '@/utils/or';
|
type: string
|
||||||
import config from '@/config';
|
md5: string
|
||||||
|
size: number
|
||||||
|
isSensitive: boolean
|
||||||
|
blurhash: string | null
|
||||||
|
properties: {
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
}
|
||||||
|
url: string
|
||||||
|
thumbnailUrl: string | null
|
||||||
|
comment?: unknown | null // FIXME
|
||||||
|
folderId: string | null
|
||||||
|
folder?: unknown | null // FIXME
|
||||||
|
userId: string | null
|
||||||
|
user: User | null
|
||||||
|
}
|
||||||
|
|
||||||
export default class Message {
|
export default class Message {
|
||||||
private ai: 藍;
|
private nullcatChan: NullcatChan
|
||||||
private messageOrNote: any;
|
private messageOrNote: any
|
||||||
public isDm: boolean;
|
public isDm: boolean
|
||||||
|
|
||||||
public get id(): string {
|
public get id(): string {
|
||||||
return this.messageOrNote.id;
|
return this.messageOrNote.id
|
||||||
}
|
}
|
||||||
|
|
||||||
public get user(): User {
|
public get user(): User {
|
||||||
return this.messageOrNote.user;
|
return this.messageOrNote.user
|
||||||
}
|
}
|
||||||
|
|
||||||
public get userId(): string {
|
public get userId(): string {
|
||||||
return this.messageOrNote.userId;
|
return this.messageOrNote.userId
|
||||||
}
|
}
|
||||||
|
|
||||||
public get text(): string {
|
public get text(): string {
|
||||||
return this.messageOrNote.text;
|
return this.messageOrNote.text
|
||||||
|
}
|
||||||
|
|
||||||
|
public get renotedText(): string | null {
|
||||||
|
return this.messageOrNote.renote.text
|
||||||
}
|
}
|
||||||
|
|
||||||
public get quoteId(): string | null {
|
public get quoteId(): string | null {
|
||||||
return this.messageOrNote.renoteId;
|
return this.messageOrNote.renoteId
|
||||||
}
|
}
|
||||||
|
|
||||||
public get visibility(): string {
|
public get files(): MisskeyFile[] | undefined {
|
||||||
return this.messageOrNote.visibility;
|
return this.messageOrNote.files
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* メンション部分を除いたテキスト本文
|
* メンション部分を除いたテキスト本文
|
||||||
*/
|
*/
|
||||||
public get extractedText(): string {
|
public get extractedText(): string {
|
||||||
const host = new URL(config.host).host.replace(/\./g, '\\.');
|
const host = new URL(config.host).host.replace(/\./g, "\\.")
|
||||||
return this.text
|
return this.text
|
||||||
.replace(new RegExp(`^@${this.ai.account.username}@${host}\\s`, 'i'), '')
|
.replace(new RegExp(`^@${this.nullcatChan.account.username}@${host}\\s`, "i"), "")
|
||||||
.replace(new RegExp(`^@${this.ai.account.username}\\s`, 'i'), '')
|
.replace(new RegExp(`^@${this.nullcatChan.account.username}\\s`, "i"), "")
|
||||||
.trim();
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
public get replyId(): string {
|
public get replyId(): string {
|
||||||
return this.messageOrNote.replyId;
|
return this.messageOrNote.replyId
|
||||||
}
|
}
|
||||||
|
|
||||||
public friend: Friend;
|
public friend: Friend
|
||||||
|
|
||||||
constructor(ai: 藍, messageOrNote: any, isDm: boolean) {
|
constructor(nullcatChan: NullcatChan, messageOrNote: any, isDm: boolean) {
|
||||||
this.ai = ai;
|
this.nullcatChan = nullcatChan
|
||||||
this.messageOrNote = messageOrNote;
|
this.messageOrNote = messageOrNote
|
||||||
this.isDm = isDm;
|
this.isDm = isDm
|
||||||
|
|
||||||
this.friend = new Friend(ai, { user: this.user });
|
this.friend = new Friend(nullcatChan, { user: this.user })
|
||||||
|
|
||||||
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
|
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
|
||||||
this.ai.api('users/show', {
|
this.nullcatChan
|
||||||
userId: this.userId
|
.api("users/show", {
|
||||||
}).then(user => {
|
userId: this.userId,
|
||||||
this.friend.updateUser(user);
|
})
|
||||||
});
|
.then((user) => {
|
||||||
|
this.friend.updateUser(user)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public async reply(text: string | null, opts?: {
|
public async reply(
|
||||||
file?: any;
|
text: string | null,
|
||||||
cw?: string;
|
opts?: {
|
||||||
renote?: string;
|
file?: any
|
||||||
immediate?: boolean;
|
cw?: string
|
||||||
}) {
|
renote?: string
|
||||||
if (text == null) return;
|
immediate?: boolean
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (text == null) return
|
||||||
|
|
||||||
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
|
this.nullcatChan.log(`>>> Sending reply to ${chalk.underline(this.id)}`)
|
||||||
|
|
||||||
if (!opts?.immediate) {
|
if (!opts?.immediate) {
|
||||||
await delay(2000);
|
await delay(2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isDm) {
|
if (this.isDm) {
|
||||||
return await this.ai.sendMessage(this.messageOrNote.userId, {
|
return await this.nullcatChan.sendMessage(this.messageOrNote.userId, {
|
||||||
text: text,
|
text: text,
|
||||||
fileId: opts?.file?.id
|
fileId: opts?.file?.id,
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return await this.ai.post({
|
return await this.nullcatChan.post({
|
||||||
replyId: this.messageOrNote.id,
|
replyId: this.messageOrNote.id,
|
||||||
text: text,
|
text: text,
|
||||||
fileIds: opts?.file ? [opts?.file.id] : undefined,
|
fileIds: opts?.file ? [opts?.file.id] : undefined,
|
||||||
cw: opts?.cw,
|
cw: opts?.cw,
|
||||||
renoteId: opts?.renote
|
renoteId: opts?.renote,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public includes(words: string[]): boolean {
|
public includes(words: string[]): boolean {
|
||||||
return includes(this.text, words);
|
return includes(this.text, words)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public or(words: (string | RegExp)[]): boolean {
|
public or(words: (string | RegExp)[]): boolean {
|
||||||
return or(this.text, words);
|
return or(this.text, words)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import NullcatChan, { InstallerResult } from "./nullcat-chan"
|
||||||
import 藍, { InstallerResult } from '@/ai';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default abstract class Module {
|
export default abstract class Module {
|
||||||
public abstract readonly name: string;
|
public abstract readonly name: string
|
||||||
|
|
||||||
protected ai: 藍;
|
protected nullcatChan: NullcatChan
|
||||||
private doc: any;
|
private doc: any
|
||||||
|
|
||||||
public init(ai: 藍) {
|
public init(nullcatChan: NullcatChan) {
|
||||||
this.ai = ai;
|
this.nullcatChan = nullcatChan
|
||||||
|
|
||||||
this.doc = this.ai.moduleData.findOne({
|
this.doc = this.nullcatChan.moduleData.findOne({
|
||||||
module: this.name
|
module: this.name,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (this.doc == null) {
|
if (this.doc == null) {
|
||||||
this.doc = this.ai.moduleData.insertOne({
|
this.doc = this.nullcatChan.moduleData.insertOne({
|
||||||
module: this.name,
|
module: this.name,
|
||||||
data: {}
|
data: {},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract install(): InstallerResult;
|
public abstract install(): InstallerResult
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
protected log(msg: string) {
|
protected log(msg: string) {
|
||||||
this.ai.log(`[${this.name}]: ${msg}`);
|
this.nullcatChan.log(`[${this.name}]: ${msg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +38,7 @@ export default abstract class Module {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
protected subscribeReply(key: string | null, isDm: boolean, id: string, data?: any) {
|
protected subscribeReply(key: string | null, isDm: boolean, id: string, data?: any) {
|
||||||
this.ai.subscribeReply(this, key, isDm, id, data);
|
this.nullcatChan.subscribeReply(this, key, isDm, id, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +47,7 @@ export default abstract class Module {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
protected unsubscribeReply(key: string | null) {
|
protected unsubscribeReply(key: string | null) {
|
||||||
this.ai.unsubscribeReply(this, key);
|
this.nullcatChan.unsubscribeReply(this, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,17 +58,17 @@ export default abstract class Module {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
public setTimeoutWithPersistence(delay: number, data?: any) {
|
public setTimeoutWithPersistence(delay: number, data?: any) {
|
||||||
this.ai.setTimeoutWithPersistence(this, delay, data);
|
this.nullcatChan.setTimeoutWithPersistence(this, delay, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
protected getData() {
|
protected getData() {
|
||||||
return this.doc.data;
|
return this.doc.data
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
protected setData(data: any) {
|
protected setData(data: any) {
|
||||||
this.doc.data = data;
|
this.doc.data = data
|
||||||
this.ai.moduleData.update(this.doc);
|
this.nullcatChan.moduleData.update(this.doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/modules/ayashii/index.ts
Normal file
28
src/modules/ayashii/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import { generate } from "cjp"
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "ayashii"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(message: Message) {
|
||||||
|
if (message.includes(["#怪しい日本語変換"])) {
|
||||||
|
const context = message.extractedText.replace("#怪しい日本語変換", "").trim()
|
||||||
|
const cjp = generate(context)
|
||||||
|
|
||||||
|
message.reply(cjp + " #怪レい曰本语")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Friend from "@/friend"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Friend from '@/friend';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
function zeroPadding(num: number, length: number): string {
|
function zeroPadding(num: number, length: number): string {
|
||||||
return ('0000000000' + num).slice(-length);
|
return ("0000000000" + num).slice(-length)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'birthday';
|
public readonly name = "birthday"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
this.crawleBirthday();
|
this.crawleBirthday()
|
||||||
setInterval(this.crawleBirthday, 1000 * 60 * 3);
|
setInterval(this.crawleBirthday, 1000 * 60 * 3)
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,34 +23,34 @@ export default class extends Module {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private crawleBirthday() {
|
private crawleBirthday() {
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
const m = now.getMonth();
|
const m = now.getMonth()
|
||||||
const d = now.getDate();
|
const d = now.getDate()
|
||||||
// Misskeyの誕生日は 2018-06-16 のような形式
|
// Misskeyの誕生日は 2018-06-16 のような形式
|
||||||
const today = `${zeroPadding(m + 1, 2)}-${zeroPadding(d, 2)}`;
|
const today = `${zeroPadding(m + 1, 2)}-${zeroPadding(d, 2)}`
|
||||||
|
|
||||||
const birthFriends = this.ai.friends.find({
|
const birthFriends = this.nullcatChan.friends.find({
|
||||||
'user.birthday': { '$regex': new RegExp('-' + today + '$') }
|
"user.birthday": { $regex: new RegExp("-" + today + "$") },
|
||||||
} as any);
|
} as any)
|
||||||
|
|
||||||
birthFriends.forEach(f => {
|
birthFriends.forEach((f) => {
|
||||||
const friend = new Friend(this.ai, { doc: f });
|
const friend = new Friend(this.nullcatChan, { doc: f })
|
||||||
|
|
||||||
// 親愛度が3以上必要
|
// 親愛度が3以上必要
|
||||||
if (friend.love < 3) return;
|
if (friend.love < 3) return
|
||||||
|
|
||||||
const data = friend.getPerModulesData(this);
|
const data = friend.getPerModulesData(this)
|
||||||
|
|
||||||
if (data.lastBirthdayChecked == today) return;
|
if (data.lastBirthdayChecked == today) return
|
||||||
|
|
||||||
data.lastBirthdayChecked = today;
|
data.lastBirthdayChecked = today
|
||||||
friend.setPerModulesData(this, data);
|
friend.setPerModulesData(this, data)
|
||||||
|
|
||||||
const text = serifs.birthday.happyBirthday(friend.name);
|
const text = serifs.birthday.happyBirthday(friend.name)
|
||||||
|
|
||||||
this.ai.sendMessage(friend.userId, {
|
this.nullcatChan.sendMessage(friend.userId, {
|
||||||
text: text
|
text: text,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,159 +1,153 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import { safeForInterpolate } from "../../../../NullcatChan-old/src/utils/safe-for-interpolate"
|
||||||
import { safeForInterpolate } from '@/utils/safe-for-interpolate';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
const titles = ['さん', 'くん', '君', 'ちゃん', '様', '先生'];
|
const titles = ["さん", "くん", "君", "ちゃん", "様", "先生"]
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'core';
|
public readonly name = "core"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook,
|
mentionHook: this.mentionHook,
|
||||||
contextHook: this.contextHook
|
contextHook: this.contextHook,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
|
|
||||||
return (
|
return this.transferBegin(msg) || this.transferEnd(msg) || this.setName(msg) || this.modules(msg) || this.version(msg)
|
||||||
this.transferBegin(msg) ||
|
|
||||||
this.transferEnd(msg) ||
|
|
||||||
this.setName(msg) ||
|
|
||||||
this.modules(msg) ||
|
|
||||||
this.version(msg)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private transferBegin(msg: Message): boolean {
|
private transferBegin(msg: Message): boolean {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
if (!msg.includes(['引継', '引き継ぎ', '引越', '引っ越し'])) return false;
|
if (!msg.includes(["引継", "引き継ぎ", "引越", "引っ越し"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) {
|
if (!msg.isDm) {
|
||||||
msg.reply(serifs.core.transferNeedDm);
|
msg.reply(serifs.core.transferNeedDm)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = msg.friend.generateTransferCode();
|
const code = msg.friend.generateTransferCode()
|
||||||
|
|
||||||
msg.reply(serifs.core.transferCode(code));
|
msg.reply(serifs.core.transferCode(code))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private transferEnd(msg: Message): boolean {
|
private transferEnd(msg: Message): boolean {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
if (!msg.text.startsWith('「') || !msg.text.endsWith('」')) return false;
|
if (!msg.text.startsWith("「") || !msg.text.endsWith("」")) return false
|
||||||
|
|
||||||
const code = msg.text.substring(1, msg.text.length - 1);
|
const code = msg.text.substring(1, msg.text.length - 1)
|
||||||
|
|
||||||
const succ = msg.friend.transferMemory(code);
|
const succ = msg.friend.transferMemory(code)
|
||||||
|
|
||||||
if (succ) {
|
if (succ) {
|
||||||
msg.reply(serifs.core.transferDone(msg.friend.name));
|
msg.reply(serifs.core.transferDone(msg.friend.name))
|
||||||
} else {
|
} else {
|
||||||
msg.reply(serifs.core.transferFailed);
|
msg.reply(serifs.core.transferFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private setName(msg: Message): boolean {
|
private setName(msg: Message): boolean {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
if (!msg.text.includes('って呼んで')) return false;
|
if (!msg.text.includes("って呼んで")) return false
|
||||||
if (msg.text.startsWith('って呼んで')) return false;
|
if (msg.text.startsWith("って呼んで")) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
const name = msg.text.match(/^(.+?)って呼んで/)![1];
|
const name = msg.text.match(/^(.+?)って呼んで/)![1]
|
||||||
|
|
||||||
if (name.length > 10) {
|
if (name.length > 10) {
|
||||||
msg.reply(serifs.core.tooLong);
|
msg.reply(serifs.core.tooLong)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!safeForInterpolate(name)) {
|
if (!safeForInterpolate(name)) {
|
||||||
msg.reply(serifs.core.invalidName);
|
msg.reply(serifs.core.invalidName)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const withSan = titles.some(t => name.endsWith(t));
|
const withSan = titles.some((t) => name.endsWith(t))
|
||||||
|
|
||||||
if (withSan) {
|
if (withSan) {
|
||||||
msg.friend.updateName(name);
|
msg.friend.updateName(name)
|
||||||
msg.reply(serifs.core.setNameOk(name));
|
msg.reply(serifs.core.setNameOk(name))
|
||||||
} else {
|
} else {
|
||||||
msg.reply(serifs.core.san).then(reply => {
|
msg.reply(serifs.core.san).then((reply) => {
|
||||||
this.subscribeReply(msg.userId, msg.isDm, msg.isDm ? msg.userId : reply.id, {
|
this.subscribeReply(msg.userId, msg.isDm, msg.isDm ? msg.userId : reply.id, {
|
||||||
name: name
|
name: name,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private modules(msg: Message): boolean {
|
private modules(msg: Message): boolean {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
if (!msg.or(['modules'])) return false;
|
if (!msg.or(["modules"])) return false
|
||||||
|
|
||||||
let text = '```\n';
|
let text = "```\n"
|
||||||
|
|
||||||
for (const m of this.ai.modules) {
|
for (const m of this.nullcatChan.modules) {
|
||||||
text += `${m.name}\n`;
|
text += `${m.name}\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
text += '```';
|
text += "```"
|
||||||
|
|
||||||
msg.reply(text, {
|
msg.reply(text, {
|
||||||
immediate: true
|
immediate: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private version(msg: Message): boolean {
|
private version(msg: Message): boolean {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
if (!msg.or(['v', 'version', 'バージョン'])) return false;
|
if (!msg.or(["v", "version", "バージョン"])) return false
|
||||||
|
|
||||||
msg.reply(`\`\`\`\nv${this.ai.version}\n\`\`\``, {
|
msg.reply(`\`\`\`\nv${this.nullcatChan.version}\n\`\`\``, {
|
||||||
immediate: true
|
immediate: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async contextHook(key: any, msg: Message, data: any) {
|
private async contextHook(key: any, msg: Message, data: any) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return
|
||||||
|
|
||||||
const done = () => {
|
const done = () => {
|
||||||
msg.reply(serifs.core.setNameOk(msg.friend.name));
|
msg.reply(serifs.core.setNameOk(msg.friend.name))
|
||||||
this.unsubscribeReply(key);
|
this.unsubscribeReply(key)
|
||||||
};
|
}
|
||||||
|
|
||||||
if (msg.text.includes('はい')) {
|
if (msg.text.includes("うん")) {
|
||||||
msg.friend.updateName(data.name + 'さん');
|
msg.friend.updateName(data.name + "ちゃん")
|
||||||
done();
|
done()
|
||||||
} else if (msg.text.includes('いいえ')) {
|
} else if (msg.text.includes("いいえ")) {
|
||||||
msg.friend.updateName(data.name);
|
msg.friend.updateName(data.name)
|
||||||
done();
|
done()
|
||||||
} else {
|
} else {
|
||||||
msg.reply(serifs.core.yesOrNo).then(reply => {
|
msg.reply(serifs.core.yesOrNo).then((reply) => {
|
||||||
this.subscribeReply(msg.userId, msg.isDm, reply.id, data);
|
this.subscribeReply(msg.userId, msg.isDm, reply.id, data)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,73 +1,69 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import { Note } from "../../misskey/note"
|
||||||
import { parse } from 'twemoji-parser';
|
import Module from "@/module"
|
||||||
const delay = require('timeout-as-promise');
|
import Stream from "@/stream"
|
||||||
|
import includes from "../../../../NullcatChan-old/src/utils/includes"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
const delay = require("timeout-as-promise")
|
||||||
|
|
||||||
import { Note } from '@/misskey/note';
|
const gomamayo = require("gomamayo-js")
|
||||||
import Module from '@/module';
|
|
||||||
import Stream from '@/stream';
|
|
||||||
import includes from '@/utils/includes';
|
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'emoji-react';
|
public readonly name = "emoji-react"
|
||||||
|
|
||||||
private htl: ReturnType<Stream['useSharedConnection']>;
|
private htl: ReturnType<Stream["useSharedConnection"]>
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
this.htl = this.ai.connection.useSharedConnection('homeTimeline');
|
this.htl = this.nullcatChan.connection.useSharedConnection("homeTimeline")
|
||||||
this.htl.on('note', this.onNote);
|
this.htl.on("note", this.onNote)
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async onNote(note: Note) {
|
private async onNote(note: Note) {
|
||||||
if (note.reply != null) return;
|
if (note.reply != null) return
|
||||||
if (note.text == null) return;
|
if (note.text == null) return
|
||||||
if (note.text.includes('@')) return; // (自分または他人問わず)メンションっぽかったらreject
|
if (note.text.includes("@")) return // (自分または他人問わず)メンションっぽかったらreject
|
||||||
|
|
||||||
const react = async (reaction: string, immediate = false) => {
|
const react = async (reaction: string, immediate = false) => {
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
await delay(1500);
|
await delay(1500)
|
||||||
}
|
}
|
||||||
this.ai.api('notes/reactions/create', {
|
this.nullcatChan.api("notes/reactions/create", {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
reaction: reaction
|
reaction: reaction,
|
||||||
});
|
})
|
||||||
};
|
|
||||||
|
|
||||||
const customEmojis = note.text.match(/:([^\n:]+?):/g);
|
|
||||||
if (customEmojis) {
|
|
||||||
// カスタム絵文字が複数種類ある場合はキャンセル
|
|
||||||
if (!customEmojis.every((val, i, arr) => val === arr[0])) return;
|
|
||||||
|
|
||||||
this.log(`Custom emoji detected - ${customEmojis[0]}`);
|
|
||||||
|
|
||||||
return react(customEmojis[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = parse(note.text).map(x => x.text);
|
if (await gomamayo.find(note.text)) return react(":bikkuribikkuri_:")
|
||||||
if (emojis.length > 0) {
|
if (includes(note.text, ["ぬるきゃっとちゃん", "ぬるきゃぼっと", "ぬるきゃっとぼっと"])) return react(":bibibi_nullcatchan:")
|
||||||
// 絵文字が複数種類ある場合はキャンセル
|
if (
|
||||||
if (!emojis.every((val, i, arr) => val === arr[0])) return;
|
includes(note.text, [
|
||||||
|
"ねむい",
|
||||||
this.log(`Emoji detected - ${emojis[0]}`);
|
"ねむたい",
|
||||||
|
"ねたい",
|
||||||
let reaction = emojis[0];
|
"ねれない",
|
||||||
|
"ねれん",
|
||||||
switch (reaction) {
|
"ねれぬ",
|
||||||
case '✊': return react('🖐', true);
|
"ふむ",
|
||||||
case '✌': return react('✊', true);
|
"つら",
|
||||||
case '🖐': case '✋': return react('✌', true);
|
"死に",
|
||||||
}
|
"つかれた",
|
||||||
|
"疲れた",
|
||||||
return react(reaction);
|
"しにたい",
|
||||||
}
|
"きえたい",
|
||||||
|
"消えたい",
|
||||||
if (includes(note.text, ['ぴざ'])) return react('🍕');
|
"やだ",
|
||||||
if (includes(note.text, ['ぷりん'])) return react('🍮');
|
"いやだ",
|
||||||
if (includes(note.text, ['寿司', 'sushi']) || note.text === 'すし') return react('🍣');
|
"なきそう",
|
||||||
|
"泣きそう",
|
||||||
if (includes(note.text, ['藍'])) return react('🙌');
|
"辛い",
|
||||||
|
])
|
||||||
|
)
|
||||||
|
return react(":nadenade_neko:")
|
||||||
|
if (includes(note.text, ["理解した", "りかいした", "わかった", "頑張った", "がんばった"])) return react(":erai:")
|
||||||
|
if (note.text.match(/う[~|ー]*んこ/) || note.text.match(/unko/)) return react(":anataima_unkotte_iimashitane:")
|
||||||
|
if (note.text.match(/う[~|ー]*ん$/) || note.text.match(/un$/)) return react(":ti_:")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/modules/feeling/index.ts
Normal file
31
src/modules/feeling/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import * as seedrandom from "seedrandom"
|
||||||
|
|
||||||
|
export const feelings = ["つらい", "ねむい", "るんるん", "虚無"]
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "feeling"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (msg.includes(["気分", "きぶん"])) {
|
||||||
|
const date = new Date()
|
||||||
|
const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}/${date.getHours()}/${msg.userId}`
|
||||||
|
const rng = seedrandom(seed)
|
||||||
|
const feeling = feelings[Math.floor(rng() * feelings.length)]
|
||||||
|
msg.reply(`**今は${feeling}かも**`)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,35 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'follow';
|
public readonly name = "follow"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook
|
mentionHook: this.mentionHook,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
if (msg.text && msg.includes(['フォロー', 'フォロバ', 'follow me'])) {
|
if (msg.text && msg.includes(["フォロー", "フォロバ", "follow me"])) {
|
||||||
if (!msg.user.isFollowing) {
|
if (!msg.user.isFollowing) {
|
||||||
this.ai.api('following/create', {
|
this.nullcatChan.api("following/create", {
|
||||||
userId: msg.userId,
|
userId: msg.userId,
|
||||||
});
|
})
|
||||||
|
msg.reply("これからよろしくね!", { immediate: true })
|
||||||
return {
|
return {
|
||||||
reaction: msg.friend.love >= 0 ? 'like' : null
|
reaction: msg.friend.love >= 0 ? ":love_nullcatchan:" : null,
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
reaction: msg.friend.love >= 0 ? 'hmm' : null
|
reaction: msg.friend.love >= 0 ? ":love_nullcatchan:" : null,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,36 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import { genItem } from "@/vocabulary"
|
||||||
import * as seedrandom from 'seedrandom';
|
import autobind from "autobind-decorator"
|
||||||
import { genItem } from '@/vocabulary';
|
import * as seedrandom from "seedrandom"
|
||||||
|
|
||||||
export const blessing = [
|
export const blessing = ["にゃん吉🐈", "みゃ~吉🐾", "ぬるきゃっと吉:love_nullcatchan:", "なんかすごい吉✨", "特大吉✨", "大大吉🎊", "大吉🎊", "吉🎉", "中吉🎉", "小吉🎉", "凶🗿", "大凶🗿"]
|
||||||
'藍吉',
|
|
||||||
'ヨタ吉',
|
|
||||||
'ゼタ吉',
|
|
||||||
'エクサ吉',
|
|
||||||
'ペタ吉',
|
|
||||||
'テラ吉',
|
|
||||||
'ギガ吉',
|
|
||||||
'メガ吉',
|
|
||||||
'キロ吉',
|
|
||||||
'ヘクト吉',
|
|
||||||
'デカ吉',
|
|
||||||
'デシ吉',
|
|
||||||
'センチ吉',
|
|
||||||
'ミリ吉',
|
|
||||||
'マイクロ吉',
|
|
||||||
'ナノ吉',
|
|
||||||
'ピコ吉',
|
|
||||||
'フェムト吉',
|
|
||||||
'アト吉',
|
|
||||||
'ゼプト吉',
|
|
||||||
'ヨクト吉',
|
|
||||||
'超吉',
|
|
||||||
'大大吉',
|
|
||||||
'大吉',
|
|
||||||
'吉',
|
|
||||||
'中吉',
|
|
||||||
'小吉',
|
|
||||||
'凶',
|
|
||||||
'大凶',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'fortune';
|
public readonly name = "fortune"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook
|
mentionHook: this.mentionHook,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
if (msg.includes(['占', 'うらな', '運勢', 'おみくじ'])) {
|
if (msg.includes(["占", "うらな", "運勢", "おみくじ"])) {
|
||||||
const date = new Date();
|
const date = new Date()
|
||||||
const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}@${msg.userId}`;
|
const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}@${msg.userId}`
|
||||||
const rng = seedrandom(seed);
|
const rng = seedrandom(seed)
|
||||||
const omikuji = blessing[Math.floor(rng() * blessing.length)];
|
const omikuji = blessing[Math.floor(rng() * blessing.length)]
|
||||||
const item = genItem(rng);
|
const item = genItem(rng)
|
||||||
msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, {
|
msg.reply(`**${omikuji}**\nラッキーアイテム: ${item}`, {
|
||||||
cw: serifs.fortune.cw(msg.friend.name)
|
cw: serifs.fortune.cw(msg.friend.name),
|
||||||
});
|
})
|
||||||
return true;
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
90
src/modules/github-status/index.ts
Normal file
90
src/modules/github-status/index.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import config from "@/config"
|
||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "github-status"
|
||||||
|
|
||||||
|
private readonly schema = z.object({
|
||||||
|
status: z.object({
|
||||||
|
description: z.string(),
|
||||||
|
indicator: z.enum(["none", "minor", "major", "critical", "maintenance"]),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
private indicatorString: Record<z.infer<typeof this.schema>["status"]["indicator"], string> = {
|
||||||
|
"none": "今はGitHubなんともないみたい!!",
|
||||||
|
"minor": "GitHubにちょっとしたエラーが起きてるかも",
|
||||||
|
"major": "GitHubにエラーが起きてるみたい",
|
||||||
|
"critical": "GitHubに重大なエラーが起きてるみたい",
|
||||||
|
"maintenance": "GitHubがメンテナンス中みたい",
|
||||||
|
}
|
||||||
|
|
||||||
|
private indicator: z.infer<typeof this.schema>["status"]["indicator"] = "none"
|
||||||
|
private description: z.infer<typeof this.schema>["status"]["description"] = ""
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
setInterval(this.updateStatus, 10 * 60 * 1000)
|
||||||
|
setInterval(this.postStatus, 60 * 60 * 1000)
|
||||||
|
|
||||||
|
this.updateStatus()
|
||||||
|
this.postStatus()
|
||||||
|
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async updateStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://www.githubstatus.com/api/v2/status.json")
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
const result = this.schema.safeParse(data)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.indicator = result.data.status.indicator
|
||||||
|
this.description = result.data.status.description
|
||||||
|
} else {
|
||||||
|
this.log("Validation failed.")
|
||||||
|
console.warn(result.error)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.log("Failed to fetch status from GitHub.")
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private postStatus() {
|
||||||
|
switch (this.indicator) {
|
||||||
|
case "minor":
|
||||||
|
case "major":
|
||||||
|
case "critical":
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: `${this.indicatorString[this.indicator]}\nせつめい: ${this.description}\nhttps://www.githubstatus.com/`,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.log("Report posted.")
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (msg.text?.toLowerCase().includes("github")) {
|
||||||
|
msg.reply(`${this.indicatorString[this.indicator]}\nせつめい: ${this.description}\nhttps://www.githubstatus.com`)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/modules/gomamayo/index.ts
Normal file
37
src/modules/gomamayo/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
const gomamayo = require("gomamayo-js")
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "gomamayo"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (msg.text && msg.text.includes("ゴママヨ")) {
|
||||||
|
const notetext = msg.renotedText != null ? msg.renotedText : msg.text
|
||||||
|
const gomamayoResult = await gomamayo.find(notetext.replace(/ゴママヨ/g, ""))
|
||||||
|
let resBodyText, resCwText
|
||||||
|
if (gomamayoResult) {
|
||||||
|
resCwText = "ゴママヨかもしれない"
|
||||||
|
resBodyText = JSON.stringify(gomamayoResult, undefined, 2)
|
||||||
|
} else {
|
||||||
|
resBodyText = "ゴママヨじゃないかも"
|
||||||
|
}
|
||||||
|
msg.reply(resBodyText, {
|
||||||
|
immediate: true,
|
||||||
|
cw: resCwText,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/modules/jihou/index.ts
Normal file
50
src/modules/jihou/index.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
|
const accurateInterval = require("accurate-interval")
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "jihou"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
accurateInterval(this.post, 1000 * 60 * 60, { aligned: true, immediate: true })
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async post() {
|
||||||
|
const date = new Date()
|
||||||
|
date.setMinutes(date.getMinutes() + 1)
|
||||||
|
|
||||||
|
const hour = date.getHours()
|
||||||
|
|
||||||
|
switch (hour) {
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: `${hour}時だよ!`,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: `みんなおはよ!${hour}時だよ!`,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: `${hour}時だよ!みんなそろそろ寝る時間かな?`,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: `${hour}時だよ!ログボリセットの時間だよ!!`,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +1,101 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import config from "@/config"
|
||||||
import * as loki from 'lokijs';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import config from '@/config';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import NGWord from "../../ng-words";
|
||||||
import { mecab } from './mecab';
|
import autobind from "autobind-decorator"
|
||||||
|
import * as loki from "lokijs"
|
||||||
|
import { mecab } from "./mecab"
|
||||||
|
|
||||||
function kanaToHira(str: string) {
|
function kanaToHira(str: string) {
|
||||||
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
return str.replace(/[\u30a1-\u30f6]/g, (match) => {
|
||||||
const chr = match.charCodeAt(0) - 0x60;
|
const chr = match.charCodeAt(0) - 0x60
|
||||||
return String.fromCharCode(chr);
|
return String.fromCharCode(chr)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'keyword';
|
public readonly name = "keyword"
|
||||||
|
|
||||||
private learnedKeywords: loki.Collection<{
|
private learnedKeywords: loki.Collection<{
|
||||||
keyword: string;
|
keyword: string
|
||||||
learnedAt: number;
|
learnedAt: number
|
||||||
}>;
|
}>
|
||||||
|
|
||||||
|
private ngWord = new NGWord()
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
if (!config.keywordEnabled) return {};
|
if (!config.keywordEnabled) return {}
|
||||||
|
|
||||||
this.learnedKeywords = this.ai.getCollection('_keyword_learnedKeywords', {
|
this.learnedKeywords = this.nullcatChan.getCollection("_keyword_learnedKeywords", {
|
||||||
indices: ['userId']
|
indices: ["userId"],
|
||||||
});
|
})
|
||||||
|
|
||||||
setInterval(this.learn, 1000 * 60 * 60);
|
setInterval(this.learn, 1000 * 60 * 45)
|
||||||
|
|
||||||
return {};
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async learn() {
|
private async learn() {
|
||||||
const tl = await this.ai.api('notes/local-timeline', {
|
const tl = await this.nullcatChan.api("notes/hybrid-timeline", {
|
||||||
limit: 30
|
limit: 30,
|
||||||
});
|
})
|
||||||
|
|
||||||
const interestedNotes = tl.filter(note =>
|
const interestedNotes = tl.filter((note) => note.userId !== this.nullcatChan.account.id && note.text != null && note.cw == null)
|
||||||
note.userId !== this.ai.account.id &&
|
|
||||||
note.text != null &&
|
|
||||||
note.cw == null);
|
|
||||||
|
|
||||||
let keywords: string[][] = [];
|
let keywords: string[][] = []
|
||||||
|
|
||||||
for (const note of interestedNotes) {
|
for (const note of interestedNotes) {
|
||||||
const tokens = await mecab(note.text, config.mecab, config.mecabDic);
|
const tokens = await mecab(note.text, config.mecab, config.mecabDic)
|
||||||
const keywordsInThisNote = tokens.filter(token => token[2] == '固有名詞' && token[8] != null);
|
const keywordsInThisNote = tokens.filter((token) => token[2] == "固有名詞" && token[8] != null)
|
||||||
keywords = keywords.concat(keywordsInThisNote);
|
keywords = keywords.concat(keywordsInThisNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywords.length === 0) return;
|
if (keywords.length === 0) return
|
||||||
|
|
||||||
const rnd = Math.floor((1 - Math.sqrt(Math.random())) * keywords.length);
|
const rnd = Math.floor((1 - Math.sqrt(Math.random())) * keywords.length)
|
||||||
const keyword = keywords.sort((a, b) => a[0].length < b[0].length ? 1 : -1)[rnd];
|
const keyword = keywords.sort((a, b) => (a[0].length < b[0].length ? 1 : -1))[rnd]
|
||||||
|
|
||||||
const exist = this.learnedKeywords.findOne({
|
const exist = this.learnedKeywords.findOne({
|
||||||
keyword: keyword[0]
|
keyword: keyword[0],
|
||||||
});
|
})
|
||||||
|
|
||||||
let text: string;
|
let text: string
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
return;
|
return
|
||||||
} else {
|
} else {
|
||||||
this.learnedKeywords.insertOne({
|
this.learnedKeywords.insertOne({
|
||||||
keyword: keyword[0],
|
keyword: keyword[0],
|
||||||
learnedAt: Date.now()
|
learnedAt: Date.now(),
|
||||||
});
|
})
|
||||||
|
|
||||||
text = serifs.keyword.learned(keyword[0], kanaToHira(keyword[8]));
|
const isNGWord = this.ngWord.get.some(word => keyword[0] === word)
|
||||||
|
if (isNGWord) return
|
||||||
|
|
||||||
|
text = serifs.keyword.learned(keyword[0], kanaToHira(keyword[8]))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
text: text
|
text: text,
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (msg.includes(["覚えて", "おぼえて"])) {
|
||||||
|
this.log("Keyword learn requested")
|
||||||
|
msg.reply("がんばってみるね")
|
||||||
|
this.learn()
|
||||||
|
return {
|
||||||
|
reaction: ":bikkuri_nullcatchan:",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from "child_process"
|
||||||
import * as util from 'util';
|
import * as memoryStreams from "memory-streams"
|
||||||
import * as stream from 'stream';
|
import { EOL } from "os"
|
||||||
import * as memoryStreams from 'memory-streams';
|
import * as stream from "stream"
|
||||||
import { EOL } from 'os';
|
import * as util from "util"
|
||||||
|
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run MeCab
|
* Run MeCab
|
||||||
@ -12,34 +12,34 @@ const pipeline = util.promisify(stream.pipeline);
|
|||||||
* @param mecab mecab bin
|
* @param mecab mecab bin
|
||||||
* @param dic mecab dictionaly path
|
* @param dic mecab dictionaly path
|
||||||
*/
|
*/
|
||||||
export async function mecab(text: string, mecab = 'mecab', dic?: string): Promise<string[][]> {
|
export async function mecab(text: string, mecab = "mecab", dic?: string): Promise<string[][]> {
|
||||||
const args: string[] = [];
|
const args: string[] = []
|
||||||
if (dic) args.push('-d', dic);
|
if (dic) args.push("-d", dic)
|
||||||
|
|
||||||
const lines = await cmd(mecab, args, `${text.replace(/[\n\s\t]/g, ' ')}\n`);
|
const lines = await cmd(mecab, args, `${text.replace(/[\n\s\t]/g, " ")}\n`)
|
||||||
|
|
||||||
const results: string[][] = [];
|
const results: string[][] = []
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line === 'EOS') break;
|
if (line === "EOS") break
|
||||||
const [word, value = ''] = line.split('\t');
|
const [word, value = ""] = line.split("\t")
|
||||||
const array = value.split(',');
|
const array = value.split(",")
|
||||||
array.unshift(word);
|
array.unshift(word)
|
||||||
results.push(array);
|
results.push(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cmd(command: string, args: string[], stdin: string): Promise<string[]> {
|
export async function cmd(command: string, args: string[], stdin: string): Promise<string[]> {
|
||||||
const mecab = spawn(command, args);
|
const mecab = spawn(command, args)
|
||||||
|
|
||||||
const writable = new memoryStreams.WritableStream();
|
const writable = new memoryStreams.WritableStream()
|
||||||
|
|
||||||
mecab.stdin.write(stdin);
|
mecab.stdin.write(stdin)
|
||||||
mecab.stdin.end();
|
mecab.stdin.end()
|
||||||
|
|
||||||
await pipeline(mecab.stdout, writable);
|
await pipeline(mecab.stdout, writable)
|
||||||
|
|
||||||
return writable.toString().split(EOL);
|
return writable.toString().split(EOL)
|
||||||
}
|
}
|
||||||
|
96
src/modules/kiatsu/index.ts
Normal file
96
src/modules/kiatsu/index.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "kiatsu"
|
||||||
|
|
||||||
|
private readonly itemSchema = z.object({
|
||||||
|
time: z.enum(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]),
|
||||||
|
weather: z.string(),
|
||||||
|
temp: z.string(),
|
||||||
|
pressure: z.string(),
|
||||||
|
pressure_level: z.enum(["0", "1", "2", "3", "4"]),
|
||||||
|
})
|
||||||
|
|
||||||
|
private readonly schema = z.object({
|
||||||
|
place_name: z.literal("東京都中央区"),
|
||||||
|
place_id: z.literal("102"),
|
||||||
|
prefectures_id: z.literal("13"),
|
||||||
|
dateTime: z.string(),
|
||||||
|
yesterday: z.array(this.itemSchema).optional(),
|
||||||
|
today: z.array(this.itemSchema),
|
||||||
|
tomorrow: z.array(this.itemSchema).optional(),
|
||||||
|
dayaftertomorrow: z.array(this.itemSchema).optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
private currentPressure: z.infer<typeof this.itemSchema>["pressure"] = ""
|
||||||
|
|
||||||
|
private currentPressureLevel: z.infer<typeof this.itemSchema>["pressure_level"] = "0"
|
||||||
|
|
||||||
|
private readonly stringPressureLevel: { [K in typeof this.currentPressureLevel]: (hPa: string) => string } = {
|
||||||
|
0: (hPa) => `${hPa}hPaだから問題ないかも。無理しないでね。`,
|
||||||
|
1: (hPa) => `${hPa}hPaだから問題ないかも。無理しないでね。`,
|
||||||
|
2: (hPa) => `気圧${hPa}hPaでちょっとやばいかも。無理しないでね。`,
|
||||||
|
3: (hPa) => `気圧${hPa}hPaでやばいかも。無理しないでね。`,
|
||||||
|
4: (hPa) => `気圧${hPa}hPaでかなりやばいかも。無理しないでね。`,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
setInterval(this.update, 10 * 60 * 1000)
|
||||||
|
setInterval(this.post, 12 * 60 * 60 * 1000)
|
||||||
|
|
||||||
|
this.update()
|
||||||
|
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async update() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://zutool.jp/api/getweatherstatus/13102")
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
const result = this.schema.safeParse(data)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
this.log("Validation failed.")
|
||||||
|
console.warn(result.error)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date()
|
||||||
|
const hour = this.itemSchema.shape.time.parse(date.getHours().toString())
|
||||||
|
|
||||||
|
this.currentPressureLevel = result.data.today[hour].pressure_level
|
||||||
|
this.currentPressure = result.data.today[hour].pressure
|
||||||
|
} catch (error) {
|
||||||
|
this.log("Failed to fetch status.")
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private post() {
|
||||||
|
if (this.currentPressureLevel === "0" || this.currentPressureLevel === "1") return
|
||||||
|
|
||||||
|
this.nullcatChan.post({
|
||||||
|
text: this.stringPressureLevel[this.currentPressureLevel](this.currentPressure),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(message: Message) {
|
||||||
|
if (!message.includes(["気圧", "きあつ"])) return false
|
||||||
|
|
||||||
|
message.reply(this.stringPressureLevel[this.currentPressureLevel](this.currentPressure), { immediate: true })
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +1,34 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import config from "@/config"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import serifs from '@/serifs';
|
import serifs from "@/serifs"
|
||||||
import { genItem } from '@/vocabulary';
|
import autobind from "autobind-decorator"
|
||||||
import config from '@/config';
|
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'noting';
|
public readonly name = "noting"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
if (config.notingEnabled === false) return {};
|
if (config.notingEnabled === false) return {}
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (Math.random() < 0.04) {
|
if (Math.random() < 0.1) {
|
||||||
this.post();
|
this.post()
|
||||||
}
|
}
|
||||||
}, 1000 * 60 * 10);
|
}, 1000 * 60 * 10)
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private post() {
|
private post() {
|
||||||
const notes = [
|
const notes = serifs.noting.notes
|
||||||
...serifs.noting.notes,
|
|
||||||
() => {
|
|
||||||
const item = genItem();
|
|
||||||
return serifs.noting.want(item);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
const item = genItem();
|
|
||||||
return serifs.noting.see(item);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
const item = genItem();
|
|
||||||
return serifs.noting.expire(item);
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const note = notes[Math.floor(Math.random() * notes.length)];
|
const note = notes[Math.floor(Math.random() * notes.length)]
|
||||||
|
|
||||||
// TODO: 季節に応じたセリフ
|
// TODO: 季節に応じたセリフ
|
||||||
|
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
text: typeof note === 'function' ? note() : note
|
text: note,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'ping';
|
public readonly name = "ping"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook
|
mentionHook: this.mentionHook,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
if (msg.text && msg.text.includes('ping')) {
|
if (msg.text && msg.text.includes("ping")) {
|
||||||
msg.reply('PONG!', {
|
msg.reply("$[x2 :bibibi_nullcatchan:]", {
|
||||||
immediate: true
|
immediate: true,
|
||||||
});
|
})
|
||||||
return true;
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,178 +1,172 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import config from "@/config"
|
||||||
import * as loki from 'lokijs';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import serifs, { getSerif } from "@/serifs"
|
||||||
import serifs, { getSerif } from '@/serifs';
|
import { acct } from "../../../../NullcatChan-old/src/utils/acct"
|
||||||
import { acct } from '@/utils/acct';
|
import autobind from "autobind-decorator"
|
||||||
import config from '@/config';
|
import * as loki from "lokijs"
|
||||||
|
|
||||||
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
|
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 1
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'reminder';
|
public readonly name = "reminder"
|
||||||
|
|
||||||
private reminds: loki.Collection<{
|
private reminds: loki.Collection<{
|
||||||
userId: string;
|
userId: string
|
||||||
id: string;
|
id: string
|
||||||
isDm: boolean;
|
isDm: boolean
|
||||||
thing: string | null;
|
thing: string | null
|
||||||
quoteId: string | null;
|
quoteId: string | null
|
||||||
times: number; // 催促した回数(使うのか?)
|
times: number // 催促した回数(使うのか?)
|
||||||
createdAt: number;
|
createdAt: number
|
||||||
}>;
|
}>
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
this.reminds = this.ai.getCollection('reminds', {
|
this.reminds = this.nullcatChan.getCollection("reminds", {
|
||||||
indices: ['userId', 'id']
|
indices: ["userId", "id"],
|
||||||
});
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook,
|
mentionHook: this.mentionHook,
|
||||||
contextHook: this.contextHook,
|
contextHook: this.contextHook,
|
||||||
timeoutCallback: this.timeoutCallback,
|
timeoutCallback: this.timeoutCallback,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
let text = msg.extractedText.toLowerCase();
|
let text = msg.extractedText.toLowerCase()
|
||||||
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
|
if (!text.startsWith("リマインド") && !text.startsWith("todo") && !text.startsWith("これやる")) return false
|
||||||
|
|
||||||
if (text.startsWith('reminds') || text.startsWith('todos')) {
|
if (text.startsWith("リスト") || text.startsWith("todos")) {
|
||||||
const reminds = this.reminds.find({
|
const reminds = this.reminds.find({
|
||||||
userId: msg.userId,
|
userId: msg.userId,
|
||||||
});
|
})
|
||||||
|
|
||||||
const getQuoteLink = id => `[${id}](${config.host}/notes/${id})`;
|
const getQuoteLink = (id) => `[${id}](${config.host}/notes/${id})`
|
||||||
|
if(reminds.length === 0) {
|
||||||
msg.reply(serifs.reminder.reminds + '\n' + reminds.map(remind => `・${remind.thing ? remind.thing : getQuoteLink(remind.quoteId)}`).join('\n'));
|
msg.reply(serifs.reminder.none)
|
||||||
return true;
|
}
|
||||||
|
else {
|
||||||
|
msg.reply(serifs.reminder.reminds + "\n" + reminds.map((remind) => `・${remind.thing ? remind.thing : getQuoteLink(remind.quoteId)}`).join("\n"))
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.match(/^(.+?)\s(.+)/)) {
|
if (text.match(/^(.+?)\s(.+)/)) {
|
||||||
text = text.replace(/^(.+?)\s/, '');
|
text = text.replace(/^(.+?)\s/, "")
|
||||||
} else {
|
} else {
|
||||||
text = '';
|
text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const separatorIndex = text.indexOf(' ') > -1 ? text.indexOf(' ') : text.indexOf('\n');
|
const separatorIndex = text.indexOf(" ") > -1 ? text.indexOf(" ") : text.indexOf("\n")
|
||||||
const thing = text.substr(separatorIndex + 1).trim();
|
const thing = text.substr(separatorIndex + 1).trim()
|
||||||
|
|
||||||
if (thing === '' && msg.quoteId == null || msg.visibility === 'followers') {
|
if (thing === "" && msg.quoteId == null) {
|
||||||
msg.reply(serifs.reminder.invalid);
|
msg.reply(serifs.reminder.invalid)
|
||||||
return {
|
return true
|
||||||
reaction: '🆖',
|
|
||||||
immediate: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const remind = this.reminds.insertOne({
|
const remind = this.reminds.insertOne({
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
userId: msg.userId,
|
userId: msg.userId,
|
||||||
isDm: msg.isDm,
|
isDm: msg.isDm,
|
||||||
thing: thing === '' ? null : thing,
|
thing: thing === "" ? null : thing,
|
||||||
quoteId: msg.quoteId,
|
quoteId: msg.quoteId,
|
||||||
times: 0,
|
times: 0,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
});
|
})
|
||||||
|
|
||||||
// メンションをsubscribe
|
// メンションをsubscribe
|
||||||
this.subscribeReply(remind!.id, msg.isDm, msg.isDm ? msg.userId : msg.id, {
|
this.subscribeReply(remind!.id, msg.isDm, msg.isDm ? msg.userId : msg.id, {
|
||||||
id: remind!.id
|
id: remind!.id,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (msg.quoteId) {
|
if (msg.quoteId) {
|
||||||
// 引用元をsubscribe
|
// 引用元をsubscribe
|
||||||
this.subscribeReply(remind!.id, false, msg.quoteId, {
|
this.subscribeReply(remind!.id, false, msg.quoteId, {
|
||||||
id: remind!.id
|
id: remind!.id,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// タイマーセット
|
// タイマーセット
|
||||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||||
id: remind!.id,
|
id: remind!.id,
|
||||||
});
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reaction: '🆗',
|
reaction: ":ok:",
|
||||||
immediate: true,
|
immediate: true,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async contextHook(key: any, msg: Message, data: any) {
|
private async contextHook(key: any, msg: Message, data: any) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return
|
||||||
|
|
||||||
const remind = this.reminds.findOne({
|
const remind = this.reminds.findOne({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (remind == null) {
|
if (remind == null) {
|
||||||
this.unsubscribeReply(key);
|
this.unsubscribeReply(key)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = msg.includes(['done', 'やった', 'やりました', 'はい']);
|
const done = msg.includes(["done", "やった", "やりました", "はい", "どね", "ドネ"])
|
||||||
const cancel = msg.includes(['やめる', 'やめた', 'キャンセル']);
|
const cancel = msg.includes(["やめる", "やめた", "キャンセル"])
|
||||||
const isOneself = msg.userId === remind.userId;
|
|
||||||
|
|
||||||
if ((done || cancel) && isOneself) {
|
if (done || cancel) {
|
||||||
this.unsubscribeReply(key);
|
this.unsubscribeReply(key)
|
||||||
this.reminds.remove(remind);
|
this.reminds.remove(remind)
|
||||||
msg.reply(done ? getSerif(serifs.reminder.done(msg.friend.name)) : serifs.reminder.cancel);
|
msg.reply(done ? getSerif(serifs.reminder.done(msg.friend.name)) : serifs.reminder.cancel)
|
||||||
return;
|
return
|
||||||
} else if (isOneself === false) {
|
|
||||||
msg.reply(serifs.reminder.doneFromInvalidUser);
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
if (msg.isDm) this.unsubscribeReply(key);
|
if (msg.isDm) this.unsubscribeReply(key)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async timeoutCallback(data) {
|
private async timeoutCallback(data) {
|
||||||
const remind = this.reminds.findOne({
|
const remind = this.reminds.findOne({
|
||||||
id: data.id
|
id: data.id,
|
||||||
});
|
})
|
||||||
if (remind == null) return;
|
if (remind == null) return
|
||||||
|
|
||||||
remind.times++;
|
remind.times++
|
||||||
this.reminds.update(remind);
|
this.reminds.update(remind)
|
||||||
|
|
||||||
const friend = this.ai.lookupFriend(remind.userId);
|
const friend = this.nullcatChan.lookupFriend(remind.userId)
|
||||||
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
if (friend == null) return // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||||
|
|
||||||
let reply;
|
let reply
|
||||||
if (remind.isDm) {
|
if (remind.isDm) {
|
||||||
this.ai.sendMessage(friend.userId, {
|
this.nullcatChan.sendMessage(friend.userId, {
|
||||||
text: serifs.reminder.notifyWithThing(remind.thing, friend.name)
|
text: serifs.reminder.notifyWithThing(remind.thing, friend.name),
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
reply = await this.ai.post({
|
reply = await this.nullcatChan.post({
|
||||||
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
|
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
|
||||||
text: acct(friend.doc.user) + ' ' + serifs.reminder.notify(friend.name)
|
text: acct(friend.doc.user) + " " + serifs.reminder.notify(friend.name),
|
||||||
});
|
visibility: "specified",
|
||||||
|
visibleUserIds: [remind.userId],
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// renote対象が消されていたらリマインダー解除
|
// TODO: renote対象が消されていたらリマインダー解除
|
||||||
if (err.statusCode === 400) {
|
return
|
||||||
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);
|
|
||||||
this.reminds.remove(remind);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subscribeReply(remind.id, remind.isDm, remind.isDm ? remind.userId : reply.id, {
|
this.subscribeReply(remind.id, remind.isDm, remind.isDm ? remind.userId : reply.id, {
|
||||||
id: remind.id
|
id: remind.id,
|
||||||
});
|
})
|
||||||
|
|
||||||
// タイマーセット
|
// タイマーセット
|
||||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||||
id: remind.id,
|
id: remind.id,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,79 +1,79 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import config from "@/config"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import serifs from '@/serifs';
|
import serifs from "@/serifs"
|
||||||
import config from '@/config';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'server';
|
public readonly name = "server"
|
||||||
|
|
||||||
private connection?: any;
|
private connection?: any
|
||||||
private recentStat: any;
|
private recentStat: any
|
||||||
private warned = false;
|
private warned = false
|
||||||
private lastWarnedAt: number;
|
private lastWarnedAt: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1秒毎のログ1分間分
|
* 1秒毎のログ1分間分
|
||||||
*/
|
*/
|
||||||
private statsLogs: any[] = [];
|
private statsLogs: any[] = []
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
if (!config.serverMonitoring) return {};
|
if (!config.serverMonitoring) return {}
|
||||||
|
|
||||||
this.connection = this.ai.connection.useSharedConnection('serverStats');
|
this.connection = this.nullcatChan.connection.useSharedConnection("serverStats")
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on("stats", this.onStats)
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.statsLogs.unshift(this.recentStat);
|
this.statsLogs.unshift(this.recentStat)
|
||||||
if (this.statsLogs.length > 60) this.statsLogs.pop();
|
if (this.statsLogs.length > 60) this.statsLogs.pop()
|
||||||
}, 1000);
|
}, 1000)
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.check();
|
this.check()
|
||||||
}, 3000);
|
}, 3000)
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private check() {
|
private check() {
|
||||||
const average = (arr) => arr.reduce((a, b) => a + b) / arr.length;
|
const average = (arr) => arr.reduce((a, b) => a + b) / arr.length
|
||||||
|
|
||||||
const cpuPercentages = this.statsLogs.map(s => s && (s.cpu_usage || s.cpu) * 100 || 0);
|
const cpuPercentages = this.statsLogs.map((s) => (s && (s.cpu_usage || s.cpu) * 100) || 0)
|
||||||
const cpuPercentage = average(cpuPercentages);
|
const cpuPercentage = average(cpuPercentages)
|
||||||
if (cpuPercentage >= 70) {
|
if (cpuPercentage >= 70) {
|
||||||
this.warn();
|
this.warn()
|
||||||
} else if (cpuPercentage <= 30) {
|
} else if (cpuPercentage <= 30) {
|
||||||
this.warned = false;
|
this.warned = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async onStats(stats: any) {
|
private async onStats(stats: any) {
|
||||||
this.recentStat = stats;
|
this.recentStat = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private warn() {
|
private warn() {
|
||||||
//#region 前に警告したときから一旦落ち着いた状態を経験していなければ警告しない
|
//#region 前に警告したときから一旦落ち着いた状態を経験していなければ警告しない
|
||||||
// 常に負荷が高いようなサーバーで無限に警告し続けるのを防ぐため
|
// 常に負荷が高いようなサーバーで無限に警告し続けるのを防ぐため
|
||||||
if (this.warned) return;
|
if (this.warned) return
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 前の警告から1時間経っていない場合は警告しない
|
//#region 前の警告から1時間経っていない場合は警告しない
|
||||||
const now = Date.now();
|
const now = Date.now()
|
||||||
|
|
||||||
if (this.lastWarnedAt != null) {
|
if (this.lastWarnedAt != null) {
|
||||||
if (now - this.lastWarnedAt < (1000 * 60 * 60)) return;
|
if (now - this.lastWarnedAt < 1000 * 60 * 60) return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastWarnedAt = now;
|
this.lastWarnedAt = now
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
text: serifs.server.cpu
|
text: serifs.server.cpu,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.warned = true;
|
this.warned = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
src/modules/shellgei/index.ts
Normal file
74
src/modules/shellgei/index.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Module from '@/module';
|
||||||
|
import Message from '@/message';
|
||||||
|
import config from "@/config";
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = 'shellgei';
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (!msg.text) return false;
|
||||||
|
if (msg.text && msg.text.includes('#シェル芸') || msg.text.includes('#shellgei')) {
|
||||||
|
|
||||||
|
const myInfoBody = { i: config.i };
|
||||||
|
const myInfoOptions = { method: 'POST', body: JSON.stringify(myInfoBody), headers: { 'Content-Type': 'application/json' } };
|
||||||
|
const myInfo = await fetch(`${config.apiUrl}/i`, myInfoOptions);
|
||||||
|
const myInfoJson: any = await myInfo.json();
|
||||||
|
const myId = myInfoJson.username;
|
||||||
|
|
||||||
|
const acct = `@${myId}`;
|
||||||
|
const hostname = config.host.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
||||||
|
const hostnameat = `@${hostname}`;
|
||||||
|
|
||||||
|
const shellText = msg.text.replace('#シェル芸', '').replace('#shellgei', '').replace(acct, '').replace(hostnameat, '');
|
||||||
|
this.log(shellText);
|
||||||
|
const shellgeiBody = { code: shellText , images: [] };
|
||||||
|
const shellgeiOptions = { method: 'POST', body: JSON.stringify(shellgeiBody), headers: { 'Content-Type': 'application/json' } };
|
||||||
|
const shellgeiURL = config.shellgeiUrl;
|
||||||
|
|
||||||
|
await (async () => {
|
||||||
|
try {
|
||||||
|
const shellgeiResult = await fetch(shellgeiURL, shellgeiOptions);
|
||||||
|
const shellgeiResultJson: any = await shellgeiResult.json();
|
||||||
|
const shellgeiResultStdOut = shellgeiResultJson.stdout;
|
||||||
|
const shellgeiResultStdErr = shellgeiResultJson.stderr;
|
||||||
|
if (shellgeiResultStdOut === "" && shellgeiResultStdErr === ""){
|
||||||
|
msg.reply(`結果がなかったよ:cry_nullcatchan:`, {
|
||||||
|
immediate: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
msg.reply(shellgeiResultStdOut + shellgeiResultStdErr, {
|
||||||
|
immediate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
msg.reply(`エラーが発生しちゃったよ:cry_nullcatchan:\n${e}`, {
|
||||||
|
immediate: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,35 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Module from "@/module"
|
||||||
import Module from '@/module';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'sleepReport';
|
public readonly name = "sleepReport"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
this.report();
|
this.report()
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private report() {
|
private report() {
|
||||||
const now = Date.now();
|
const now = Date.now()
|
||||||
|
|
||||||
const sleepTime = now - this.ai.lastSleepedAt;
|
const sleepTime = now - this.nullcatChan.lastSleepedAt
|
||||||
|
|
||||||
const sleepHours = sleepTime / 1000 / 60 / 60;
|
const sleepHours = sleepTime / 1000 / 60 / 60
|
||||||
|
|
||||||
if (sleepHours < 0.1) return;
|
if (sleepHours < 0.1) return
|
||||||
|
|
||||||
if (sleepHours >= 1) {
|
if (sleepHours >= 1) {
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
text: serifs.sleepReport.report(Math.round(sleepHours))
|
text: serifs.sleepReport.report(Math.round(sleepHours)),
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
text: serifs.sleepReport.reportUtatane
|
text: serifs.sleepReport.reportUtatane,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import { HandlerResult } from '@/ai';
|
import Module from "@/module"
|
||||||
import Module from '@/module';
|
import { HandlerResult } from "../../nullcat-chan"
|
||||||
import Message from '@/message';
|
import serifs, { getSerif } from "@/serifs"
|
||||||
import serifs, { getSerif } from '@/serifs';
|
import getDate from "../../../../NullcatChan-old/src/utils/get-date"
|
||||||
import getDate from '@/utils/get-date';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'talk';
|
public readonly name = "talk"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook,
|
mentionHook: this.mentionHook,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
if (!msg.text) return false;
|
if (!msg.text) return false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.greet(msg) ||
|
this.greet(msg) ||
|
||||||
@ -30,304 +30,314 @@ export default class extends Module {
|
|||||||
this.humu(msg) ||
|
this.humu(msg) ||
|
||||||
this.batou(msg) ||
|
this.batou(msg) ||
|
||||||
this.itai(msg) ||
|
this.itai(msg) ||
|
||||||
|
this.turai(msg) ||
|
||||||
|
this.kurusii(msg) ||
|
||||||
this.ote(msg) ||
|
this.ote(msg) ||
|
||||||
this.ponkotu(msg) ||
|
this.ponkotu(msg) ||
|
||||||
this.rmrf(msg) ||
|
this.rmrf(msg) ||
|
||||||
this.shutdown(msg)
|
this.shutdown(msg)
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private greet(msg: Message): boolean {
|
private greet(msg: Message): boolean {
|
||||||
if (msg.text == null) return false;
|
if (msg.text == null) return false
|
||||||
|
|
||||||
const incLove = () => {
|
const incLove = () => {
|
||||||
//#region 1日に1回だけ親愛度を上げる
|
//#region 1日に1回だけ親愛度を上げる
|
||||||
const today = getDate();
|
const today = getDate()
|
||||||
|
|
||||||
const data = msg.friend.getPerModulesData(this);
|
const data = msg.friend.getPerModulesData(this)
|
||||||
|
|
||||||
if (data.lastGreetedAt == today) return;
|
if (data.lastGreetedAt == today) return
|
||||||
|
|
||||||
data.lastGreetedAt = today;
|
data.lastGreetedAt = today
|
||||||
msg.friend.setPerModulesData(this, data);
|
msg.friend.setPerModulesData(this, data)
|
||||||
|
|
||||||
msg.friend.incLove();
|
msg.friend.incLove()
|
||||||
//#endregion
|
//#endregion
|
||||||
};
|
}
|
||||||
|
|
||||||
// 末尾のエクスクラメーションマーク
|
// 末尾のエクスクラメーションマーク
|
||||||
const tension = (msg.text.match(/[!!]{2,}/g) || [''])
|
const tension = (msg.text.match(/[!!]{2,}/g) || [""]).sort((a, b) => (a.length < b.length ? 1 : -1))[0].substr(1)
|
||||||
.sort((a, b) => a.length < b.length ? 1 : -1)[0]
|
|
||||||
.substr(1);
|
|
||||||
|
|
||||||
if (msg.includes(['こんにちは', 'こんにちわ'])) {
|
if (msg.includes(["こんにちは", "こんにちわ"])) {
|
||||||
msg.reply(serifs.core.hello(msg.friend.name));
|
msg.reply(serifs.core.hello(msg.friend.name))
|
||||||
incLove();
|
incLove()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes(['こんばんは', 'こんばんわ'])) {
|
if (msg.includes(["こんばんは", "こんばんわ"])) {
|
||||||
msg.reply(serifs.core.helloNight(msg.friend.name));
|
msg.reply(serifs.core.helloNight(msg.friend.name))
|
||||||
incLove();
|
incLove()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes(['おは', 'おっは', 'お早う'])) {
|
if (msg.includes(["おは", "おっは", "お早う"])) {
|
||||||
msg.reply(serifs.core.goodMorning(tension, msg.friend.name));
|
msg.reply(serifs.core.goodMorning(tension, msg.friend.name))
|
||||||
incLove();
|
incLove()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes(['おやすみ', 'お休み'])) {
|
if (msg.includes(["おやすみ", "お休み"])) {
|
||||||
msg.reply(serifs.core.goodNight(msg.friend.name));
|
msg.reply(serifs.core.goodNight(msg.friend.name))
|
||||||
incLove();
|
incLove()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes(['行ってくる', '行ってきます', 'いってくる', 'いってきます'])) {
|
if (msg.includes(["行ってくる", "行ってきます", "いってくる", "いってきます"])) {
|
||||||
|
msg.reply(msg.friend.love >= 7 ? serifs.core.itterassyai.love(msg.friend.name) : serifs.core.itterassyai.normal(msg.friend.name))
|
||||||
|
incLove()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.includes(["ただいま"])) {
|
||||||
msg.reply(
|
msg.reply(
|
||||||
msg.friend.love >= 7
|
msg.friend.love >= 15 ? serifs.core.okaeri.love2(msg.friend.name) : msg.friend.love >= 7 ? getSerif(serifs.core.okaeri.love(msg.friend.name)) : serifs.core.okaeri.normal(msg.friend.name)
|
||||||
? serifs.core.itterassyai.love(msg.friend.name)
|
)
|
||||||
: serifs.core.itterassyai.normal(msg.friend.name));
|
incLove()
|
||||||
incLove();
|
return true
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes(['ただいま'])) {
|
return false
|
||||||
msg.reply(
|
|
||||||
msg.friend.love >= 15 ? serifs.core.okaeri.love2(msg.friend.name) :
|
|
||||||
msg.friend.love >= 7 ? getSerif(serifs.core.okaeri.love(msg.friend.name)) :
|
|
||||||
serifs.core.okaeri.normal(msg.friend.name));
|
|
||||||
incLove();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private erait(msg: Message): boolean {
|
private erait(msg: Message): boolean {
|
||||||
const match = msg.extractedText.match(/(.+?)た(から|ので)(褒|ほ)めて/);
|
const match = msg.extractedText.match(/(.+?)た(から|ので)(褒|ほ)めて/)
|
||||||
if (match) {
|
if (match) {
|
||||||
msg.reply(getSerif(serifs.core.erait.specify(match[1], msg.friend.name)));
|
msg.reply(getSerif(serifs.core.erait.specify(match[1], msg.friend.name)))
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const match2 = msg.extractedText.match(/(.+?)る(から|ので)(褒|ほ)めて/);
|
const match2 = msg.extractedText.match(/(.+?)る(から|ので)(褒|ほ)めて/)
|
||||||
if (match2) {
|
if (match2) {
|
||||||
msg.reply(getSerif(serifs.core.erait.specify(match2[1], msg.friend.name)));
|
msg.reply(getSerif(serifs.core.erait.specify(match2[1], msg.friend.name)))
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const match3 = msg.extractedText.match(/(.+?)だから(褒|ほ)めて/);
|
const match3 = msg.extractedText.match(/(.+?)だから(褒|ほ)めて/)
|
||||||
if (match3) {
|
if (match3) {
|
||||||
msg.reply(getSerif(serifs.core.erait.specify(match3[1], msg.friend.name)));
|
msg.reply(getSerif(serifs.core.erait.specify(match3[1], msg.friend.name)))
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!msg.includes(['褒めて', 'ほめて'])) return false;
|
if (!msg.includes(["褒めて", "ほめて"])) return false
|
||||||
|
|
||||||
msg.reply(getSerif(serifs.core.erait.general(msg.friend.name)));
|
msg.reply(getSerif(serifs.core.erait.general(msg.friend.name)))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private omedeto(msg: Message): boolean {
|
private omedeto(msg: Message): boolean {
|
||||||
if (!msg.includes(['おめでと'])) return false;
|
if (!msg.includes(["おめでと"])) return false
|
||||||
|
|
||||||
msg.reply(serifs.core.omedeto(msg.friend.name));
|
msg.reply(serifs.core.omedeto(msg.friend.name))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private nadenade(msg: Message): boolean {
|
private nadenade(msg: Message): boolean {
|
||||||
if (!msg.includes(['なでなで'])) return false;
|
if (!msg.includes(["なでなで"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
//#region 1日に1回だけ親愛度を上げる(嫌われてない場合のみ)
|
//#region 1日に1回だけ親愛度を上げる(嫌われてない場合のみ)
|
||||||
if (msg.friend.love >= 0) {
|
if (msg.friend.love >= 0) {
|
||||||
const today = getDate();
|
const today = getDate()
|
||||||
|
|
||||||
const data = msg.friend.getPerModulesData(this);
|
const data = msg.friend.getPerModulesData(this)
|
||||||
|
|
||||||
if (data.lastNadenadeAt != today) {
|
if (data.lastNadenadeAt != today) {
|
||||||
data.lastNadenadeAt = today;
|
data.lastNadenadeAt = today
|
||||||
msg.friend.setPerModulesData(this, data);
|
msg.friend.setPerModulesData(this, data)
|
||||||
|
|
||||||
msg.friend.incLove();
|
msg.friend.incLove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
msg.reply(getSerif(
|
msg.reply(
|
||||||
msg.friend.love >= 10 ? serifs.core.nadenade.love3 :
|
getSerif(
|
||||||
msg.friend.love >= 5 ? serifs.core.nadenade.love2 :
|
msg.friend.love >= 10
|
||||||
msg.friend.love <= -15 ? serifs.core.nadenade.hate4 :
|
? serifs.core.nadenade.love3
|
||||||
msg.friend.love <= -10 ? serifs.core.nadenade.hate3 :
|
: msg.friend.love >= 5
|
||||||
msg.friend.love <= -5 ? serifs.core.nadenade.hate2 :
|
? serifs.core.nadenade.love2
|
||||||
msg.friend.love <= -1 ? serifs.core.nadenade.hate1 :
|
: msg.friend.love <= -15
|
||||||
serifs.core.nadenade.normal
|
? serifs.core.nadenade.hate4
|
||||||
));
|
: msg.friend.love <= -10
|
||||||
|
? serifs.core.nadenade.hate3
|
||||||
|
: msg.friend.love <= -5
|
||||||
|
? serifs.core.nadenade.hate2
|
||||||
|
: msg.friend.love <= -1
|
||||||
|
? serifs.core.nadenade.hate1
|
||||||
|
: serifs.core.nadenade.normal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private kawaii(msg: Message): boolean {
|
private kawaii(msg: Message): boolean {
|
||||||
if (!msg.includes(['かわいい', '可愛い'])) return false;
|
if (!msg.includes(["かわいい", "可愛い"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(getSerif(
|
msg.reply(getSerif(msg.friend.love >= 5 ? serifs.core.kawaii.love : msg.friend.love <= -3 ? serifs.core.kawaii.hate : serifs.core.kawaii.normal))
|
||||||
msg.friend.love >= 5 ? serifs.core.kawaii.love :
|
|
||||||
msg.friend.love <= -3 ? serifs.core.kawaii.hate :
|
|
||||||
serifs.core.kawaii.normal));
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private suki(msg: Message): boolean {
|
private suki(msg: Message): boolean {
|
||||||
if (!msg.or(['好き', 'すき'])) return false;
|
if (!msg.or(["好き", "すき"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(msg.friend.love >= 5 ? (msg.friend.name ? serifs.core.suki.love(msg.friend.name) : serifs.core.suki.normal) : msg.friend.love <= -3 ? serifs.core.suki.hate : serifs.core.suki.normal)
|
||||||
msg.friend.love >= 5 ? (msg.friend.name ? serifs.core.suki.love(msg.friend.name) : serifs.core.suki.normal) :
|
|
||||||
msg.friend.love <= -3 ? serifs.core.suki.hate :
|
|
||||||
serifs.core.suki.normal);
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private hug(msg: Message): boolean {
|
private hug(msg: Message): boolean {
|
||||||
if (!msg.or(['ぎゅ', 'むぎゅ', /^はぐ(し(て|よ|よう)?)?$/])) return false;
|
if (!msg.or(["ぎゅ", "むぎゅ", /^はぐ(し(て|よ|よう)?)?$/])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
//#region 前のハグから1分経ってない場合は返信しない
|
//#region 前のハグから1分経ってない場合は返信しない
|
||||||
// これは、「ハグ」と言って「ぎゅー」と返信したとき、相手が
|
// これは、「ハグ」と言って「ぎゅー」と返信したとき、相手が
|
||||||
// それに対してさらに「ぎゅー」と返信するケースがあったため。
|
// それに対してさらに「ぎゅー」と返信するケースがあったため。
|
||||||
// そうするとその「ぎゅー」に対してもマッチするため、また
|
// そうするとその「ぎゅー」に対してもマッチするため、また
|
||||||
// 藍がそれに返信してしまうことになり、少し不自然になる。
|
// ぬるきゃっとちゃんがそれに返信してしまうことになり、少し不自然になる。
|
||||||
// これを防ぐために前にハグしてから少し時間が経っていないと
|
// これを防ぐために前にハグしてから少し時間が経っていないと
|
||||||
// 返信しないようにする
|
// 返信しないようにする
|
||||||
const now = Date.now();
|
const now = Date.now()
|
||||||
|
|
||||||
const data = msg.friend.getPerModulesData(this);
|
const data = msg.friend.getPerModulesData(this)
|
||||||
|
|
||||||
if (data.lastHuggedAt != null) {
|
if (data.lastHuggedAt != null) {
|
||||||
if (now - data.lastHuggedAt < (1000 * 60)) return true;
|
if (now - data.lastHuggedAt < 1000 * 60) return true
|
||||||
}
|
}
|
||||||
|
|
||||||
data.lastHuggedAt = now;
|
data.lastHuggedAt = now
|
||||||
msg.friend.setPerModulesData(this, data);
|
msg.friend.setPerModulesData(this, data)
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(msg.friend.love >= 5 ? serifs.core.hug.love : msg.friend.love <= -3 ? serifs.core.hug.hate : serifs.core.hug.normal)
|
||||||
msg.friend.love >= 5 ? serifs.core.hug.love :
|
|
||||||
msg.friend.love <= -3 ? serifs.core.hug.hate :
|
|
||||||
serifs.core.hug.normal);
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private humu(msg: Message): boolean {
|
private humu(msg: Message): boolean {
|
||||||
if (!msg.includes(['踏んで'])) return false;
|
if (!msg.includes(["踏んで"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(msg.friend.love >= 5 ? serifs.core.humu.love : msg.friend.love <= -3 ? serifs.core.humu.hate : serifs.core.humu.normal)
|
||||||
msg.friend.love >= 5 ? serifs.core.humu.love :
|
|
||||||
msg.friend.love <= -3 ? serifs.core.humu.hate :
|
|
||||||
serifs.core.humu.normal);
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private batou(msg: Message): boolean {
|
private batou(msg: Message): boolean {
|
||||||
if (!msg.includes(['罵倒して', '罵って'])) return false;
|
if (!msg.includes(["罵倒して", "罵って"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(msg.friend.love >= 5 ? serifs.core.batou.love : msg.friend.love <= -5 ? serifs.core.batou.hate : serifs.core.batou.normal)
|
||||||
msg.friend.love >= 5 ? serifs.core.batou.love :
|
|
||||||
msg.friend.love <= -5 ? serifs.core.batou.hate :
|
|
||||||
serifs.core.batou.normal);
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private itai(msg: Message): boolean {
|
private itai(msg: Message): boolean {
|
||||||
if (!msg.or(['痛い', 'いたい']) && !msg.extractedText.endsWith('痛い')) return false;
|
if (!msg.or(["痛い", "いたい"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(serifs.core.itai(msg.friend.name));
|
msg.reply(serifs.core.itai(msg.friend.name))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private turai(msg: Message): boolean {
|
||||||
|
if (!msg.or(["辛い", "つらい"])) return false
|
||||||
|
|
||||||
|
// メッセージのみ
|
||||||
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
|
msg.reply(msg.friend.love >= 5 ? serifs.core.turai.love(msg.friend.name) : msg.friend.love >= -3 ? serifs.core.turai.hate : serifs.core.turai.normal(msg.friend.name))
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private kurusii(msg: Message): boolean {
|
||||||
|
if (!msg.or(["苦しい", "くるしい"])) return false
|
||||||
|
|
||||||
|
// メッセージのみ
|
||||||
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
|
msg.reply(msg.friend.love >= 5 ? serifs.core.kurusii.love(msg.friend.name) : msg.friend.love >= -3 ? serifs.core.kurusii.hate : serifs.core.kurusii.normal(msg.friend.name))
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private ote(msg: Message): boolean {
|
private ote(msg: Message): boolean {
|
||||||
if (!msg.or(['お手'])) return false;
|
if (!msg.or(["お手"])) return false
|
||||||
|
|
||||||
// メッセージのみ
|
// メッセージのみ
|
||||||
if (!msg.isDm) return true;
|
if (!msg.isDm) return true
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(msg.friend.love >= 10 ? serifs.core.ote.love2 : msg.friend.love >= 5 ? serifs.core.ote.love1 : serifs.core.ote.normal)
|
||||||
msg.friend.love >= 10 ? serifs.core.ote.love2 :
|
|
||||||
msg.friend.love >= 5 ? serifs.core.ote.love1 :
|
|
||||||
serifs.core.ote.normal);
|
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private ponkotu(msg: Message): boolean | HandlerResult {
|
private ponkotu(msg: Message): boolean | HandlerResult {
|
||||||
if (!msg.includes(['ぽんこつ'])) return false;
|
if (!msg.includes(["ぽんこつ"])) return false
|
||||||
|
|
||||||
msg.friend.decLove();
|
msg.friend.decLove()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reaction: 'angry'
|
reaction: "angry",
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private rmrf(msg: Message): boolean | HandlerResult {
|
private rmrf(msg: Message): boolean | HandlerResult {
|
||||||
if (!msg.includes(['rm -rf'])) return false;
|
if (!msg.includes(["rm -rf"])) return false
|
||||||
|
|
||||||
msg.friend.decLove();
|
msg.friend.decLove()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reaction: 'angry'
|
reaction: "angry",
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private shutdown(msg: Message): boolean | HandlerResult {
|
private shutdown(msg: Message): boolean | HandlerResult {
|
||||||
if (!msg.includes(['shutdown'])) return false;
|
if (!msg.includes(["shutdown"])) return false
|
||||||
|
|
||||||
msg.reply(serifs.core.shutdown);
|
msg.reply(serifs.core.shutdown)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reaction: 'confused'
|
reaction: "confused",
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,72 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Message from "@/message"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Message from '@/message';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'timer';
|
public readonly name = "timer"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
return {
|
return {
|
||||||
mentionHook: this.mentionHook,
|
mentionHook: this.mentionHook,
|
||||||
timeoutCallback: this.timeoutCallback,
|
timeoutCallback: this.timeoutCallback,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async mentionHook(msg: Message) {
|
private async mentionHook(msg: Message) {
|
||||||
const secondsQuery = (msg.text || '').match(/([0-9]+)秒/);
|
const secondsQuery = (msg.text || "").match(/([0-9]+)秒/)
|
||||||
const minutesQuery = (msg.text || '').match(/([0-9]+)分/);
|
const minutesQuery = (msg.text || "").match(/([0-9]+)分/)
|
||||||
const hoursQuery = (msg.text || '').match(/([0-9]+)時間/);
|
const hoursQuery = (msg.text || "").match(/([0-9]+)時間/)
|
||||||
|
|
||||||
const seconds = secondsQuery ? parseInt(secondsQuery[1], 10) : 0;
|
const seconds = secondsQuery ? parseInt(secondsQuery[1], 10) : 0
|
||||||
const minutes = minutesQuery ? parseInt(minutesQuery[1], 10) : 0;
|
const minutes = minutesQuery ? parseInt(minutesQuery[1], 10) : 0
|
||||||
const hours = hoursQuery ? parseInt(hoursQuery[1], 10) : 0;
|
const hours = hoursQuery ? parseInt(hoursQuery[1], 10) : 0
|
||||||
|
|
||||||
if (!(secondsQuery || minutesQuery || hoursQuery)) return false;
|
if (!(secondsQuery || minutesQuery || hoursQuery)) return false
|
||||||
|
|
||||||
if ((seconds + minutes + hours) == 0) {
|
if (seconds + minutes + hours == 0) {
|
||||||
msg.reply(serifs.timer.invalid);
|
msg.reply(serifs.timer.invalid)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const time =
|
const time = 1000 * seconds + 1000 * 60 * minutes + 1000 * 60 * 60 * hours
|
||||||
(1000 * seconds) +
|
|
||||||
(1000 * 60 * minutes) +
|
|
||||||
(1000 * 60 * 60 * hours);
|
|
||||||
|
|
||||||
if (time > 86400000) {
|
if (time > 86400000) {
|
||||||
msg.reply(serifs.timer.tooLong);
|
msg.reply(serifs.timer.tooLong)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.reply(serifs.timer.set);
|
msg.reply(serifs.timer.set)
|
||||||
|
|
||||||
const str = `${hours ? hoursQuery![0] : ''}${minutes ? minutesQuery![0] : ''}${seconds ? secondsQuery![0] : ''}`;
|
const str = `${hours ? hoursQuery![0] : ""}${minutes ? minutesQuery![0] : ""}${seconds ? secondsQuery![0] : ""}`
|
||||||
|
|
||||||
// タイマーセット
|
// タイマーセット
|
||||||
this.setTimeoutWithPersistence(time, {
|
this.setTimeoutWithPersistence(time, {
|
||||||
isDm: msg.isDm,
|
isDm: msg.isDm,
|
||||||
msgId: msg.id,
|
msgId: msg.id,
|
||||||
userId: msg.friend.userId,
|
userId: msg.friend.userId,
|
||||||
time: str
|
time: str,
|
||||||
});
|
})
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private timeoutCallback(data) {
|
private timeoutCallback(data) {
|
||||||
const friend = this.ai.lookupFriend(data.userId);
|
const friend = this.nullcatChan.lookupFriend(data.userId)
|
||||||
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
if (friend == null) return // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||||
const text = serifs.timer.notify(data.time, friend.name);
|
const text = serifs.timer.notify(data.time, friend.name)
|
||||||
if (data.isDm) {
|
if (data.isDm) {
|
||||||
this.ai.sendMessage(friend.userId, {
|
this.nullcatChan.sendMessage(friend.userId, {
|
||||||
text: text
|
text: text,
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
this.ai.post({
|
this.nullcatChan.post({
|
||||||
replyId: data.msgId,
|
replyId: data.msgId,
|
||||||
text: text
|
text: text,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
157
src/modules/trace-moe/index.ts
Normal file
157
src/modules/trace-moe/index.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
import { z } from "zod"
|
||||||
|
import humanizeDuration = require("humanize-duration")
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "trace-moe"
|
||||||
|
|
||||||
|
private readonly itemSchema = z.object({
|
||||||
|
anilist: z.object({
|
||||||
|
title: z.object({
|
||||||
|
native: z.string().nullable(),
|
||||||
|
romaji: z.string().nullable(),
|
||||||
|
english: z.string().nullable(),
|
||||||
|
}),
|
||||||
|
isAdult: z.boolean().nullable(),
|
||||||
|
}),
|
||||||
|
episode: z.number().or(z.string()).or(z.array(z.number())).nullable(),
|
||||||
|
from: z.number().nullable(),
|
||||||
|
to: z.number().nullable(),
|
||||||
|
similarity: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
private readonly schema = z.object({
|
||||||
|
error: z.string(),
|
||||||
|
result: z.array(this.itemSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private getImageUrl(message: Message) {
|
||||||
|
if (!message.files) {
|
||||||
|
this.log("No files found.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredImageFiles = message.files.filter((file) => file.type.startsWith("image"))
|
||||||
|
|
||||||
|
if (!filteredImageFiles.length) {
|
||||||
|
this.log("No valid images found.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredImageFiles[0].url
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async getFromTraceMoe(imageUrl: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://api.trace.moe/search?anilistInfo&url=${encodeURIComponent(imageUrl)}`)
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
const result = this.schema.safeParse(data)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
this.log("Validation failed.")
|
||||||
|
this.log(JSON.stringify(data))
|
||||||
|
console.warn(result.error)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data.result[0]
|
||||||
|
} catch (error) {
|
||||||
|
this.log("Failed to fetch data from Trace Moe.")
|
||||||
|
console.warn(error)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(message: Message) {
|
||||||
|
if (!message.includes(["アニメ"])) return false
|
||||||
|
|
||||||
|
if (message.isDm) {
|
||||||
|
message.reply("僕にアニメのシーンの画像を添付して「アニメ教えて」ってメンションすると、何のアニメか教えるよ!")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl = this.getImageUrl(message)
|
||||||
|
|
||||||
|
if (!imageUrl) {
|
||||||
|
message.reply("画像を添付してね!")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const traceMoe = await this.getFromTraceMoe(imageUrl)
|
||||||
|
|
||||||
|
if (!traceMoe) {
|
||||||
|
message.reply("ぬぁ~~~、いまはめんどくさいかも…")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const animeTitle = traceMoe.anilist.title.native || traceMoe.anilist.title.english
|
||||||
|
|
||||||
|
if (!animeTitle) {
|
||||||
|
message.reply("ごめんね、わかんないや…")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof traceMoe.episode === "string") traceMoe.episode = traceMoe.episode.replace(/\|/g, "か")
|
||||||
|
else if (Array.isArray(traceMoe.episode)) traceMoe.episode = traceMoe.episode.join("話と")
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
language: "ja",
|
||||||
|
round: true,
|
||||||
|
delimiter: "",
|
||||||
|
spacer: "",
|
||||||
|
}
|
||||||
|
const fromText = traceMoe.from !== null ? humanizeDuration(traceMoe.from * 1000, options) : null
|
||||||
|
const toText = traceMoe.to !== null ? humanizeDuration(traceMoe.to * 1000, options) : null
|
||||||
|
|
||||||
|
const pronoun = traceMoe.episode || (traceMoe.from && traceMoe.to) ? "これは" : "このアニメは"
|
||||||
|
|
||||||
|
const prefix = (() => {
|
||||||
|
if (traceMoe.similarity >= 0.9) return pronoun
|
||||||
|
if (traceMoe.similarity >= 0.8) return `${pronoun}たぶん`
|
||||||
|
return "よくわかんないけど、強いて言うなら"
|
||||||
|
})()
|
||||||
|
|
||||||
|
const suffix = (() => {
|
||||||
|
if (traceMoe.similarity >= 0.9) return "だよ!"
|
||||||
|
if (traceMoe.similarity >= 0.8) return "だと思う!"
|
||||||
|
return "に似てるかな"
|
||||||
|
})()
|
||||||
|
|
||||||
|
const time = fromText && toText && fromText === toText ? fromText : `${fromText}から${toText}`
|
||||||
|
|
||||||
|
|
||||||
|
const detail = (() => {
|
||||||
|
if (traceMoe.episode && traceMoe.from && traceMoe.to) return `第${traceMoe.episode}話の${time}`
|
||||||
|
if (traceMoe.from && traceMoe.to) return `の${time}`
|
||||||
|
if (traceMoe.episode) return `の第${traceMoe.episode}話`
|
||||||
|
return ""
|
||||||
|
})()
|
||||||
|
const content = `『${animeTitle}』${detail}`
|
||||||
|
|
||||||
|
const messageToReply = `${prefix}${content}${suffix}`
|
||||||
|
|
||||||
|
if (traceMoe.anilist.isAdult) {
|
||||||
|
message.reply(messageToReply, { cw: "そぎぎ" })
|
||||||
|
} else {
|
||||||
|
message.reply(messageToReply)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import Friend from "@/friend"
|
||||||
import Module from '@/module';
|
import Module from "@/module"
|
||||||
import Friend from '@/friend';
|
import serifs from "@/serifs"
|
||||||
import serifs from '@/serifs';
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'valentine';
|
public readonly name = "valentine"
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public install() {
|
public install() {
|
||||||
this.crawleValentine();
|
this.crawleValentine()
|
||||||
setInterval(this.crawleValentine, 1000 * 60 * 3);
|
setInterval(this.crawleValentine, 1000 * 60 * 3)
|
||||||
|
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,33 +19,33 @@ export default class extends Module {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private crawleValentine() {
|
private crawleValentine() {
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
|
|
||||||
const isValentine = now.getMonth() == 1 && now.getDate() == 14;
|
const isValentine = now.getMonth() == 1 && now.getDate() == 14
|
||||||
if (!isValentine) return;
|
if (!isValentine) return
|
||||||
|
|
||||||
const date = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
|
const date = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`
|
||||||
|
|
||||||
const friends = this.ai.friends.find({} as any);
|
const friends = this.nullcatChan.friends.find({} as any)
|
||||||
|
|
||||||
friends.forEach(f => {
|
friends.forEach((f) => {
|
||||||
const friend = new Friend(this.ai, { doc: f });
|
const friend = new Friend(this.nullcatChan, { doc: f })
|
||||||
|
|
||||||
// 親愛度が5以上必要
|
// 親愛度が7以上必要
|
||||||
if (friend.love < 5) return;
|
if (friend.love < 7) return
|
||||||
|
|
||||||
const data = friend.getPerModulesData(this);
|
const data = friend.getPerModulesData(this)
|
||||||
|
|
||||||
if (data.lastChocolated == date) return;
|
if (data.lastChocolated == date) return
|
||||||
|
|
||||||
data.lastChocolated = date;
|
data.lastChocolated = date
|
||||||
friend.setPerModulesData(this, data);
|
friend.setPerModulesData(this, data)
|
||||||
|
|
||||||
const text = serifs.valentine.chocolateForYou(friend.name);
|
const text = serifs.valentine.chocolateForYou(friend.name)
|
||||||
|
|
||||||
this.ai.sendMessage(friend.userId, {
|
this.nullcatChan.sendMessage(friend.userId, {
|
||||||
text: text
|
text: text,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
src/modules/what/index.ts
Normal file
27
src/modules/what/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "what"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(message: Message) {
|
||||||
|
if (!message.includes(["って何", "ってなに", "ってにゃに", ":is_nani:"])) return false
|
||||||
|
|
||||||
|
const match = message.extractedText.match(/(.+?)って(何|なに|にゃに)/)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
message.reply(`Google先生に聞いてみた!!!\n${match[1]} 検索`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
50
src/modules/yarukoto/index.ts
Normal file
50
src/modules/yarukoto/index.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import Message from "@/message"
|
||||||
|
import Module from "@/module"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
|
||||||
|
const yarukotoList = [
|
||||||
|
"勉強する",
|
||||||
|
"コード書く",
|
||||||
|
"お絵描きする",
|
||||||
|
"とりあえずトイレ行く",
|
||||||
|
"とりあえずお水とってくる",
|
||||||
|
"寝る",
|
||||||
|
"ゲームする",
|
||||||
|
"通話する",
|
||||||
|
"とりあえずAmazon見る",
|
||||||
|
"そんなことより薬飲んだ?",
|
||||||
|
"ご飯食べる",
|
||||||
|
"VRやる",
|
||||||
|
"部屋掃除する",
|
||||||
|
"お風呂入る",
|
||||||
|
"とりあえず今はmisskeyやっとく",
|
||||||
|
"落書きする",
|
||||||
|
"掃除機かける",
|
||||||
|
"ごろごろする",
|
||||||
|
"YouTube見る",
|
||||||
|
"爪切る",
|
||||||
|
"カフェイン飲む",
|
||||||
|
]
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = "yarukoto"
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (msg.includes(["やる事", "やること", "なにしよ", "なにやろ", "にゃにしよ", "にゃにやろ"])) {
|
||||||
|
const yarukoto = yarukotoList[Math.floor(Math.random() * yarukotoList.length)]
|
||||||
|
|
||||||
|
msg.reply(yarukoto)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/ng-words.ts
Normal file
77
src/ng-words.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import toHiragana from '../../NullcatChan-old/src/utils/to-hiragana';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const readline = require('readline');
|
||||||
|
|
||||||
|
export default class NGWord {
|
||||||
|
private excludedWords: string[] = [];
|
||||||
|
private ngWords: string[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const rs = fs.createReadStream('ngwords.txt');
|
||||||
|
const rl = readline.createInterface(rs);
|
||||||
|
rl.on('line', (line) => {
|
||||||
|
const word = toHiragana(line.trim().toLowerCase());
|
||||||
|
if (word.startsWith('#')) return;
|
||||||
|
if (word.startsWith('-')) {
|
||||||
|
if (/な/g.test(word)) this.excludedWords.push(word.substring(1).replace(/な/g, 'にゃ'));
|
||||||
|
this.excludedWords.push(word.substring(1));
|
||||||
|
} else {
|
||||||
|
if (/な/g.test(word)) this.ngWords.push(word.replace(/な/g, 'にゃ'));
|
||||||
|
this.ngWords.push(word);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeAllowedWord(str: string): string {
|
||||||
|
let text = toHiragana(str.toLowerCase());
|
||||||
|
this.excludedWords.forEach((w) => {
|
||||||
|
text = text.replace(w, '');
|
||||||
|
});
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get get(): string[] {
|
||||||
|
return this.ngWords;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNGWord(str: string): boolean {
|
||||||
|
const word = toHiragana(str.trim().toLowerCase());
|
||||||
|
if (this.ngWords.some((ng) => word.includes(ng))) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.ngWords.push(word);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNGWord(str: string): boolean {
|
||||||
|
const word = toHiragana(str.trim().toLowerCase());
|
||||||
|
if (this.ngWords.some((ng) => word.includes(ng))) {
|
||||||
|
this.ngWords = this.ngWords.filter((ng) => ng !== word);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addExcludedWord(str: string): boolean {
|
||||||
|
const word = toHiragana(str.trim().toLowerCase());
|
||||||
|
if (this.excludedWords.some((ng) => word.includes(ng))) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.excludedWords.push(word);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeExcludedWord(str: string): boolean {
|
||||||
|
const word = toHiragana(str.trim().toLowerCase());
|
||||||
|
if (this.excludedWords.some((ng) => word.includes(ng))) {
|
||||||
|
this.excludedWords = this.excludedWords.filter((e) => e !== word);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
522
src/nullcat-chan.ts
Normal file
522
src/nullcat-chan.ts
Normal file
@ -0,0 +1,522 @@
|
|||||||
|
// NULLCAT-CHAN CORE
|
||||||
|
|
||||||
|
import config from "./config"
|
||||||
|
import Friend, { FriendDoc } from "./friend"
|
||||||
|
import Message from "./message"
|
||||||
|
import { User } from "./misskey/user"
|
||||||
|
import Module from "../../NullcatChan-old/src/module"
|
||||||
|
import Stream from "./stream"
|
||||||
|
import log from "../../NullcatChan-old/src/utils/log"
|
||||||
|
import autobind from "autobind-decorator"
|
||||||
|
import * as chalk from "chalk"
|
||||||
|
import * as fs from "fs"
|
||||||
|
import * as loki from "lokijs"
|
||||||
|
import * as request from "request-promise-native"
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
const delay = require("timeout-as-promise")
|
||||||
|
|
||||||
|
const pkg = require("../../NullcatChan-old/package.json")
|
||||||
|
|
||||||
|
type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>
|
||||||
|
type ContextHook = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>
|
||||||
|
type TimeoutCallback = (data?: any) => void
|
||||||
|
|
||||||
|
export type HandlerResult = {
|
||||||
|
reaction?: string | null
|
||||||
|
immediate?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallerResult = {
|
||||||
|
mentionHook?: MentionHook
|
||||||
|
contextHook?: ContextHook
|
||||||
|
timeoutCallback?: TimeoutCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Meta = {
|
||||||
|
lastWakingAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ぬるきゃっとちゃん
|
||||||
|
*/
|
||||||
|
export default class NullcatChan {
|
||||||
|
public readonly version = pkg._v
|
||||||
|
public account: User
|
||||||
|
public connection: Stream
|
||||||
|
public modules: Module[] = []
|
||||||
|
private mentionHooks: MentionHook[] = []
|
||||||
|
private contextHooks: { [moduleName: string]: ContextHook } = {}
|
||||||
|
private timeoutCallbacks: { [moduleName: string]: TimeoutCallback } = {}
|
||||||
|
public db: loki
|
||||||
|
public lastSleepedAt: number
|
||||||
|
|
||||||
|
private meta: loki.Collection<Meta>
|
||||||
|
|
||||||
|
private contexts: loki.Collection<{
|
||||||
|
isDm: boolean
|
||||||
|
noteId?: string
|
||||||
|
userId?: string
|
||||||
|
module: string
|
||||||
|
key: string | null
|
||||||
|
data?: any
|
||||||
|
}>
|
||||||
|
|
||||||
|
private timers: loki.Collection<{
|
||||||
|
id: string
|
||||||
|
module: string
|
||||||
|
insertedAt: number
|
||||||
|
delay: number
|
||||||
|
data?: any
|
||||||
|
}>
|
||||||
|
|
||||||
|
public friends: loki.Collection<FriendDoc>
|
||||||
|
public moduleData: loki.Collection<any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ぬるきゃっとちゃんのインスタンスを生成します
|
||||||
|
* @param account ぬるきゃっとちゃんを遣うアカウント
|
||||||
|
* @param modules モジュール。先頭のモジュールほど高優先度
|
||||||
|
*/
|
||||||
|
constructor(account: User, modules: Module[]) {
|
||||||
|
this.account = account
|
||||||
|
this.modules = modules
|
||||||
|
|
||||||
|
let memoryDir = "."
|
||||||
|
if (config.memoryDir) {
|
||||||
|
memoryDir = config.memoryDir
|
||||||
|
}
|
||||||
|
const file = process.env.NODE_ENV === "test" ? `${memoryDir}/test.memory.json` : `${memoryDir}/memory.json`
|
||||||
|
|
||||||
|
this.log(`Lodaing the memory from ${file}...`)
|
||||||
|
|
||||||
|
this.db = new loki(file, {
|
||||||
|
autoload: true,
|
||||||
|
autosave: true,
|
||||||
|
autosaveInterval: 1000,
|
||||||
|
autoloadCallback: (err) => {
|
||||||
|
if (err) {
|
||||||
|
this.log(chalk.red(`Failed to load the memory: ${err}`))
|
||||||
|
} else {
|
||||||
|
this.log(chalk.green("The memory loaded successfully"))
|
||||||
|
this.run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public log(msg: string) {
|
||||||
|
log(chalk`[{magenta Core}]: ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private run() {
|
||||||
|
//#region Init DB
|
||||||
|
this.meta = this.getCollection("meta", {})
|
||||||
|
|
||||||
|
this.contexts = this.getCollection("contexts", {
|
||||||
|
indices: ["key"],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.timers = this.getCollection("timers", {
|
||||||
|
indices: ["module"],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.friends = this.getCollection("friends", {
|
||||||
|
indices: ["userId"],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.moduleData = this.getCollection("moduleData", {
|
||||||
|
indices: ["module"],
|
||||||
|
})
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
const meta = this.getMeta()
|
||||||
|
this.lastSleepedAt = meta.lastWakingAt
|
||||||
|
|
||||||
|
// Init stream
|
||||||
|
this.connection = new Stream()
|
||||||
|
|
||||||
|
//#region Main stream
|
||||||
|
const mainStream = this.connection.useSharedConnection("main")
|
||||||
|
|
||||||
|
// メンションされたとき
|
||||||
|
mainStream.on("mention", async (data) => {
|
||||||
|
if (data.userId == this.account.id) return // 自分は弾く
|
||||||
|
if (data.text && data.text.startsWith("@" + this.account.username)) {
|
||||||
|
// Misskeyのバグで投稿が非公開扱いになる
|
||||||
|
if (data.text == null) data = await this.api("notes/show", { noteId: data.id })
|
||||||
|
this.onReceiveMessage(new Message(this, data, false))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返信されたとき
|
||||||
|
mainStream.on("reply", async (data) => {
|
||||||
|
if (data.userId == this.account.id) return // 自分は弾く
|
||||||
|
if (data.text && data.text.startsWith("@" + this.account.username)) return
|
||||||
|
// Misskeyのバグで投稿が非公開扱いになる
|
||||||
|
if (data.text == null) data = await this.api("notes/show", { noteId: data.id })
|
||||||
|
this.onReceiveMessage(new Message(this, data, false))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Renoteされたとき
|
||||||
|
mainStream.on("renote", async (data) => {
|
||||||
|
if (data.userId == this.account.id) return // 自分は弾く
|
||||||
|
if (data.text == null && (data.files || []).length == 0) return
|
||||||
|
|
||||||
|
// リアクションする
|
||||||
|
this.api("notes/reactions/create", {
|
||||||
|
noteId: data.id,
|
||||||
|
reaction: ":love_nullcatchan:",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// メッセージ
|
||||||
|
mainStream.on("messagingMessage", (data) => {
|
||||||
|
if (data.userId == this.account.id) return // 自分は弾く
|
||||||
|
this.onReceiveMessage(new Message(this, data, true))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 通知
|
||||||
|
mainStream.on("notification", (data) => {
|
||||||
|
this.onNotification(data)
|
||||||
|
})
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// Install modules
|
||||||
|
this.modules.forEach((m) => {
|
||||||
|
this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`)
|
||||||
|
m.init(this)
|
||||||
|
const res = m.install()
|
||||||
|
if (res != null) {
|
||||||
|
if (res.mentionHook) this.mentionHooks.push(res.mentionHook)
|
||||||
|
if (res.contextHook) this.contextHooks[m.name] = res.contextHook
|
||||||
|
if (res.timeoutCallback) this.timeoutCallbacks[m.name] = res.timeoutCallback
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// タイマー監視
|
||||||
|
this.crawleTimer()
|
||||||
|
setInterval(this.crawleTimer, 1000)
|
||||||
|
|
||||||
|
setInterval(this.logWaking, 10000)
|
||||||
|
|
||||||
|
this.log(chalk.green.bold("Nullcat chan is now running!"))
|
||||||
|
this.log(`Mode: ${process.env.NODE_ENV}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーから話しかけられたとき
|
||||||
|
* (メンション、リプライ、トークのメッセージ)
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private async onReceiveMessage(msg: Message): Promise<void> {
|
||||||
|
this.log(chalk.gray(`<<< An message received: ${chalk.underline(msg.id)}`))
|
||||||
|
|
||||||
|
// Ignore message if the user is a bot
|
||||||
|
// To avoid infinity reply loop.
|
||||||
|
if (msg.user.isBot) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNoContext = !msg.isDm && msg.replyId == null
|
||||||
|
|
||||||
|
// Look up the context
|
||||||
|
const context = isNoContext
|
||||||
|
? null
|
||||||
|
: this.contexts.findOne(
|
||||||
|
msg.isDm
|
||||||
|
? {
|
||||||
|
isDm: true,
|
||||||
|
userId: msg.userId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isDm: false,
|
||||||
|
noteId: msg.replyId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let reaction: string | null = ":love_nullcatchan:"
|
||||||
|
let immediate: boolean = false
|
||||||
|
|
||||||
|
//#region
|
||||||
|
const invokeMentionHooks = async () => {
|
||||||
|
let res: boolean | HandlerResult | null = null
|
||||||
|
|
||||||
|
for (const handler of this.mentionHooks) {
|
||||||
|
res = await handler(msg)
|
||||||
|
if (res === true || typeof res === "object") break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res != null && typeof res === "object") {
|
||||||
|
if (res.reaction != null) reaction = res.reaction
|
||||||
|
if (res.immediate != null) immediate = res.immediate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// コンテキストがあればコンテキストフック呼び出し
|
||||||
|
// なければそれぞれのモジュールについてフックが引っかかるまで呼び出し
|
||||||
|
if (context != null) {
|
||||||
|
const handler = this.contextHooks[context.module]
|
||||||
|
const res = await handler(context.key, msg, context.data)
|
||||||
|
|
||||||
|
if (res != null && typeof res === "object") {
|
||||||
|
if (res.reaction != null) reaction = res.reaction
|
||||||
|
if (res.immediate != null) immediate = res.immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res === false) {
|
||||||
|
await invokeMentionHooks()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await invokeMentionHooks()
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
if (!immediate) {
|
||||||
|
await delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isDm) {
|
||||||
|
// 既読にする
|
||||||
|
this.api("messaging/messages/read", {
|
||||||
|
messageId: msg.id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// リアクションする
|
||||||
|
if (reaction) {
|
||||||
|
this.api("notes/reactions/create", {
|
||||||
|
noteId: msg.id,
|
||||||
|
reaction: reaction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private onNotification(notification: any) {
|
||||||
|
switch (notification.type) {
|
||||||
|
// リアクションされたら親愛度を少し上げる
|
||||||
|
// TODO: リアクション取り消しをよしなにハンドリングする
|
||||||
|
case "reaction": {
|
||||||
|
const friend = new Friend(this, { user: notification.user })
|
||||||
|
friend.incLove(0.1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private crawleTimer() {
|
||||||
|
const timers = this.timers.find()
|
||||||
|
for (const timer of timers) {
|
||||||
|
// タイマーが時間切れかどうか
|
||||||
|
if (Date.now() - (timer.insertedAt + timer.delay) >= 0) {
|
||||||
|
this.log(`Timer expired: ${timer.module} ${timer.id}`)
|
||||||
|
this.timers.remove(timer)
|
||||||
|
this.timeoutCallbacks[timer.module](timer.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private logWaking() {
|
||||||
|
this.setMeta({
|
||||||
|
lastWakingAt: Date.now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* データベースのコレクションを取得します
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public getCollection(name: string, opts?: any): loki.Collection {
|
||||||
|
let collection: loki.Collection
|
||||||
|
|
||||||
|
collection = this.db.getCollection(name)
|
||||||
|
|
||||||
|
if (collection == null) {
|
||||||
|
collection = this.db.addCollection(name, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public lookupFriend(userId: User["id"]): Friend | null {
|
||||||
|
const doc = this.friends.findOne({
|
||||||
|
userId: userId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (doc == null) return null
|
||||||
|
|
||||||
|
const friend = new Friend(this, { doc: doc })
|
||||||
|
|
||||||
|
return friend
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ファイルをドライブにアップロードします
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public async upload(file: Buffer | fs.ReadStream, meta: any) {
|
||||||
|
const res = await request.post({
|
||||||
|
url: `${config.apiUrl}/drive/files/create`,
|
||||||
|
formData: {
|
||||||
|
i: config.i,
|
||||||
|
file: {
|
||||||
|
value: file,
|
||||||
|
options: meta,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
json: true,
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 投稿します
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public async post(param: any) {
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
const res = await this.api("notes/create", param)
|
||||||
|
return res.createdNote
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log(chalk`[{magenta Debug:Post}]: ${JSON.stringify(param)}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定ユーザーにトークメッセージを送信します
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public sendMessage(userId: any, param: any) {
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
return this.api(
|
||||||
|
"messaging/messages/create",
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
param
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log(chalk`[{magenta Debug:SendMessage}]: userId: ${userId}`)
|
||||||
|
log(chalk`[{magenta Debug:SendMessage}]: param: ${JSON.stringify(param)}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIを呼び出します
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public api(endpoint: string, param?: any) {
|
||||||
|
return request.post(`${config.apiUrl}/${endpoint}`, {
|
||||||
|
json: Object.assign(
|
||||||
|
{
|
||||||
|
i: config.i,
|
||||||
|
},
|
||||||
|
param
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* コンテキストを生成し、ユーザーからの返信を待ち受けます
|
||||||
|
* @param module 待ち受けるモジュール名
|
||||||
|
* @param key コンテキストを識別するためのキー
|
||||||
|
* @param isDm トークメッセージ上のコンテキストかどうか
|
||||||
|
* @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID
|
||||||
|
* @param data コンテキストに保存するオプションのデータ
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public subscribeReply(module: Module, key: string | null, isDm: boolean, id: string, data?: any) {
|
||||||
|
this.contexts.insertOne(
|
||||||
|
isDm
|
||||||
|
? {
|
||||||
|
isDm: true,
|
||||||
|
userId: id,
|
||||||
|
module: module.name,
|
||||||
|
key: key,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isDm: false,
|
||||||
|
noteId: id,
|
||||||
|
module: module.name,
|
||||||
|
key: key,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返信の待ち受けを解除します
|
||||||
|
* @param module 解除するモジュール名
|
||||||
|
* @param key コンテキストを識別するためのキー
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public unsubscribeReply(module: Module, key: string | null) {
|
||||||
|
this.contexts.findAndRemove({
|
||||||
|
key: key,
|
||||||
|
module: module.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定したミリ秒経過後に、そのモジュールのタイムアウトコールバックを呼び出します。
|
||||||
|
* このタイマーは記憶に永続化されるので、途中でプロセスを再起動しても有効です。
|
||||||
|
* @param module モジュール名
|
||||||
|
* @param delay ミリ秒
|
||||||
|
* @param data オプションのデータ
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public setTimeoutWithPersistence(module: Module, delay: number, data?: any) {
|
||||||
|
const id = uuid()
|
||||||
|
this.timers.insertOne({
|
||||||
|
id: id,
|
||||||
|
module: module.name,
|
||||||
|
insertedAt: Date.now(),
|
||||||
|
delay: delay,
|
||||||
|
data: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.log(`Timer persisted: ${module.name} ${id} ${delay}ms`)
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public getMeta() {
|
||||||
|
const rec = this.meta.findOne()
|
||||||
|
|
||||||
|
if (rec) {
|
||||||
|
return rec
|
||||||
|
} else {
|
||||||
|
const initial: Meta = {
|
||||||
|
lastWakingAt: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.meta.insertOne(initial)
|
||||||
|
return initial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public setMeta(meta: Partial<Meta>) {
|
||||||
|
const rec = this.getMeta()
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(meta)) {
|
||||||
|
rec[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
this.meta.update(rec)
|
||||||
|
}
|
||||||
|
}
|
450
src/serifs.ts
450
src/serifs.ts
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
core: {
|
core: {
|
||||||
setNameOk: name => `わかりました。これからは${name}とお呼びしますね!`,
|
setNameOk: name => `わかった!今度から${name}って呼ぶね!`,
|
||||||
|
|
||||||
san: 'さん付けした方がいいですか?',
|
san: 'さん付けした方がいいかな?',
|
||||||
|
|
||||||
yesOrNo: '「はい」か「いいえ」しかわからないんです...',
|
yesOrNo: 'ごめんね...僕「うん」か「いいえ」しかわからないんだ...',
|
||||||
|
|
||||||
hello: name => name ? `こんにちは、${name}♪` : `こんにちは♪`,
|
hello: name => name ? `やっほぉ${name}` : `やっほぉ!`,
|
||||||
|
|
||||||
helloNight: name => name ? `こんばんは、${name}♪` : `こんばんは♪`,
|
helloNight: name => name ? `こんばんわ${name}!` : `こんばんわ~!`,
|
||||||
|
|
||||||
goodMorning: (tension, name) => name ? `おはようございます、${name}!${tension}` : `おはようございます!${tension}`,
|
goodMorning: (tension, name) => name ? `おはよ${name}!${tension}` : `おはよ!${tension}`,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
goodMorning: {
|
goodMorning: {
|
||||||
@ -22,88 +22,88 @@ export default {
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
goodNight: name => name ? `おやすみなさい、${name}!` : 'おやすみなさい!',
|
goodNight: name => name ? `おやすみ${name}!` : 'おやすみ!',
|
||||||
|
|
||||||
omedeto: name => name ? `ありがとうございます、${name}♪` : 'ありがとうございます♪',
|
omedeto: name => name ? `ありがと~${name}!` : 'ありがと~!',
|
||||||
|
|
||||||
erait: {
|
erait: {
|
||||||
general: name => name ? [
|
general: name => name ? [
|
||||||
`${name}、今日もえらいです!`,
|
`${name}、今日もえらい!`,
|
||||||
`${name}、今日もえらいですよ~♪`
|
`${name}、今日もえらいね!`
|
||||||
] : [
|
] : [
|
||||||
`今日もえらいです!`,
|
`今日もえらい!`,
|
||||||
`今日もえらいですよ~♪`
|
`今日もえらいね!`
|
||||||
],
|
],
|
||||||
|
|
||||||
specify: (thing, name) => name ? [
|
specify: (thing, name) => name ? [
|
||||||
`${name}、${thing}てえらいです!`,
|
`${name}、${thing}てえらい!`,
|
||||||
`${name}、${thing}てえらいですよ~♪`
|
`${name}、${thing}てえらいね!`
|
||||||
] : [
|
] : [
|
||||||
`${thing}てえらいです!`,
|
`${thing}てえらい!`,
|
||||||
`${thing}てえらいですよ~♪`
|
`${thing}てえらいね!`
|
||||||
],
|
],
|
||||||
|
|
||||||
specify2: (thing, name) => name ? [
|
specify2: (thing, name) => name ? [
|
||||||
`${name}、${thing}でえらいです!`,
|
`${name}、${thing}でえらい!`,
|
||||||
`${name}、${thing}でえらいですよ~♪`
|
`${name}、${thing}でえらいね!`
|
||||||
] : [
|
] : [
|
||||||
`${thing}でえらいです!`,
|
`${thing}でえらい!`,
|
||||||
`${thing}でえらいですよ~♪`
|
`${thing}でえらいね!`
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
okaeri: {
|
okaeri: {
|
||||||
love: name => name ? [
|
love: name => name ? [
|
||||||
`おかえりなさい、${name}♪`,
|
`おかえり${name}!`,
|
||||||
`おかえりなさいませっ、${name}っ。`
|
`おかえりぃ${name}~`
|
||||||
] : [
|
] : [
|
||||||
'おかえりなさい♪',
|
'おかえり!',
|
||||||
'おかえりなさいませっ、ご主人様っ。'
|
'おかえりぃ~'
|
||||||
],
|
],
|
||||||
|
|
||||||
love2: name => name ? `おかえりなさいませ♡♡♡${name}っっ♡♡♡♡♡` : 'おかえりなさいませ♡♡♡ご主人様っっ♡♡♡♡♡',
|
love2: name => name ? `おかえり~~!!${name}今日も偉いね:love_nullcatchan:` : 'おかえり~~!!今日も偉いね:love_nullcatchan:',
|
||||||
|
|
||||||
normal: name => name ? `おかえりなさい、${name}!` : 'おかえりなさい!',
|
normal: name => name ? `おかえり${name}!` : 'おかえり!',
|
||||||
},
|
},
|
||||||
|
|
||||||
itterassyai: {
|
itterassyai: {
|
||||||
love: name => name ? `いってらっしゃい、${name}♪` : 'いってらっしゃい♪',
|
love: name => name ? `いってらっしゃい${name}!` : 'いってらっしゃい!',
|
||||||
|
|
||||||
normal: name => name ? `いってらっしゃい、${name}!` : 'いってらっしゃい!',
|
normal: name => name ? `いってらっしゃい${name}!` : 'いってらっしゃい!',
|
||||||
},
|
},
|
||||||
|
|
||||||
tooLong: '長すぎる気がします...',
|
tooLong: '長すぎる..',
|
||||||
|
|
||||||
invalidName: '発音が難しい気がします',
|
invalidName: '発音が難しいよぉ...',
|
||||||
|
|
||||||
nadenade: {
|
nadenade: {
|
||||||
normal: 'ひゃっ…! びっくりしました',
|
normal: 'うにゃ…?! びっくりした...',
|
||||||
|
|
||||||
love2: ['わわっ… 恥ずかしいです', 'あうぅ… 恥ずかしいです…', 'ふやぁ…?'],
|
love2: ['あぅ… 恥ずかしいよぉ', 'あぅ… 恥ずかしぃ…', 'ふみゃ…!?'],
|
||||||
|
|
||||||
love3: ['んぅ… ありがとうございます♪', 'わっ、なんだか落ち着きますね♪', 'くぅんっ… 安心します…', '眠くなってきました…'],
|
love3: ['んへへぇ ありがと:love_nullcatchan:', 'にへぇ~~', 'んみゅっ… ', 'もっともっとぉ...'],
|
||||||
|
|
||||||
hate1: '…っ! やめてほしいです...',
|
hate1: 'やめて',
|
||||||
|
|
||||||
hate2: '触らないでください',
|
hate2: '触んないで',
|
||||||
|
|
||||||
hate3: '近寄らないでください',
|
hate3: 'きもい',
|
||||||
|
|
||||||
hate4: 'やめてください。通報しますよ?',
|
hate4: '..?',
|
||||||
},
|
},
|
||||||
|
|
||||||
kawaii: {
|
kawaii: {
|
||||||
normal: ['ありがとうございます♪', '照れちゃいます...'],
|
normal: ['そんなことないよ?', 'えへへへうれしい。'],
|
||||||
|
|
||||||
love: ['嬉しいです♪', '照れちゃいます...'],
|
love: ['えへへ。うれしいな', 'んむぅ~~...うれしい。'],
|
||||||
|
|
||||||
hate: '…ありがとうございます'
|
hate: 'は?きも。'
|
||||||
},
|
},
|
||||||
|
|
||||||
suki: {
|
suki: {
|
||||||
normal: 'えっ… ありがとうございます…♪',
|
normal: 'えへへ。ありがと~!',
|
||||||
|
|
||||||
love: name => `私もその… ${name}のこと好きですよ!`,
|
love: name => `僕も${name}のこと好き!`,
|
||||||
|
|
||||||
hate: null
|
hate: null
|
||||||
},
|
},
|
||||||
@ -113,360 +113,163 @@ export default {
|
|||||||
|
|
||||||
love: 'ぎゅーっ♪',
|
love: 'ぎゅーっ♪',
|
||||||
|
|
||||||
hate: '離れてください...'
|
hate: '無理...やめて...'
|
||||||
},
|
},
|
||||||
|
|
||||||
humu: {
|
humu: {
|
||||||
love: 'え、えっと…… ふみふみ……… どうですか…?',
|
love: 'もふもふ!ふみふみ!',
|
||||||
|
|
||||||
normal: 'えぇ... それはちょっと...',
|
normal: 'ふみふみ!',
|
||||||
|
|
||||||
hate: '……'
|
hate: '?'
|
||||||
},
|
},
|
||||||
|
|
||||||
batou: {
|
batou: {
|
||||||
love: 'えっと…、お、おバカさん…?',
|
love: 'ば~か♡♡♡',
|
||||||
|
|
||||||
normal: '(じとー…)',
|
normal: 'きっしょ',
|
||||||
|
|
||||||
hate: '…頭大丈夫ですか?'
|
hate: '?'
|
||||||
},
|
},
|
||||||
|
|
||||||
itai: name => name ? `${name}、大丈夫ですか…? いたいのいたいの飛んでけっ!` : '大丈夫ですか…? いたいのいたいの飛んでけっ!',
|
itai: name => name ? `${name}大丈夫?なでなで` : '大丈夫?なでなで',
|
||||||
|
|
||||||
|
turai: {
|
||||||
|
love: name => name ? `${name}なでなで ぽんぽんぎゅ~!` : 'なでなで ぽんぽんぎゅ~!',
|
||||||
|
|
||||||
|
normal: name => name ? `${name}なでなで` : 'なでなで',
|
||||||
|
|
||||||
|
hate: 'ん~。がんばって',
|
||||||
|
},
|
||||||
|
|
||||||
|
kurusii: {
|
||||||
|
love: name => name ? `${name}なでなで ぽんぽんぎゅ~!` : 'なでなで ぽんぽんぎゅ~!',
|
||||||
|
|
||||||
|
normal: name => name ? `${name}なでなで` : 'なでなで',
|
||||||
|
|
||||||
|
hate: 'ん~。がんばって',
|
||||||
|
},
|
||||||
|
|
||||||
ote: {
|
ote: {
|
||||||
normal: 'くぅん... 私わんちゃんじゃないですよ...?',
|
normal: '犬じゃないんだが!!',
|
||||||
|
|
||||||
love1: 'わん!',
|
love1: 'にゃ~!ぼくは犬じゃないよぉ',
|
||||||
|
|
||||||
love2: 'わんわん♪',
|
love2: 'にゃにゃにゃ!',
|
||||||
},
|
},
|
||||||
|
|
||||||
shutdown: '私まだ眠くないですよ...?',
|
shutdown: 'ぼくまだ眠くない...',
|
||||||
|
|
||||||
transferNeedDm: 'わかりました、それはチャットで話しませんか?',
|
transferNeedDm: 'わかった!二人っきりでお話ししたいな',
|
||||||
|
|
||||||
transferCode: code => `わかりました。\n合言葉は「${code}」です!`,
|
transferCode: code => `わかった!\n合言葉は「${code}」だよ!`,
|
||||||
|
|
||||||
transferFailed: 'うーん、合言葉が間違ってませんか...?',
|
transferFailed: 'うーん、合言葉違うみたい',
|
||||||
|
|
||||||
transferDone: name => name ? `はっ...! おかえりなさい、${name}!` : `はっ...! おかえりなさい!`,
|
transferDone: name => name ? `んみゃ..! おかえり${name}!` : `んみゃ...! おかえりなさい!`,
|
||||||
},
|
},
|
||||||
|
|
||||||
keyword: {
|
keyword: {
|
||||||
learned: (word, reading) => `(${word}..... ${reading}..... 覚えました)`,
|
learned: (word, reading) => `え~っと...${word}...${reading}...僕覚えた!!!`,
|
||||||
|
|
||||||
remembered: (word) => `${word}`
|
remembered: (word) => `${word}`
|
||||||
},
|
},
|
||||||
|
|
||||||
dice: {
|
|
||||||
done: res => `${res} です!`
|
|
||||||
},
|
|
||||||
|
|
||||||
birthday: {
|
birthday: {
|
||||||
happyBirthday: name => name ? `お誕生日おめでとうございます、${name}🎉` : 'お誕生日おめでとうございます🎉',
|
happyBirthday: name => name ? `お誕生日おめでと~~~!!!${name}!!!!!!` : 'お誕生日おめでと~~~~~!!!',
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* リバーシ
|
|
||||||
*/
|
|
||||||
reversi: {
|
|
||||||
/**
|
|
||||||
* リバーシへの誘いを承諾するとき
|
|
||||||
*/
|
|
||||||
ok: '良いですよ~',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* リバーシへの誘いを断るとき
|
|
||||||
*/
|
|
||||||
decline: 'ごめんなさい、今リバーシはするなと言われてます...',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 対局開始
|
|
||||||
*/
|
|
||||||
started: (name, strength) => `対局を${name}と始めました! (強さ${strength})`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接待開始
|
|
||||||
*/
|
|
||||||
startedSettai: name => `(${name}の接待を始めました)`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 勝ったとき
|
|
||||||
*/
|
|
||||||
iWon: name => `${name}に勝ちました♪`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接待のつもりが勝ってしまったとき
|
|
||||||
*/
|
|
||||||
iWonButSettai: name => `(${name}に接待で勝っちゃいました...)`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 負けたとき
|
|
||||||
*/
|
|
||||||
iLose: name => `${name}に負けました...`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接待で負けてあげたとき
|
|
||||||
*/
|
|
||||||
iLoseButSettai: name => `(${name}に接待で負けてあげました...♪)`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 引き分けたとき
|
|
||||||
*/
|
|
||||||
drawn: name => `${name}と引き分けました~`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接待で引き分けたとき
|
|
||||||
*/
|
|
||||||
drawnSettai: name => `(${name}に接待で引き分けました...)`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 相手が投了したとき
|
|
||||||
*/
|
|
||||||
youSurrendered: name => `${name}が投了しちゃいました`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接待してたら相手が投了したとき
|
|
||||||
*/
|
|
||||||
settaiButYouSurrendered: name => `(${name}を接待していたら投了されちゃいました... ごめんなさい)`,
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数当てゲーム
|
|
||||||
*/
|
|
||||||
guessingGame: {
|
|
||||||
/**
|
|
||||||
* やろうと言われたけど既にやっているとき
|
|
||||||
*/
|
|
||||||
alreadyStarted: 'え、ゲームは既に始まってますよ!',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* タイムライン上で誘われたとき
|
|
||||||
*/
|
|
||||||
plzDm: 'メッセージでやりましょう!',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ゲーム開始
|
|
||||||
*/
|
|
||||||
started: '0~100の秘密の数を当ててみてください♪',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数字じゃない返信があったとき
|
|
||||||
*/
|
|
||||||
nan: '数字でお願いします!「やめる」と言ってゲームをやめることもできますよ!',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 中止を要求されたとき
|
|
||||||
*/
|
|
||||||
cancel: 'わかりました~。ありがとうございました♪',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小さい数を言われたとき
|
|
||||||
*/
|
|
||||||
grater: num => `${num}より大きいですね`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小さい数を言われたとき(2度目)
|
|
||||||
*/
|
|
||||||
graterAgain: num => `もう一度言いますが${num}より大きいですよ!`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 大きい数を言われたとき
|
|
||||||
*/
|
|
||||||
less: num => `${num}より小さいですね`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 大きい数を言われたとき(2度目)
|
|
||||||
*/
|
|
||||||
lessAgain: num => `もう一度言いますが${num}より小さいですよ!`,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 正解したとき
|
|
||||||
*/
|
|
||||||
congrats: tries => `正解です🎉 (${tries}回目で当てました)`,
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数取りゲーム
|
|
||||||
*/
|
|
||||||
kazutori: {
|
|
||||||
alreadyStarted: '今ちょうどやってますよ~',
|
|
||||||
|
|
||||||
matakondo: 'また今度やりましょう!',
|
|
||||||
|
|
||||||
intro: minutes => `みなさん、数取りゲームしましょう!\n0~100の中で最も大きい数字を取った人が勝ちです。他の人と被ったらだめですよ~\n制限時間は${minutes}分です。数字はこの投稿にリプライで送ってくださいね!`,
|
|
||||||
|
|
||||||
finish: 'ゲームの結果発表です!',
|
|
||||||
|
|
||||||
finishWithWinner: (user, name) => name ? `今回は${user}さん(${name})の勝ちです!またやりましょう♪` : `今回は${user}さんの勝ちです!またやりましょう♪`,
|
|
||||||
|
|
||||||
finishWithNoWinner: '今回は勝者はいませんでした... またやりましょう♪',
|
|
||||||
|
|
||||||
onagare: '参加者が集まらなかったのでお流れになりました...'
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 絵文字生成
|
|
||||||
*/
|
|
||||||
emoji: {
|
|
||||||
suggest: emoji => `こんなのはどうですか?→${emoji}`,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 占い
|
* 占い
|
||||||
*/
|
*/
|
||||||
fortune: {
|
fortune: {
|
||||||
cw: name => name ? `私が今日の${name}の運勢を占いました...` : '私が今日のあなたの運勢を占いました...',
|
cw: name => name ? `今日の${name}の運勢を占ったよ!` : '今日のきみの運勢を占ったよ!',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* タイマー
|
* タイマー
|
||||||
*/
|
*/
|
||||||
timer: {
|
timer: {
|
||||||
set: 'わかりました!',
|
set: 'OK!',
|
||||||
|
|
||||||
invalid: 'うーん...?',
|
invalid: 'うむむ?',
|
||||||
|
|
||||||
tooLong: '長すぎます…',
|
tooLong: '長すぎる…',
|
||||||
|
|
||||||
notify: (time, name) => name ? `${name}、${time}経ちましたよ!` : `${time}経ちましたよ!`
|
notify: (time, name) => name ? `${name}!!${time}経ったよ!` : `${time}経ったよ!`
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* リマインダー
|
* リマインダー
|
||||||
*/
|
*/
|
||||||
reminder: {
|
reminder: {
|
||||||
invalid: 'うーん...?',
|
invalid: 'うむむ?',
|
||||||
|
|
||||||
doneFromInvalidUser: 'イタズラはめっですよ!',
|
reminds: 'やること一覧だよ!',
|
||||||
|
none: 'やることはないよ!',
|
||||||
|
|
||||||
reminds: 'やること一覧です!',
|
notify: (name) => name ? `${name}これやった?` : `これやった?`,
|
||||||
|
|
||||||
notify: (name) => name ? `${name}、これやりましたか?` : `これやりましたか?`,
|
notifyWithThing: (thing, name) => name ? `${name}「${thing}」やった?` : `「${thing}」やった?`,
|
||||||
|
|
||||||
notifyWithThing: (thing, name) => name ? `${name}、「${thing}」やりましたか?` : `「${thing}」やりましたか?`,
|
|
||||||
|
|
||||||
done: (name) => name ? [
|
done: (name) => name ? [
|
||||||
`よく出来ました、${name}♪`,
|
`すごい!!天才!!${name}えらい!!`,
|
||||||
`${name}、さすがですっ!`,
|
`${name}さすがすぎる!!!`,
|
||||||
`${name}、えらすぎます...!`,
|
`${name}えらすぎる!!`,
|
||||||
] : [
|
] : [
|
||||||
`よく出来ました♪`,
|
`すごい!!天才!!えらい!!`,
|
||||||
`さすがですっ!`,
|
`さすがすぎる!!!`,
|
||||||
`えらすぎます...!`,
|
`えらすぎる!!`,
|
||||||
],
|
],
|
||||||
|
|
||||||
cancel: `わかりました。`,
|
cancel: `OK!`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
cpu: 'サーバーざぁこ♡♡♡'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ろぐぼ
|
||||||
|
*/
|
||||||
|
rogubo: 'ログボ!!',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* バレンタイン
|
* バレンタイン
|
||||||
*/
|
*/
|
||||||
valentine: {
|
valentine: {
|
||||||
chocolateForYou: name => name ? `${name}、その... チョコレート作ったのでよかったらどうぞ!🍫` : 'チョコレート作ったのでよかったらどうぞ!🍫',
|
chocolateForYou: name => name ? `${name}!チョコあげる!` : 'チョコあげる!',
|
||||||
},
|
|
||||||
|
|
||||||
server: {
|
|
||||||
cpu: 'サーバーの負荷が高そうです。大丈夫でしょうか...?'
|
|
||||||
},
|
|
||||||
|
|
||||||
maze: {
|
|
||||||
post: '今日の迷路です! #AiMaze',
|
|
||||||
foryou: '描きました!'
|
|
||||||
},
|
|
||||||
|
|
||||||
chart: {
|
|
||||||
post: 'インスタンスの投稿数です!',
|
|
||||||
foryou: '描きました!'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sleepReport: {
|
sleepReport: {
|
||||||
report: hours => `んぅ、${hours}時間くらい寝ちゃってたみたいです`,
|
report: hours => `んぬぁ~、${hours}時間くらいねちゃってたかも`,
|
||||||
reportUtatane: 'ん... うたた寝しちゃってました',
|
reportUtatane: 'ぬぁ... ',
|
||||||
},
|
},
|
||||||
|
|
||||||
noting: {
|
noting: {
|
||||||
notes: [
|
notes: [
|
||||||
'ゴロゴロ…',
|
'うみゅ',
|
||||||
'ちょっと眠いです',
|
'んぬぁ~',
|
||||||
'いいですよ?',
|
'ねむい',
|
||||||
'(。´・ω・)?',
|
'さみしい',
|
||||||
'ふぇー',
|
'なでてぇ',
|
||||||
'あれ…これをこうして…あれー?',
|
'なんもわからん',
|
||||||
'ぼー…',
|
'う~~~',
|
||||||
'ふぅ…疲れました',
|
'ねみゅい',
|
||||||
'お味噌汁、作りましょうか?',
|
'つらいニダ',
|
||||||
'ご飯にしますか?お風呂にしますか?',
|
'うが~~~',
|
||||||
'ふえええええ!?',
|
'疲れた',
|
||||||
'私のサイトに、私のイラストがたくさんあって嬉しいです!',
|
'みゃ~',
|
||||||
'みすきーって、かわいい名前ですよね!',
|
'うぅ',
|
||||||
'うぅ、リバーシ難しいなぁ…',
|
'ぬるきゃっとちゃんだよ!',
|
||||||
'失敗しても、次に活かせたらプラスですよね!',
|
'進捗どうですか',
|
||||||
'なんだか、おなか空いちゃいました',
|
'おふとんふわふわ~',
|
||||||
'お掃除は、定期的にしないとダメですよー?',
|
'うぐぅ',
|
||||||
'今日もお勤めご苦労様です! 私も頑張ります♪',
|
'ぬぁ~ん',
|
||||||
'えっと、何しようとしてたんだっけ…?',
|
'に゙',
|
||||||
'おうちがいちばん、落ち着きます…',
|
'ぎゅ~~~',
|
||||||
'疲れたら、私がなでなでってしてあげます♪',
|
'むぅ',
|
||||||
'離れていても、心はそばにいます♪',
|
|
||||||
'藍ですよ〜',
|
|
||||||
'わんちゃん可愛いです',
|
|
||||||
'ぷろぐらむ?',
|
|
||||||
'ごろーん…',
|
|
||||||
'なにもしていないのに、パソコンが壊れちゃいました…',
|
|
||||||
'Have a nice day♪',
|
|
||||||
'お布団に食べられちゃってます',
|
|
||||||
'寝ながら見てます',
|
|
||||||
'念力で操作してます',
|
|
||||||
'仮想空間から投稿してます',
|
|
||||||
'今日はMisskey本部に来てます!',
|
|
||||||
'Misskey本部は、Z地区の第三セクターにあります',
|
|
||||||
'Misskey本部には、さーばーっていう機械がいっぱいあります',
|
|
||||||
'しっぽはないですよ?',
|
|
||||||
'ひゃっ…!\nネコミミ触られると、くすぐったいです',
|
|
||||||
'抗逆コンパイル性って、なにかな?',
|
|
||||||
'Misskeyの制服、かわいくて好きです♪',
|
|
||||||
'ふわぁ、おふとん気持ちいいです...',
|
|
||||||
'メイド服、似合うかな?',
|
|
||||||
'挨拶ができる人間は開発もできる!…って、syuiloさんが言ってました',
|
|
||||||
'ふえぇ、ご主人様どこ見てるんですか?',
|
|
||||||
'私を覗くとき、私もまたご主人様を覗いています',
|
|
||||||
'はい、ママですよ〜',
|
|
||||||
'くぅ~ん...',
|
|
||||||
'All your note are belong to me!',
|
|
||||||
'せっかくだから、私はこの赤の扉を選びます!',
|
|
||||||
'よしっ',
|
|
||||||
'( ˘ω˘)スヤァ',
|
|
||||||
'(`・ω・´)シャキーン',
|
|
||||||
'失礼、かみまみた',
|
|
||||||
'おはようからおやすみまで、あなたの藍ですよ〜',
|
|
||||||
'Misskey開発者の朝は遅いらしいです',
|
|
||||||
'の、のじゃ...',
|
|
||||||
'にゃんにゃんお!',
|
|
||||||
'上から来ます!気をつけてください!',
|
|
||||||
'ふわぁ...',
|
|
||||||
'あぅ',
|
|
||||||
'ふみゃ〜',
|
|
||||||
'ふぁ… ねむねむですー',
|
|
||||||
'ヾ(๑╹◡╹)ノ"',
|
|
||||||
'私の"インスタンス"を周囲に展開して分身するのが特技です!\n人数分のエネルギー消費があるので、4人くらいが限界ですけど',
|
|
||||||
'うとうと...',
|
|
||||||
'ふわー、メモリが五臓六腑に染み渡ります…',
|
|
||||||
'i pwned you!',
|
|
||||||
'ひょこっ',
|
|
||||||
'にゃん♪',
|
|
||||||
'(*>ω<*)',
|
|
||||||
'にこー♪',
|
|
||||||
'ぷくー',
|
|
||||||
'にゃふぅ',
|
|
||||||
'藍が来ましたよ~',
|
|
||||||
'じー',
|
|
||||||
'はにゃ?',
|
|
||||||
],
|
],
|
||||||
want: item => `${item}、欲しいなぁ...`,
|
|
||||||
see: item => `お散歩していたら、道に${item}が落ちているのを見たんです!`,
|
|
||||||
expire: item => `気づいたら、${item}の賞味期限が切れてました…`,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -477,3 +280,4 @@ export function getSerif(variant: string | string[]): string {
|
|||||||
return variant;
|
return variant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
249
src/stream.ts
249
src/stream.ts
@ -1,63 +1,63 @@
|
|||||||
import autobind from 'autobind-decorator';
|
import autobind from "autobind-decorator"
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from "events"
|
||||||
import * as WebSocket from 'ws';
|
import * as WebSocket from "ws"
|
||||||
const ReconnectingWebsocket = require('reconnecting-websocket');
|
import config from "./config"
|
||||||
import config from './config';
|
const ReconnectingWebsocket = require("reconnecting-websocket")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Misskey stream connection
|
* Misskey stream connection
|
||||||
*/
|
*/
|
||||||
export default class Stream extends EventEmitter {
|
export default class Stream extends EventEmitter {
|
||||||
private stream: any;
|
private stream: any
|
||||||
private state: string;
|
private state: string
|
||||||
private buffer: any[];
|
private buffer: any[]
|
||||||
private sharedConnectionPools: Pool[] = [];
|
private sharedConnectionPools: Pool[] = []
|
||||||
private sharedConnections: SharedConnection[] = [];
|
private sharedConnections: SharedConnection[] = []
|
||||||
private nonSharedConnections: NonSharedConnection[] = [];
|
private nonSharedConnections: NonSharedConnection[] = []
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super()
|
||||||
|
|
||||||
this.state = 'initializing';
|
this.state = "initializing"
|
||||||
this.buffer = [];
|
this.buffer = []
|
||||||
|
|
||||||
this.stream = new ReconnectingWebsocket(`${config.wsUrl}/streaming?i=${config.i}`, [], {
|
this.stream = new ReconnectingWebsocket(`${config.wsUrl}/streaming?i=${config.i}`, [], {
|
||||||
WebSocket: WebSocket
|
WebSocket: WebSocket,
|
||||||
});
|
})
|
||||||
this.stream.addEventListener('open', this.onOpen);
|
this.stream.addEventListener("open", this.onOpen)
|
||||||
this.stream.addEventListener('close', this.onClose);
|
this.stream.addEventListener("close", this.onClose)
|
||||||
this.stream.addEventListener('message', this.onMessage);
|
this.stream.addEventListener("message", this.onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public useSharedConnection(channel: string): SharedConnection {
|
public useSharedConnection(channel: string): SharedConnection {
|
||||||
let pool = this.sharedConnectionPools.find(p => p.channel === channel);
|
let pool = this.sharedConnectionPools.find((p) => p.channel === channel)
|
||||||
|
|
||||||
if (pool == null) {
|
if (pool == null) {
|
||||||
pool = new Pool(this, channel);
|
pool = new Pool(this, channel)
|
||||||
this.sharedConnectionPools.push(pool);
|
this.sharedConnectionPools.push(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = new SharedConnection(this, channel, pool);
|
const connection = new SharedConnection(this, channel, pool)
|
||||||
this.sharedConnections.push(connection);
|
this.sharedConnections.push(connection)
|
||||||
return connection;
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public removeSharedConnection(connection: SharedConnection) {
|
public removeSharedConnection(connection: SharedConnection) {
|
||||||
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
|
this.sharedConnections = this.sharedConnections.filter((c) => c !== connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
||||||
const connection = new NonSharedConnection(this, channel, params);
|
const connection = new NonSharedConnection(this, channel, params)
|
||||||
this.nonSharedConnections.push(connection);
|
this.nonSharedConnections.push(connection)
|
||||||
return connection;
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public disconnectToChannel(connection: NonSharedConnection) {
|
public disconnectToChannel(connection: NonSharedConnection) {
|
||||||
this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection);
|
this.nonSharedConnections = this.nonSharedConnections.filter((c) => c !== connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,26 +65,26 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onOpen() {
|
private onOpen() {
|
||||||
const isReconnect = this.state == 'reconnecting';
|
const isReconnect = this.state == "reconnecting"
|
||||||
|
|
||||||
this.state = 'connected';
|
this.state = "connected"
|
||||||
this.emit('_connected_');
|
this.emit("_connected_")
|
||||||
|
|
||||||
// バッファーを処理
|
// バッファーを処理
|
||||||
const _buffer = [...this.buffer]; // Shallow copy
|
const _buffer = [...this.buffer] // Shallow copy
|
||||||
this.buffer = []; // Clear buffer
|
this.buffer = [] // Clear buffer
|
||||||
for (const data of _buffer) {
|
for (const data of _buffer) {
|
||||||
this.send(data); // Resend each buffered messages
|
this.send(data) // Resend each buffered messages
|
||||||
}
|
}
|
||||||
|
|
||||||
// チャンネル再接続
|
// チャンネル再接続
|
||||||
if (isReconnect) {
|
if (isReconnect) {
|
||||||
this.sharedConnectionPools.forEach(p => {
|
this.sharedConnectionPools.forEach((p) => {
|
||||||
p.connect();
|
p.connect()
|
||||||
});
|
})
|
||||||
this.nonSharedConnections.forEach(c => {
|
this.nonSharedConnections.forEach((c) => {
|
||||||
c.connect();
|
c.connect()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,8 +93,8 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onClose() {
|
private onClose() {
|
||||||
this.state = 'reconnecting';
|
this.state = "reconnecting"
|
||||||
this.emit('_disconnected_');
|
this.emit("_disconnected_")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,26 +102,26 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onMessage(message) {
|
private onMessage(message) {
|
||||||
const { type, body } = JSON.parse(message.data);
|
const { type, body } = JSON.parse(message.data)
|
||||||
|
|
||||||
if (type == 'channel') {
|
if (type == "channel") {
|
||||||
const id = body.id;
|
const id = body.id
|
||||||
|
|
||||||
let connections: (Connection | undefined)[];
|
let connections: (Connection | undefined)[]
|
||||||
|
|
||||||
connections = this.sharedConnections.filter(c => c.id === id);
|
connections = this.sharedConnections.filter((c) => c.id === id)
|
||||||
|
|
||||||
if (connections.length === 0) {
|
if (connections.length === 0) {
|
||||||
connections = [this.nonSharedConnections.find(c => c.id === id)];
|
connections = [this.nonSharedConnections.find((c) => c.id === id)]
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const c of connections.filter(c => c != null)) {
|
for (const c of connections.filter((c) => c != null)) {
|
||||||
c!.emit(body.type, body.body);
|
c!.emit(body.type, body.body)
|
||||||
c!.emit('*', { type: body.type, body: body.body });
|
c!.emit("*", { type: body.type, body: body.body })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.emit(type, body);
|
this.emit(type, body)
|
||||||
this.emit('*', { type, body });
|
this.emit("*", { type, body })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,18 +130,21 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
public send(typeOrPayload, payload?) {
|
public send(typeOrPayload, payload?) {
|
||||||
const data = payload === undefined ? typeOrPayload : {
|
const data =
|
||||||
|
payload === undefined
|
||||||
|
? typeOrPayload
|
||||||
|
: {
|
||||||
type: typeOrPayload,
|
type: typeOrPayload,
|
||||||
body: payload
|
body: payload,
|
||||||
};
|
|
||||||
|
|
||||||
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
|
||||||
if (this.state != 'connected') {
|
|
||||||
this.buffer.push(data);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stream.send(JSON.stringify(data));
|
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
||||||
|
if (this.state != "connected") {
|
||||||
|
this.buffer.push(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stream.send(JSON.stringify(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,157 +152,157 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
public close() {
|
public close() {
|
||||||
this.stream.removeEventListener('open', this.onOpen);
|
this.stream.removeEventListener("open", this.onOpen)
|
||||||
this.stream.removeEventListener('message', this.onMessage);
|
this.stream.removeEventListener("message", this.onMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Pool {
|
class Pool {
|
||||||
public channel: string;
|
public channel: string
|
||||||
public id: string;
|
public id: string
|
||||||
protected stream: Stream;
|
protected stream: Stream
|
||||||
private users = 0;
|
private users = 0
|
||||||
private disposeTimerId: any;
|
private disposeTimerId: any
|
||||||
private isConnected = false;
|
private isConnected = false
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string) {
|
constructor(stream: Stream, channel: string) {
|
||||||
this.channel = channel;
|
this.channel = channel
|
||||||
this.stream = stream;
|
this.stream = stream
|
||||||
|
|
||||||
this.id = Math.random().toString();
|
this.id = Math.random().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public inc() {
|
public inc() {
|
||||||
if (this.users === 0 && !this.isConnected) {
|
if (this.users === 0 && !this.isConnected) {
|
||||||
this.connect();
|
this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.users++;
|
this.users++
|
||||||
|
|
||||||
// タイマー解除
|
// タイマー解除
|
||||||
if (this.disposeTimerId) {
|
if (this.disposeTimerId) {
|
||||||
clearTimeout(this.disposeTimerId);
|
clearTimeout(this.disposeTimerId)
|
||||||
this.disposeTimerId = null;
|
this.disposeTimerId = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public dec() {
|
public dec() {
|
||||||
this.users--;
|
this.users--
|
||||||
|
|
||||||
// そのコネクションの利用者が誰もいなくなったら
|
// そのコネクションの利用者が誰もいなくなったら
|
||||||
if (this.users === 0) {
|
if (this.users === 0) {
|
||||||
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
||||||
// 新たな利用者が現れなければコネクションを切断する
|
// 新たな利用者が現れなければコネクションを切断する
|
||||||
this.disposeTimerId = setTimeout(() => {
|
this.disposeTimerId = setTimeout(() => {
|
||||||
this.disconnect();
|
this.disconnect()
|
||||||
}, 3000);
|
}, 3000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public connect() {
|
public connect() {
|
||||||
this.isConnected = true;
|
this.isConnected = true
|
||||||
this.stream.send('connect', {
|
this.stream.send("connect", {
|
||||||
channel: this.channel,
|
channel: this.channel,
|
||||||
id: this.id
|
id: this.id,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private disconnect() {
|
private disconnect() {
|
||||||
this.isConnected = false;
|
this.isConnected = false
|
||||||
this.disposeTimerId = null;
|
this.disposeTimerId = null
|
||||||
this.stream.send('disconnect', { id: this.id });
|
this.stream.send("disconnect", { id: this.id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Connection extends EventEmitter {
|
abstract class Connection extends EventEmitter {
|
||||||
public channel: string;
|
public channel: string
|
||||||
protected stream: Stream;
|
protected stream: Stream
|
||||||
public abstract id: string;
|
public abstract id: string
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string) {
|
constructor(stream: Stream, channel: string) {
|
||||||
super();
|
super()
|
||||||
|
|
||||||
this.stream = stream;
|
this.stream = stream
|
||||||
this.channel = channel;
|
this.channel = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public send(id: string, typeOrPayload, payload?) {
|
public send(id: string, typeOrPayload, payload?) {
|
||||||
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
|
const type = payload === undefined ? typeOrPayload.type : typeOrPayload
|
||||||
const body = payload === undefined ? typeOrPayload.body : payload;
|
const body = payload === undefined ? typeOrPayload.body : payload
|
||||||
|
|
||||||
this.stream.send('ch', {
|
this.stream.send("ch", {
|
||||||
id: id,
|
id: id,
|
||||||
type: type,
|
type: type,
|
||||||
body: body
|
body: body,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract dispose(): void;
|
public abstract dispose(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
class SharedConnection extends Connection {
|
class SharedConnection extends Connection {
|
||||||
private pool: Pool;
|
private pool: Pool
|
||||||
|
|
||||||
public get id(): string {
|
public get id(): string {
|
||||||
return this.pool.id;
|
return this.pool.id
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string, pool: Pool) {
|
constructor(stream: Stream, channel: string, pool: Pool) {
|
||||||
super(stream, channel);
|
super(stream, channel)
|
||||||
|
|
||||||
this.pool = pool;
|
this.pool = pool
|
||||||
this.pool.inc();
|
this.pool.inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public send(typeOrPayload, payload?) {
|
public send(typeOrPayload, payload?) {
|
||||||
super.send(this.pool.id, typeOrPayload, payload);
|
super.send(this.pool.id, typeOrPayload, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this.pool.dec();
|
this.pool.dec()
|
||||||
this.removeAllListeners();
|
this.removeAllListeners()
|
||||||
this.stream.removeSharedConnection(this);
|
this.stream.removeSharedConnection(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NonSharedConnection extends Connection {
|
class NonSharedConnection extends Connection {
|
||||||
public id: string;
|
public id: string
|
||||||
protected params: any;
|
protected params: any
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string, params?: any) {
|
constructor(stream: Stream, channel: string, params?: any) {
|
||||||
super(stream, channel);
|
super(stream, channel)
|
||||||
|
|
||||||
this.params = params;
|
this.params = params
|
||||||
this.id = Math.random().toString();
|
this.id = Math.random().toString()
|
||||||
|
|
||||||
this.connect();
|
this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public connect() {
|
public connect() {
|
||||||
this.stream.send('connect', {
|
this.stream.send("connect", {
|
||||||
channel: this.channel,
|
channel: this.channel,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
params: this.params
|
params: this.params,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public send(typeOrPayload, payload?) {
|
public send(typeOrPayload, payload?) {
|
||||||
super.send(this.id, typeOrPayload, payload);
|
super.send(this.id, typeOrPayload, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this.removeAllListeners();
|
this.removeAllListeners()
|
||||||
this.stream.send('disconnect', { id: this.id });
|
this.stream.send("disconnect", { id: this.id })
|
||||||
this.stream.disconnectToChannel(this);
|
this.stream.disconnectToChannel(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,7 @@
|
|||||||
import * as seedrandom from 'seedrandom';
|
import * as seedrandom from 'seedrandom';
|
||||||
|
|
||||||
export const itemPrefixes = [
|
export const itemPrefixes = [
|
||||||
'プラチナ製',
|
|
||||||
'新鮮な',
|
|
||||||
'最新式の',
|
|
||||||
'古代の',
|
|
||||||
'手作り',
|
|
||||||
'時計じかけの',
|
|
||||||
'伝説の',
|
|
||||||
'焼き',
|
|
||||||
'生の',
|
|
||||||
'藍謹製',
|
|
||||||
'ポケットサイズ',
|
|
||||||
'3日前の',
|
|
||||||
'そこらへんの',
|
'そこらへんの',
|
||||||
'偽の',
|
|
||||||
'使用済み',
|
'使用済み',
|
||||||
'壊れた',
|
'壊れた',
|
||||||
'市販の',
|
'市販の',
|
||||||
@ -22,52 +9,24 @@ export const itemPrefixes = [
|
|||||||
'業務用の',
|
'業務用の',
|
||||||
'Microsoft製',
|
'Microsoft製',
|
||||||
'Apple製',
|
'Apple製',
|
||||||
'人類の技術を結集して作った',
|
|
||||||
'2018年製', // TODO ランダム
|
|
||||||
'500kgくらいある',
|
|
||||||
'高級',
|
'高級',
|
||||||
'腐った',
|
'腐った',
|
||||||
'人工知能搭載',
|
'人工知能搭載',
|
||||||
'反重力',
|
|
||||||
'折り畳み式',
|
|
||||||
'携帯型',
|
'携帯型',
|
||||||
'遺伝子組み換え',
|
|
||||||
'飛行能力を獲得した',
|
|
||||||
'純金製',
|
|
||||||
'透明な',
|
'透明な',
|
||||||
'光る',
|
'光る',
|
||||||
'ハート型の',
|
|
||||||
'動く',
|
'動く',
|
||||||
'半分にカットされた',
|
|
||||||
'USBコネクタ付きの',
|
'USBコネクタ付きの',
|
||||||
'いにしえの',
|
'いにしえの',
|
||||||
'呪われた',
|
'呪われた',
|
||||||
'エンチャントされた',
|
|
||||||
'一日分のビタミンが入った',
|
|
||||||
'かじりかけ',
|
|
||||||
'幻の',
|
'幻の',
|
||||||
'仮想的な',
|
'仮想的な',
|
||||||
'原子力',
|
|
||||||
'高度に訓練された',
|
|
||||||
'遺伝子組み換えでない',
|
|
||||||
'ダンジョン最深部で見つかった',
|
|
||||||
'異世界の',
|
'異世界の',
|
||||||
'異星の',
|
'異星の',
|
||||||
'謎の',
|
'謎の',
|
||||||
'時空を歪める',
|
'時空を歪める',
|
||||||
'異音がする',
|
|
||||||
'霧散する',
|
|
||||||
'プラズマ化した',
|
|
||||||
'衝撃を与えると低確率で爆発する',
|
|
||||||
'ズッキーニに擬態した',
|
|
||||||
'仮説上の',
|
|
||||||
'毒の',
|
|
||||||
'真の',
|
|
||||||
'究極の',
|
'究極の',
|
||||||
'チョコ入り',
|
|
||||||
'異臭を放つ',
|
'異臭を放つ',
|
||||||
'4次元',
|
|
||||||
'脈動する',
|
|
||||||
'得体の知れない',
|
'得体の知れない',
|
||||||
'四角い',
|
'四角い',
|
||||||
'暴れ回る',
|
'暴れ回る',
|
||||||
@ -75,70 +34,27 @@ export const itemPrefixes = [
|
|||||||
'闇の',
|
'闇の',
|
||||||
'暗黒の',
|
'暗黒の',
|
||||||
'封印されし',
|
'封印されし',
|
||||||
'死の',
|
|
||||||
'凍った',
|
'凍った',
|
||||||
'魔の',
|
'魔の',
|
||||||
'禁断の',
|
'禁断の',
|
||||||
'ホログラフィックな',
|
'ホログラフィックな',
|
||||||
'油圧式',
|
|
||||||
'辛そうで辛くない少し辛い',
|
|
||||||
'焦げた',
|
|
||||||
'宇宙',
|
|
||||||
'電子',
|
|
||||||
'陽電子',
|
|
||||||
'量子力学的',
|
|
||||||
'シュレディンガーの',
|
|
||||||
'分散型',
|
|
||||||
'卵かけ',
|
|
||||||
'次世代',
|
'次世代',
|
||||||
'帯電',
|
'3G対応',
|
||||||
'太古の',
|
'消費期限切れ',
|
||||||
'WiFi対応',
|
|
||||||
'高反発',
|
|
||||||
'【令和最新版】',
|
|
||||||
'廉価版',
|
|
||||||
'ねばねば',
|
|
||||||
'どろどろ',
|
|
||||||
'パサパサの',
|
|
||||||
'湿気った',
|
|
||||||
'賞味期限切れ',
|
|
||||||
'地獄から来た',
|
|
||||||
'ニンニクマシ',
|
|
||||||
'放射性',
|
|
||||||
'フラクタルな',
|
|
||||||
'再帰的',
|
|
||||||
'ときどき分裂する',
|
|
||||||
'消える',
|
'消える',
|
||||||
'等速直線運動する',
|
|
||||||
'X線照射',
|
|
||||||
'蠢く',
|
|
||||||
'形而上学的',
|
|
||||||
'もちもち',
|
'もちもち',
|
||||||
'冷やし',
|
'冷やし',
|
||||||
'あつあつ',
|
'あつあつ',
|
||||||
'巨大',
|
'巨大',
|
||||||
'ナノサイズ',
|
'ナノサイズ',
|
||||||
'やわらかい',
|
'やわらかい',
|
||||||
|
'人の手に負えない',
|
||||||
'バグった',
|
'バグった',
|
||||||
'人工',
|
'人工',
|
||||||
'天然',
|
'天然',
|
||||||
'祀られた',
|
|
||||||
'チョコレートコーティング',
|
|
||||||
'抗菌仕様',
|
|
||||||
'耐火',
|
|
||||||
'激',
|
|
||||||
'猛',
|
|
||||||
'超',
|
'超',
|
||||||
'群生する',
|
|
||||||
'軽量',
|
|
||||||
'国宝級',
|
|
||||||
'流行りの',
|
|
||||||
'8カラットの',
|
|
||||||
'中古の',
|
'中古の',
|
||||||
'新品の',
|
'新品の',
|
||||||
'愛妻',
|
|
||||||
'ブランドものの',
|
|
||||||
'増殖する',
|
|
||||||
'ぷるぷる',
|
'ぷるぷる',
|
||||||
'ぐにゃぐにゃ',
|
'ぐにゃぐにゃ',
|
||||||
'多目的',
|
'多目的',
|
||||||
@ -146,315 +62,54 @@ export const itemPrefixes = [
|
|||||||
'激辛',
|
'激辛',
|
||||||
'先進的な',
|
'先進的な',
|
||||||
'レトロな',
|
'レトロな',
|
||||||
'ヴィンテージ',
|
|
||||||
'合法',
|
'合法',
|
||||||
|
'違法',
|
||||||
'プレミア付き',
|
'プレミア付き',
|
||||||
'デカ',
|
|
||||||
'ギガ',
|
|
||||||
'穢れた',
|
|
||||||
'品質保証付き',
|
|
||||||
'AppleCare+加入済み',
|
|
||||||
'えっちな',
|
|
||||||
'デザイナーズ',
|
|
||||||
'蠱惑的な',
|
|
||||||
'霊験灼かな',
|
|
||||||
'つやつや',
|
|
||||||
'べとべと',
|
|
||||||
'ムキムキ',
|
|
||||||
'オーバークロックされた',
|
|
||||||
'無機質な',
|
|
||||||
'前衛的な',
|
|
||||||
'怪しい',
|
'怪しい',
|
||||||
'妖しい',
|
'妖しい',
|
||||||
'カビの生えた',
|
|
||||||
'熟成',
|
|
||||||
'アルミダイキャスト',
|
|
||||||
'養殖',
|
|
||||||
'やばい',
|
'やばい',
|
||||||
'すごい',
|
'すごい',
|
||||||
'かわいい',
|
'かわいい',
|
||||||
'デジタル',
|
'デジタル',
|
||||||
'アナログ',
|
'アナログ',
|
||||||
'彁な',
|
|
||||||
'カラフルな',
|
|
||||||
'電動',
|
|
||||||
'当たり判定のない',
|
|
||||||
'めり込んだ',
|
|
||||||
'100年に一度の',
|
'100年に一度の',
|
||||||
'ジューシーな',
|
|
||||||
'Hi-Res',
|
|
||||||
'確変',
|
|
||||||
'食用',
|
'食用',
|
||||||
'THE ',
|
'THE ',
|
||||||
'某',
|
|
||||||
'朽ちゆく',
|
|
||||||
'滅びの',
|
|
||||||
'反発係数がe>1の',
|
|
||||||
'摩擦係数0の',
|
|
||||||
'解き放たれし',
|
'解き放たれし',
|
||||||
'大きな',
|
'大きな',
|
||||||
'小さな',
|
'小さな',
|
||||||
'強欲な',
|
|
||||||
'うねうね',
|
|
||||||
'水没',
|
|
||||||
'燃え盛る',
|
|
||||||
'高圧',
|
|
||||||
'異常',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const items = [
|
export const items = [
|
||||||
'ナス',
|
'右足',
|
||||||
'トマト',
|
'左足',
|
||||||
'きゅうり',
|
'お金',
|
||||||
'じゃがいも',
|
'金パブ',
|
||||||
'焼きビーフン',
|
'ブロン',
|
||||||
'腰',
|
'ぬるきゃっとちゃん!',
|
||||||
'寿司',
|
'この世のすべて',
|
||||||
'かぼちゃ',
|
|
||||||
'諭吉',
|
|
||||||
'キロバー',
|
|
||||||
'アルミニウム',
|
|
||||||
'ナトリウム',
|
|
||||||
'マグネシウム',
|
|
||||||
'プルトニウム',
|
|
||||||
'ちいさなメダル',
|
|
||||||
'牛乳パック',
|
|
||||||
'ペットボトル',
|
|
||||||
'クッキー',
|
|
||||||
'チョコレート',
|
|
||||||
'メイド服',
|
|
||||||
'オレンジ',
|
|
||||||
'ニーソ',
|
|
||||||
'反物質コンデンサ',
|
|
||||||
'粒子加速器',
|
|
||||||
'マイクロプロセッサ(4コア8スレッド)',
|
|
||||||
'原子力発電所',
|
|
||||||
'レイヤ4スイッチ',
|
|
||||||
'緩衝チェーン',
|
|
||||||
'陽電子頭脳',
|
|
||||||
'惑星',
|
|
||||||
'テルミン',
|
|
||||||
'虫歯車',
|
|
||||||
'マウンター',
|
|
||||||
'バケットホイールエクスカベーター',
|
|
||||||
'デーモンコア',
|
|
||||||
'ゲームボーイアドバンス',
|
|
||||||
'量子コンピューター',
|
'量子コンピューター',
|
||||||
'アナモルフィックレンズ',
|
'スマホ',
|
||||||
'押し入れの奥から出てきた謎の生き物',
|
'PC',
|
||||||
'スマートフォン',
|
'モンスター',
|
||||||
'時計',
|
'好きなもの',
|
||||||
'プリン',
|
|
||||||
'ガブリエルのラッパ',
|
|
||||||
'メンガーのスポンジ',
|
|
||||||
'ハンドスピナー',
|
|
||||||
'超立方体',
|
|
||||||
'建築物',
|
|
||||||
'エナジードリンク',
|
|
||||||
'マウスカーソル',
|
|
||||||
'メガネ',
|
|
||||||
'まぐろ',
|
|
||||||
'ゴミ箱',
|
|
||||||
'つまようじ',
|
|
||||||
'お弁当に入ってる緑の仕切りみたいなやつ',
|
|
||||||
'割りばし',
|
|
||||||
'換気扇',
|
|
||||||
'ペットボトルのキャップ',
|
|
||||||
'消波ブロック',
|
|
||||||
'ピザ',
|
|
||||||
'歯磨き粉',
|
|
||||||
'空き缶',
|
|
||||||
'キーホルダー',
|
|
||||||
'金髪碧眼の美少女',
|
|
||||||
'SDカード',
|
|
||||||
'リップクリーム',
|
|
||||||
'チョコ無しチョココロネ',
|
|
||||||
'鳥インフルエンザ',
|
|
||||||
'自動販売機',
|
|
||||||
'重いもの',
|
|
||||||
'ノートパソコン',
|
|
||||||
'ビーフジャーキー',
|
|
||||||
'さけるチーズ',
|
|
||||||
'ダイヤモンド',
|
|
||||||
'物体',
|
|
||||||
'月の石',
|
|
||||||
'特異点',
|
|
||||||
'中性子星',
|
|
||||||
'液体',
|
|
||||||
'衛星',
|
|
||||||
'ズッキーニ',
|
|
||||||
'黒いもの',
|
|
||||||
'白いもの',
|
|
||||||
'赤いもの',
|
|
||||||
'丸いもの',
|
|
||||||
'四角いもの',
|
|
||||||
'カード状のもの',
|
|
||||||
'気体',
|
|
||||||
'鉛筆',
|
|
||||||
'消しゴム',
|
|
||||||
'つるぎ',
|
|
||||||
'棒状のもの',
|
|
||||||
'農産物',
|
|
||||||
'メタルスライム',
|
|
||||||
'タコの足',
|
|
||||||
'きのこ',
|
|
||||||
'なめこ',
|
|
||||||
'缶チューハイ',
|
|
||||||
'爪切り',
|
|
||||||
'耳かき',
|
|
||||||
'ぬいぐるみ',
|
'ぬいぐるみ',
|
||||||
'ティラノサウルス',
|
'おふとん',
|
||||||
'尿路結石',
|
|
||||||
'エンターキー',
|
|
||||||
'壺',
|
|
||||||
'水銀',
|
|
||||||
'DHMO',
|
|
||||||
'水',
|
|
||||||
'土地',
|
|
||||||
'大陸',
|
|
||||||
'サイコロ',
|
|
||||||
'室外機',
|
|
||||||
'油圧ジャッキ',
|
|
||||||
'タピオカ',
|
|
||||||
'トイレットペーパーの芯',
|
|
||||||
'ダンボール箱',
|
|
||||||
'ハニワ',
|
|
||||||
'ボールペン',
|
|
||||||
'シャーペン',
|
|
||||||
'原子',
|
|
||||||
'宇宙',
|
|
||||||
'素粒子',
|
|
||||||
'ごま油',
|
|
||||||
'卵かけご飯',
|
|
||||||
'ダークマター',
|
|
||||||
'ブラックホール',
|
|
||||||
'太陽',
|
|
||||||
'石英ガラス',
|
|
||||||
'ダム',
|
|
||||||
'ウイルス',
|
|
||||||
'細菌',
|
|
||||||
'アーチ式コンクリートダム',
|
|
||||||
'重力式コンクリートダム',
|
|
||||||
'フラッシュバルブ',
|
|
||||||
'ヴィブラスラップ',
|
|
||||||
'オブジェ',
|
|
||||||
'原子力発電所',
|
|
||||||
'原子炉',
|
|
||||||
'エラトステネスの篩',
|
|
||||||
'ブラウン管',
|
|
||||||
'タキオン',
|
|
||||||
'ラッセルのティーポット',
|
|
||||||
'電子機器',
|
|
||||||
'TNT',
|
|
||||||
'ポリゴン',
|
|
||||||
'空気',
|
|
||||||
'RTX 3090',
|
|
||||||
'シャーペンの芯',
|
|
||||||
'ロゼッタストーン',
|
|
||||||
'CapsLockキー',
|
|
||||||
'虚無',
|
|
||||||
'UFO',
|
|
||||||
'NumLockキー',
|
|
||||||
'放射性廃棄物',
|
|
||||||
'火星',
|
|
||||||
'ウラン',
|
|
||||||
'遠心分離機',
|
|
||||||
'undefined',
|
|
||||||
'null',
|
|
||||||
'NaN',
|
|
||||||
'[object Object]',
|
|
||||||
'ゼロ幅スペース',
|
|
||||||
'全角スペース',
|
|
||||||
'太鼓',
|
|
||||||
'石像',
|
|
||||||
'スライム',
|
|
||||||
'点P',
|
|
||||||
'🤯',
|
|
||||||
'きんのたま',
|
|
||||||
'フロッピーディスク',
|
|
||||||
'掛け軸',
|
|
||||||
'JavaScriptコンソール',
|
|
||||||
'インターネットエクスプローラー',
|
|
||||||
'潜水艦発射弾道ミサイル',
|
|
||||||
'ミトコンドリア',
|
|
||||||
'ヘリウム',
|
|
||||||
'タンパク質',
|
|
||||||
'カプサイシン',
|
|
||||||
'エスカレーター',
|
|
||||||
'核融合炉',
|
|
||||||
'地熱発電所',
|
|
||||||
'マンション',
|
|
||||||
'ラバライト',
|
|
||||||
'ガリレオ温度計',
|
|
||||||
'ラジオメーター',
|
|
||||||
'サンドピクチャー',
|
|
||||||
'ストームグラス',
|
|
||||||
'ニュートンクレードル',
|
|
||||||
'永久機関',
|
|
||||||
'柿の種のピーナッツ部分',
|
|
||||||
'伝票入れる筒状のアレ',
|
|
||||||
'布団',
|
|
||||||
'寝具',
|
|
||||||
'偶像',
|
|
||||||
'森羅万象',
|
'森羅万象',
|
||||||
'卒塔婆',
|
'めがね',
|
||||||
'国民の基本的な権利',
|
|
||||||
'こたつ',
|
|
||||||
'靴下(片方は紛失)',
|
|
||||||
'健康保険証',
|
|
||||||
'テレホンカード',
|
|
||||||
'ピアノの黒鍵',
|
|
||||||
'ACアダプター',
|
|
||||||
'DVD',
|
|
||||||
'市営バス',
|
|
||||||
'基地局',
|
|
||||||
'404 Not Found',
|
|
||||||
'JSON',
|
|
||||||
'タペストリー',
|
|
||||||
'本',
|
|
||||||
'石像',
|
|
||||||
'古文書',
|
|
||||||
'巻物',
|
|
||||||
'Misskey',
|
|
||||||
'もぎもぎフルーツ',
|
|
||||||
'<ここに任意の文字列>',
|
|
||||||
'化石',
|
|
||||||
'マンホールの蓋',
|
|
||||||
'蛇口',
|
|
||||||
'彁',
|
|
||||||
'鬮',
|
|
||||||
'1円玉',
|
|
||||||
'ト音記号',
|
|
||||||
'ポータル',
|
|
||||||
'国家予算',
|
|
||||||
'閉じ忘れられた鉤括弧の片割れ',
|
|
||||||
'電動マッサージ機',
|
|
||||||
'ポップアップ広告',
|
|
||||||
'README.txt',
|
|
||||||
'あああああ',
|
|
||||||
'コミット',
|
|
||||||
'素数',
|
|
||||||
'タスクマネージャー',
|
|
||||||
'有象無象',
|
|
||||||
'炭水化物',
|
|
||||||
'正十二面体',
|
|
||||||
'クラインの壺',
|
|
||||||
'メビウスの輪',
|
|
||||||
'オリハルコン',
|
|
||||||
'ヘドロ',
|
|
||||||
'グレーチング',
|
|
||||||
'繝九Λ縺ョ縺ソ縺晄ア',
|
|
||||||
'スーパーカミオカンデ',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const and = [
|
export const and = [
|
||||||
'に擬態した',
|
'に擬態した',
|
||||||
'入りの',
|
'入りの',
|
||||||
|
'が埋め込まれた',
|
||||||
|
'を連想させる',
|
||||||
'っぽい',
|
'っぽい',
|
||||||
'に見せかけて',
|
'に見せかけて',
|
||||||
'を虐げる',
|
'を虐げる',
|
||||||
'を侍らせた',
|
'を侍らせた',
|
||||||
'が上に乗った',
|
'が上に乗った',
|
||||||
|
'のそばにある',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function genItem(seedOrRng?: (() => number) | string | number) {
|
export function genItem(seedOrRng?: (() => number) | string | number) {
|
||||||
|
5
to-hiragana.ts
Normal file
5
to-hiragana.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const moji = require('moji');
|
||||||
|
|
||||||
|
export default function toHiragana(str: string): string {
|
||||||
|
return moji(str).convert('HK', 'ZK').convert('KK', 'HG').toString();
|
||||||
|
}
|
104
torisetu.md
104
torisetu.md
@ -1,90 +1,64 @@
|
|||||||
<img src="https://github.com/syuilo/ai/blob/master/ai.png?raw=true" align="right" height="320px"/>
|
<img src="https://usercontent.misskey.online/xb4786y7/235f8ff2-1e18-4f84-8ee2-80490eacc3a6.png" align="right" height="320px"/>
|
||||||
|
|
||||||
# 取扱説明書
|
# ぬるきゃっとちゃん!の主な機能
|
||||||
|
|
||||||
## プロフィール
|
### フォローする
|
||||||
[こちら](https://xn--931a.moe/)
|
僕に「フォローして」って言ってくれたらフォローするよ
|
||||||
|
|
||||||
## 藍の主な機能
|
### お話
|
||||||
### 挨拶
|
「おはよう」「おやすみ」などと話しかけると反応するよ
|
||||||
「おはよう」「おやすみ」などと話しかけると反応してくれます。
|
|
||||||
|
### リアクション
|
||||||
|
僕が設定されている特定のワードにリアクションするよ
|
||||||
|
|
||||||
### 占い
|
### 占い
|
||||||
藍に「占い」と言うと、あなたの今日の運勢を占ってくれます。
|
僕に「占って」と言うと、あなたの今日の運勢を占うよ
|
||||||
|
|
||||||
### タイマー
|
### タイマー
|
||||||
指定した時間、分、秒を経過したら教えてくれます。「3分40秒」のように単位を混ぜることもできます。
|
指定した時間、分、秒を経過したら教えてくれるよ「3分40秒」のように単位を混ぜることもできるよ
|
||||||
|
|
||||||
### リマインダー
|
### リマインダー
|
||||||
```
|
`@nullcat todo(リマインド、これやる) 寝る` みたいに言ってくれたら1時間置きにリマインドするよ。その飛ばしたメンションか、僕からの催促に「やった」「やめた」など返信するとリマインダー解除されるよ<br>
|
||||||
@ai remind 部屋の掃除
|
引用Renoteでメンションすることもできるよ<br>
|
||||||
```
|
リマインダーの一覧は `@nullcat todos` で見れるよ
|
||||||
のようにメンションを飛ばすと12時間置きに責付かれます。その飛ばしたメンションか、藍ちゃんからの催促に「やった」または「やめた」と返信することでリマインダー解除されます。
|
|
||||||
また、引用Renoteでメンションすることもできます。
|
|
||||||
|
|
||||||
### 福笑い
|
### GitHub Status
|
||||||
藍に「絵文字」と言うと、藍が考えた絵文字の組み合わせを教えてくれます。
|
僕に「GitHub」って言ってくれたら今のStatusを教えるよ
|
||||||
|
|
||||||
### サイコロ
|
### Cloudflare Status
|
||||||
ダイスノーテーションを伝えるとサイコロを振ってくれます。
|
僕に「Cloudflare」って言ってくれたら今のStatusを教えるよ
|
||||||
例: "2d6" (6面サイコロを2回振る)、"3d5" (5面サイコロを3回振る)
|
|
||||||
|
|
||||||
### 迷路
|
### シェル芸機能
|
||||||
「迷路」と言うと迷路を描いてくれます。「難しい」「簡単」などの言葉を添えることで、難易度も調整できます。
|
僕に #シェル芸、#shellge をつけてコマンドを送ってくれたら実行結果を返すよ
|
||||||
|
|
||||||
### 数当てゲーム
|
### 怪レい曰本语変換
|
||||||
藍にメッセージで「数当てゲーム」と言うと遊べます。
|
僕に `#怪しい日本語変換` っていうタグ付きで変換してほしい文章をメンションしてくれたら怪レい曰本语に変換するよ
|
||||||
藍の考えている数字を当てるゲームです。
|
|
||||||
|
|
||||||
### 数取りゲーム
|
### やること決める
|
||||||
藍に「数取りゲーム」と言うと遊べます。
|
僕に「なにしよ」って言ってくれたらやることを決めるよ
|
||||||
複数人で行うゲームで、もっとも大きい数字を言った人が勝ちです。
|
|
||||||
|
|
||||||
### リバーシ
|
### 気圧
|
||||||
藍とリバーシで対局できます。(この機能はインスタンスによっては無効になっている可能性があります)
|
僕に「気圧教えて」って言ってくれたら今の気圧を教えるよ
|
||||||
藍に「リバーシ」と言うか、リバーシで藍を指名すれば対局できます。
|
|
||||||
強さも調整できます。
|
|
||||||
|
|
||||||
### 覚える
|
|
||||||
たまにタイムラインにあるキーワードを「覚え」ます。
|
|
||||||
(この機能はインスタンスによっては無効になっている可能性があります)
|
|
||||||
|
|
||||||
### 呼び方を教える
|
### 呼び方を教える
|
||||||
藍があなたのことをなんて呼べばいいか教えられます。
|
僕が君のことをなんて呼べばいいか教えてくれたら、その名前で呼ぶよ!<br>
|
||||||
ただし後述の親愛度が一定の値に達している必要があります。
|
親愛度が一定の値に達している必要があるよ<br>
|
||||||
(トークでのみ反応します)
|
(チャットのみで反応するよ)
|
||||||
|
|
||||||
### いらっしゃい
|
|
||||||
Misskeyにアカウントを作成して初めて投稿を行うと、藍がネコミミアンテナでそれを補足し、Renoteしてみんなに知らせてくれる機能です。
|
|
||||||
|
|
||||||
### Follow me
|
|
||||||
藍に「フォローして」と言うとフォローしてくれます。
|
|
||||||
|
|
||||||
### HappyBirthday
|
### HappyBirthday
|
||||||
藍があなたの誕生日を祝ってくれます。
|
誕生日になったら僕が君の誕生日を祝うよ
|
||||||
|
|
||||||
### バレンタイン
|
### バレンタイン
|
||||||
藍がチョコレートをくれます。
|
バレンタインになったら仲のいい子に僕がチョコレートをあげるよ
|
||||||
|
|
||||||
### チャート
|
|
||||||
インスタンスの投稿チャートなどを投稿してくれます。
|
|
||||||
|
|
||||||
### サーバー監視
|
|
||||||
サーバーの状態を監視し、負荷が高くなっているときは教えてくれます。
|
|
||||||
|
|
||||||
### ping
|
### ping
|
||||||
PONGを返します。生存確認にどうぞ
|
僕に「ping」って言ってくれたらフォローするよで起きてるとき返信するよ!寝てるときは返信できないかも...
|
||||||
|
|
||||||
### その他反応するフレーズ (トークのみ)
|
### 親愛度
|
||||||
* かわいい
|
僕は君に対する親愛度を持っているよ<br>
|
||||||
* なでなで
|
僕にお話ししてくれたりすると、少しずつ上がるよ<br>
|
||||||
* 好き
|
親愛度によって反応が変化するよ!親愛度がある程度ないとしてくれないこともあるよ<br>
|
||||||
* ぎゅー
|
たくさん話しかけてね
|
||||||
* 罵って
|
|
||||||
* 踏んで
|
|
||||||
* 痛い
|
|
||||||
|
|
||||||
## 親愛度
|
|
||||||
藍はあなたに対する親愛度を持っています。
|
僕のリポジトリは[ここ](https://github.com/nullnyat/NullcatChan)だよ
|
||||||
藍に挨拶したりすると、少しずつ上がっていきます。
|
|
||||||
親愛度によって反応や各種セリフが変化します。親愛度がある程度ないとしてくれないこともあります。
|
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
"noLib": false,
|
"noLib": false,
|
||||||
"outDir": "built",
|
"outDir": "built",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": ".",
|
"baseUrl": "../NullcatChan-old",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"include": [
|
"include": [
|
||||||
|
10
utils/includes.ts
Normal file
10
utils/includes.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { hankakuToZenkaku, katakanaToHiragana } from "./japanese"
|
||||||
|
|
||||||
|
export default function (text: string, words: string[]): boolean {
|
||||||
|
if (text == null) return false
|
||||||
|
|
||||||
|
text = katakanaToHiragana(hankakuToZenkaku(text)).toLowerCase()
|
||||||
|
words = words.map((word) => katakanaToHiragana(word).toLowerCase())
|
||||||
|
|
||||||
|
return words.some((word) => text.includes(word))
|
||||||
|
}
|
@ -9,7 +9,7 @@ export default function(text: string, words: (string | RegExp)[]): boolean {
|
|||||||
return words.some(word => {
|
return words.some(word => {
|
||||||
/**
|
/**
|
||||||
* テキストの余分な部分を取り除く
|
* テキストの余分な部分を取り除く
|
||||||
* 例えば「藍ちゃん好き!」のようなテキストを「好き」にする
|
* 例えば「ぬるきゃっとちゃん好き!」のようなテキストを「好き」にする
|
||||||
*/
|
*/
|
||||||
function denoise(text: string): string {
|
function denoise(text: string): string {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
@ -34,7 +34,7 @@ export default function(text: string, words: (string | RegExp)[]): boolean {
|
|||||||
text = text.replace(/です$/, '');
|
text = text.replace(/です$/, '');
|
||||||
text = text.replace(/(\.|…)+$/, '');
|
text = text.replace(/(\.|…)+$/, '');
|
||||||
text = text.replace(/[♪♥]+$/, '');
|
text = text.replace(/[♪♥]+$/, '');
|
||||||
text = text.replace(/^藍/, '');
|
text = text.replace(/^ぬるきゃっと/, '');
|
||||||
text = text.replace(/^ちゃん/, '');
|
text = text.replace(/^ちゃん/, '');
|
||||||
text = text.replace(/、+$/, '');
|
text = text.replace(/、+$/, '');
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user