mirror of
https://github.com/nullnyat/NullcatChan.git
synced 2025-04-29 03:57:20 +09:00
Nullcatchan!
This commit is contained in:
parent
eb853be7e8
commit
8bf9550139
@ -1,6 +1,6 @@
|
||||
config.json
|
||||
font.ttf
|
||||
ai.*
|
||||
nullcat.*
|
||||
*.md
|
||||
*.png
|
||||
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">An Ai for Misskey. <a href="./torisetu.md">About Ai</a></p>
|
||||
<p align="center">
|
||||
<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` します。
|
||||
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
||||
``` json
|
||||
{
|
||||
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
||||
"i": "藍として動かしたいアカウントのアクセストークン",
|
||||
"i": "ぬるきゃっとちゃん!として動かしたいアカウントのアクセストークン",
|
||||
"master": "管理者のユーザー名(オプション)",
|
||||
"notingEnabled": "ランダムにノートを投稿する機能を無効にする場合は false を入れる",
|
||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"chartEnabled": "チャート機能を無効化する場合は false を入れてください",
|
||||
"reversiEnabled": "藍とリバーシで対局できる機能を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"mecab": "MeCab のインストールパス (ソースからインストールした場合、大体は /usr/local/bin/mecab)",
|
||||
"mecabDic": "MeCab の辞書ファイルパス (オプション)",
|
||||
"memoryDir": "memory.jsonの保存先(オプション、デフォルトは'.'(レポジトリのルートです))"
|
||||
"notingEnabled": "ランダムにノートを投稿する機能。true(on) or false(off)",
|
||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) true or false",
|
||||
"serverMonitoring": "サーバー監視の機能(重かったりすると教えてくれるよ。)true or false",
|
||||
"mecab": "MeCab のインストールパス (ソースからインストールした場合、大体は /usr/local/bin/mecab) true or false",
|
||||
"mecabDic": "MeCab の辞書ファイルパス",
|
||||
"memoryDir": "memory.jsonの保存先(オプション、デフォルトは'.'(レポジトリのルートです))",
|
||||
"shellgeiUrl": "シェル芸BotのAPIのURLです(デフォルトではhttps://websh.jiro4989.com/api/shellgei)"
|
||||
}
|
||||
```
|
||||
`npm install` して `npm run build` して `npm start` すれば起動できます
|
||||
`npm install` して `npm run build` して `npm start` すれば起動できます。
|
||||
|
||||
## Dockerで動かす
|
||||
まず適当なディレクトリに `git clone` します。
|
||||
### Dockerで動かす
|
||||
|
||||
まず適当なディレクトリに `git clone` します。<br>
|
||||
次にそのディレクトリに `config.json` を作成します。中身は次のようにします:
|
||||
(MeCabの設定、memoryDirについては触らないでください)
|
||||
``` json
|
||||
{
|
||||
"host": "https:// + あなたのインスタンスのURL (末尾の / は除く)",
|
||||
"i": "藍として動かしたいアカウントのアクセストークン",
|
||||
"i": "ぬるきゃっとちゃん!として動かしたいアカウントのアクセストークン",
|
||||
"master": "管理者のユーザー名(オプション)",
|
||||
"notingEnabled": "ランダムにノートを投稿する機能を無効にする場合は false を入れる",
|
||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"chartEnabled": "チャート機能を無効化する場合は false を入れてください",
|
||||
"reversiEnabled": "藍とリバーシで対局できる機能を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"serverMonitoring": "サーバー監視の機能を有効にする場合は true を入れる (無効にする場合は false)",
|
||||
"notingEnabled": "ランダムにノートを投稿する機能。true(on) or false(off)",
|
||||
"keywordEnabled": "キーワードを覚える機能 (MeCab が必要) true or false",
|
||||
"mecab": "/usr/bin/mecab",
|
||||
"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をインストールしないようにもできます。(メモリが少ない環境など)
|
||||
|
||||
## フォント
|
||||
一部の機能にはフォントが必要です。藍にはフォントは同梱されていないので、ご自身でフォントをインストールディレクトリに`font.ttf`という名前で設置してください。
|
||||
|
||||
## 記憶
|
||||
藍は記憶の保持にインメモリデータベースを使用しており、藍のインストールディレクトリに `memory.json` という名前で永続化されます。
|
||||
|
||||
## ライセンス
|
||||
MIT
|
||||
|
||||
## Awards
|
||||
<img src="./WorksOnMyMachine.png" alt="Works on my machine" height="120">
|
||||
#### 一部の機能にはフォントが必要です。NullcatChan!にはフォントは同梱されていないので、ご自身でフォントをインストールしてそのフォントを`font.ttf`という名前でインストールディレクトリに設置してください。
|
||||
#### NullcatChan!は記憶の保持にインメモリデータベースを使用しており、僕のインストールディレクトリに `memory.json` という名前で永続化されます。
|
||||
|
@ -2,11 +2,14 @@ version: '3'
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile_production
|
||||
context: ../NullcatChan-old
|
||||
args:
|
||||
- enable_mecab=1
|
||||
volumes:
|
||||
- './config.json:/ai/config.json:ro'
|
||||
- './font.ttf:/ai/font.ttf:ro'
|
||||
- './data:/ai/data'
|
||||
- './config.json:/nullcat-chan/config.json:ro'
|
||||
- './font.ttf:/nullcat-chan/font.ttf:ro'
|
||||
- './data:/nullcat-chan/data'
|
||||
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",
|
||||
"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",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/accurate-interval": "1.0.0",
|
||||
"@types/chalk": "2.2.0",
|
||||
"@types/humanize-duration": "3.27.1",
|
||||
"@types/lokijs": "1.5.4",
|
||||
"@types/moji": "0.5.0",
|
||||
"@types/node": "16.0.1",
|
||||
"@types/promise-retry": "1.1.3",
|
||||
"@types/random-seed": "0.3.3",
|
||||
@ -17,13 +23,19 @@
|
||||
"@types/twemoji-parser": "13.1.1",
|
||||
"@types/uuid": "8.3.1",
|
||||
"@types/ws": "7.4.6",
|
||||
"accurate-interval": "1.0.9",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"canvas": "2.8.0",
|
||||
"chalk": "4.1.1",
|
||||
"cjp": "1.2.3",
|
||||
"gomamayo-js": "0.2.1",
|
||||
"humanize-duration": "3.27.1",
|
||||
"lokijs": "1.5.12",
|
||||
"memory-streams": "0.1.3",
|
||||
"misskey-reversi": "0.0.5",
|
||||
"module-alias": "2.2.2",
|
||||
"moji": "0.5.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"promise-retry": "2.0.1",
|
||||
"random-seed": "0.3.0",
|
||||
"reconnecting-websocket": "4.4.0",
|
||||
@ -33,9 +45,10 @@
|
||||
"timeout-as-promise": "1.0.0",
|
||||
"ts-node": "10.0.0",
|
||||
"twemoji-parser": "13.1.0",
|
||||
"typescript": "4.3.5",
|
||||
"typescript": "4.5.5",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.2"
|
||||
"ws": "7.5.2",
|
||||
"zod": "3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@koa/router": "9.4.0",
|
||||
@ -43,9 +56,11 @@
|
||||
"@types/koa": "2.13.1",
|
||||
"@types/koa__router": "8.0.4",
|
||||
"@types/websocket": "1.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
"jest": "26.6.3",
|
||||
"koa": "2.13.1",
|
||||
"koa-json-body": "5.3.0",
|
||||
"prettier": "2.5.1",
|
||||
"ts-jest": "26.5.6",
|
||||
"websocket": "1.0.34"
|
||||
},
|
||||
|
@ -5,13 +5,12 @@ type Config = {
|
||||
wsUrl: string;
|
||||
apiUrl: string;
|
||||
keywordEnabled: boolean;
|
||||
reversiEnabled: boolean;
|
||||
notingEnabled: boolean;
|
||||
chartEnabled: boolean;
|
||||
serverMonitoring: boolean;
|
||||
mecab?: string;
|
||||
mecabDic?: string;
|
||||
memoryDir?: string;
|
||||
shellgeiUrl: string;
|
||||
};
|
||||
|
||||
const config = require('../config.json');
|
||||
|
164
src/friend.ts
164
src/friend.ts
@ -1,71 +1,71 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import 藍 from '@/ai';
|
||||
import IModule from '@/module';
|
||||
import getDate from '@/utils/get-date';
|
||||
import { User } from '@/misskey/user';
|
||||
import { genItem } from '@/vocabulary';
|
||||
import { User } from "./misskey/user"
|
||||
import IModule from "../../NullcatChan-old/src/module"
|
||||
import NullcatChan from "../../NullcatChan-old/src/nullcat-chan"
|
||||
import getDate from "../../NullcatChan-old/src/utils/get-date"
|
||||
import { genItem } from "../../NullcatChan-old/src/vocabulary"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export type FriendDoc = {
|
||||
userId: string;
|
||||
user: User;
|
||||
name?: string | null;
|
||||
love?: number;
|
||||
lastLoveIncrementedAt?: string;
|
||||
todayLoveIncrements?: number;
|
||||
perModulesData?: any;
|
||||
married?: boolean;
|
||||
transferCode?: string;
|
||||
};
|
||||
userId: string
|
||||
user: User
|
||||
name?: string | null
|
||||
love?: number
|
||||
lastLoveIncrementedAt?: string
|
||||
todayLoveIncrements?: number
|
||||
perModulesData?: any
|
||||
married?: boolean
|
||||
transferCode?: string
|
||||
}
|
||||
|
||||
export default class Friend {
|
||||
private ai: 藍;
|
||||
private nullcatChan: NullcatChan
|
||||
|
||||
public get userId() {
|
||||
return this.doc.userId;
|
||||
return this.doc.userId
|
||||
}
|
||||
|
||||
public get name() {
|
||||
return this.doc.name;
|
||||
return this.doc.name
|
||||
}
|
||||
|
||||
public get love() {
|
||||
return this.doc.love || 0;
|
||||
return this.doc.love || 0
|
||||
}
|
||||
|
||||
public get married() {
|
||||
return this.doc.married;
|
||||
return this.doc.married
|
||||
}
|
||||
|
||||
public doc: FriendDoc;
|
||||
public doc: FriendDoc
|
||||
|
||||
constructor(ai: 藍, opts: { user?: User, doc?: FriendDoc }) {
|
||||
this.ai = ai;
|
||||
constructor(nullcatChan: NullcatChan, opts: { user?: User; doc?: FriendDoc }) {
|
||||
this.nullcatChan = nullcatChan
|
||||
|
||||
if (opts.user) {
|
||||
const exist = this.ai.friends.findOne({
|
||||
userId: opts.user.id
|
||||
});
|
||||
const exist = this.nullcatChan.friends.findOne({
|
||||
userId: opts.user.id,
|
||||
})
|
||||
|
||||
if (exist == null) {
|
||||
const inserted = this.ai.friends.insertOne({
|
||||
const inserted = this.nullcatChan.friends.insertOne({
|
||||
userId: opts.user.id,
|
||||
user: opts.user
|
||||
});
|
||||
user: opts.user,
|
||||
})
|
||||
|
||||
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 {
|
||||
this.doc = exist;
|
||||
this.doc.user = { ...this.doc.user, ...opts.user };
|
||||
this.save();
|
||||
this.doc = exist
|
||||
this.doc.user = { ...this.doc.user, ...opts.user }
|
||||
this.save()
|
||||
}
|
||||
} else if (opts.doc) {
|
||||
this.doc = opts.doc;
|
||||
this.doc = opts.doc
|
||||
} 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,
|
||||
...user,
|
||||
};
|
||||
this.save();
|
||||
}
|
||||
this.save()
|
||||
}
|
||||
|
||||
@autobind
|
||||
public getPerModulesData(module: IModule) {
|
||||
if (this.doc.perModulesData == null) {
|
||||
this.doc.perModulesData = {};
|
||||
this.doc.perModulesData[module.name] = {};
|
||||
this.save();
|
||||
this.doc.perModulesData = {}
|
||||
this.doc.perModulesData[module.name] = {}
|
||||
this.save()
|
||||
} else if (this.doc.perModulesData[module.name] == null) {
|
||||
this.doc.perModulesData[module.name] = {};
|
||||
this.save();
|
||||
this.doc.perModulesData[module.name] = {}
|
||||
this.save()
|
||||
}
|
||||
|
||||
return this.doc.perModulesData[module.name];
|
||||
return this.doc.perModulesData[module.name]
|
||||
}
|
||||
|
||||
@autobind
|
||||
public setPerModulesData(module: IModule, data: any) {
|
||||
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
|
||||
public incLove(amount = 1) {
|
||||
const today = getDate();
|
||||
const today = getDate()
|
||||
|
||||
if (this.doc.lastLoveIncrementedAt != today) {
|
||||
this.doc.todayLoveIncrements = 0;
|
||||
this.doc.todayLoveIncrements = 0
|
||||
}
|
||||
|
||||
// 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;
|
||||
this.doc.love += amount;
|
||||
if (this.doc.love == null) this.doc.love = 0
|
||||
this.doc.love += amount
|
||||
|
||||
// 最大 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.todayLoveIncrements = (this.doc.todayLoveIncrements || 0) + amount;
|
||||
this.save();
|
||||
this.doc.lastLoveIncrementedAt = today
|
||||
this.doc.todayLoveIncrements = (this.doc.todayLoveIncrements || 0) + amount
|
||||
this.save()
|
||||
|
||||
this.ai.log(`💗 ${this.userId} +${amount}`);
|
||||
this.nullcatChan.log(`💗 ${this.userId} +${amount}`)
|
||||
}
|
||||
|
||||
@autobind
|
||||
public decLove(amount = 1) {
|
||||
// 親愛度MAXなら下げない
|
||||
if (this.doc.love === 100) return;
|
||||
if (this.doc.love === 100) return
|
||||
|
||||
if (this.doc.love == null) this.doc.love = 0;
|
||||
this.doc.love -= amount;
|
||||
if (this.doc.love == null) this.doc.love = 0
|
||||
this.doc.love -= amount
|
||||
|
||||
// 最低 -30
|
||||
if (this.doc.love < -30) this.doc.love = -30;
|
||||
if (this.doc.love < -30) this.doc.love = -30
|
||||
|
||||
// 親愛度マイナスなら名前を忘れる
|
||||
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
|
||||
public updateName(name: string) {
|
||||
this.doc.name = name;
|
||||
this.save();
|
||||
this.doc.name = name
|
||||
this.save()
|
||||
}
|
||||
|
||||
@autobind
|
||||
public save() {
|
||||
this.ai.friends.update(this.doc);
|
||||
this.nullcatChan.friends.update(this.doc)
|
||||
}
|
||||
|
||||
@autobind
|
||||
public generateTransferCode(): string {
|
||||
const code = genItem();
|
||||
const code = genItem()
|
||||
|
||||
this.doc.transferCode = code;
|
||||
this.save();
|
||||
this.doc.transferCode = code
|
||||
this.save()
|
||||
|
||||
return code;
|
||||
return code
|
||||
}
|
||||
|
||||
@autobind
|
||||
public transferMemory(code: string): boolean {
|
||||
const src = this.ai.friends.findOne({
|
||||
transferCode: code
|
||||
});
|
||||
const src = this.nullcatChan.friends.findOne({
|
||||
transferCode: code,
|
||||
})
|
||||
|
||||
if (src == null) return false;
|
||||
if (src == null) return false
|
||||
|
||||
this.doc.name = src.name;
|
||||
this.doc.love = src.love;
|
||||
this.doc.married = src.married;
|
||||
this.doc.perModulesData = src.perModulesData;
|
||||
this.save();
|
||||
this.doc.name = src.name
|
||||
this.doc.love = src.love
|
||||
this.doc.married = src.married
|
||||
this.doc.perModulesData = src.perModulesData
|
||||
this.save()
|
||||
|
||||
// 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';
|
||||
import * as request from 'request-promise-native';
|
||||
const promiseRetry = require('promise-retry');
|
||||
const promiseRetry = require("promise-retry")
|
||||
|
||||
import 藍 from './ai';
|
||||
import config from './config';
|
||||
import _log from './utils/log';
|
||||
const pkg = require('../package.json');
|
||||
const pkg = require("../../NullcatChan-old/package.json")
|
||||
|
||||
import CoreModule from './modules/core';
|
||||
import TalkModule from './modules/talk';
|
||||
import BirthdayModule from './modules/birthday';
|
||||
import ReversiModule from './modules/reversi';
|
||||
import PingModule from './modules/ping';
|
||||
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');
|
||||
console.log(" _ __ ____ __ ________ __ ")
|
||||
console.log(" / | / /_ __/ / /________ _/ /_/ ____/ /_ ____ _____ / / ")
|
||||
console.log(" / |/ / / / / / / ___/ __ `/ __/ / / __ \\/ __ `/ __ \\/ / ")
|
||||
console.log(" / /| / /_/ / / / /__/ /_/ / /_/ /___/ / / / /_/ / / / /_/ ")
|
||||
console.log("/_/ |_/\\__,_/_/_/\\___/\\__,_/\\__/\\____/_/ /_/\\__,_/_/ /_(_)\n")
|
||||
|
||||
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 => {
|
||||
log(`Account fetching... ${chalk.gray(config.host)}`);
|
||||
promiseRetry(
|
||||
(retry) => {
|
||||
log(`Account fetching... ${chalk.gray(config.host)}`)
|
||||
|
||||
// アカウントをフェッチ
|
||||
return request.post(`${config.apiUrl}/i`, {
|
||||
return request
|
||||
.post(`${config.apiUrl}/i`, {
|
||||
json: {
|
||||
i: config.i
|
||||
i: config.i,
|
||||
},
|
||||
})
|
||||
.catch(retry)
|
||||
},
|
||||
{
|
||||
retries: 3,
|
||||
}
|
||||
}).catch(retry);
|
||||
}, {
|
||||
retries: 3
|
||||
}).then(account => {
|
||||
const acct = `@${account.username}`;
|
||||
log(chalk.green(`Account fetched successfully: ${chalk.underline(acct)}`));
|
||||
)
|
||||
.then((account) => {
|
||||
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 EmojiModule(),
|
||||
new EmojiReactModule(),
|
||||
new FortuneModule(),
|
||||
new GuessingGameModule(),
|
||||
new KazutoriModule(),
|
||||
new ReversiModule(),
|
||||
new TimerModule(),
|
||||
new DiceModule(),
|
||||
new TalkModule(),
|
||||
new PingModule(),
|
||||
new WelcomeModule(),
|
||||
new ServerModule(),
|
||||
new FollowModule(),
|
||||
new BirthdayModule(),
|
||||
new ValentineModule(),
|
||||
new KeywordModule(),
|
||||
new MazeModule(),
|
||||
new ChartModule(),
|
||||
new SleepReportModule(),
|
||||
new NotingModule(),
|
||||
new PollModule(),
|
||||
new ReminderModule(),
|
||||
]);
|
||||
}).catch(e => {
|
||||
log(chalk.red('Failed to fetch the account'));
|
||||
});
|
||||
new GomamayoModule(),
|
||||
new GitHubStatusModule(),
|
||||
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 * as chalk from 'chalk';
|
||||
const delay = require('timeout-as-promise');
|
||||
import config from "@/config"
|
||||
import Friend from "@/friend"
|
||||
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';
|
||||
import Friend from '@/friend';
|
||||
import { User } from '@/misskey/user';
|
||||
import includes from '@/utils/includes';
|
||||
import or from '@/utils/or';
|
||||
import config from '@/config';
|
||||
interface MisskeyFile {
|
||||
id: string
|
||||
createdAt: string
|
||||
name: string
|
||||
type: string
|
||||
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 {
|
||||
private ai: 藍;
|
||||
private messageOrNote: any;
|
||||
public isDm: boolean;
|
||||
private nullcatChan: NullcatChan
|
||||
private messageOrNote: any
|
||||
public isDm: boolean
|
||||
|
||||
public get id(): string {
|
||||
return this.messageOrNote.id;
|
||||
return this.messageOrNote.id
|
||||
}
|
||||
|
||||
public get user(): User {
|
||||
return this.messageOrNote.user;
|
||||
return this.messageOrNote.user
|
||||
}
|
||||
|
||||
public get userId(): string {
|
||||
return this.messageOrNote.userId;
|
||||
return this.messageOrNote.userId
|
||||
}
|
||||
|
||||
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 {
|
||||
return this.messageOrNote.renoteId;
|
||||
return this.messageOrNote.renoteId
|
||||
}
|
||||
|
||||
public get visibility(): string {
|
||||
return this.messageOrNote.visibility;
|
||||
public get files(): MisskeyFile[] | undefined {
|
||||
return this.messageOrNote.files
|
||||
}
|
||||
|
||||
/**
|
||||
* メンション部分を除いたテキスト本文
|
||||
*/
|
||||
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
|
||||
.replace(new RegExp(`^@${this.ai.account.username}@${host}\\s`, 'i'), '')
|
||||
.replace(new RegExp(`^@${this.ai.account.username}\\s`, 'i'), '')
|
||||
.trim();
|
||||
.replace(new RegExp(`^@${this.nullcatChan.account.username}@${host}\\s`, "i"), "")
|
||||
.replace(new RegExp(`^@${this.nullcatChan.account.username}\\s`, "i"), "")
|
||||
.trim()
|
||||
}
|
||||
|
||||
public get replyId(): string {
|
||||
return this.messageOrNote.replyId;
|
||||
return this.messageOrNote.replyId
|
||||
}
|
||||
|
||||
public friend: Friend;
|
||||
public friend: Friend
|
||||
|
||||
constructor(ai: 藍, messageOrNote: any, isDm: boolean) {
|
||||
this.ai = ai;
|
||||
this.messageOrNote = messageOrNote;
|
||||
this.isDm = isDm;
|
||||
constructor(nullcatChan: NullcatChan, messageOrNote: any, isDm: boolean) {
|
||||
this.nullcatChan = nullcatChan
|
||||
this.messageOrNote = messageOrNote
|
||||
this.isDm = isDm
|
||||
|
||||
this.friend = new Friend(ai, { user: this.user });
|
||||
this.friend = new Friend(nullcatChan, { user: this.user })
|
||||
|
||||
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
|
||||
this.ai.api('users/show', {
|
||||
userId: this.userId
|
||||
}).then(user => {
|
||||
this.friend.updateUser(user);
|
||||
});
|
||||
this.nullcatChan
|
||||
.api("users/show", {
|
||||
userId: this.userId,
|
||||
})
|
||||
.then((user) => {
|
||||
this.friend.updateUser(user)
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
public async reply(text: string | null, opts?: {
|
||||
file?: any;
|
||||
cw?: string;
|
||||
renote?: string;
|
||||
immediate?: boolean;
|
||||
}) {
|
||||
if (text == null) return;
|
||||
public async reply(
|
||||
text: string | null,
|
||||
opts?: {
|
||||
file?: any
|
||||
cw?: string
|
||||
renote?: string
|
||||
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) {
|
||||
await delay(2000);
|
||||
await delay(2000)
|
||||
}
|
||||
|
||||
if (this.isDm) {
|
||||
return await this.ai.sendMessage(this.messageOrNote.userId, {
|
||||
return await this.nullcatChan.sendMessage(this.messageOrNote.userId, {
|
||||
text: text,
|
||||
fileId: opts?.file?.id
|
||||
});
|
||||
fileId: opts?.file?.id,
|
||||
})
|
||||
} else {
|
||||
return await this.ai.post({
|
||||
return await this.nullcatChan.post({
|
||||
replyId: this.messageOrNote.id,
|
||||
text: text,
|
||||
fileIds: opts?.file ? [opts?.file.id] : undefined,
|
||||
cw: opts?.cw,
|
||||
renoteId: opts?.renote
|
||||
});
|
||||
renoteId: opts?.renote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
public includes(words: string[]): boolean {
|
||||
return includes(this.text, words);
|
||||
return includes(this.text, words)
|
||||
}
|
||||
|
||||
@autobind
|
||||
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 藍, { InstallerResult } from '@/ai';
|
||||
import NullcatChan, { InstallerResult } from "./nullcat-chan"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default abstract class Module {
|
||||
public abstract readonly name: string;
|
||||
public abstract readonly name: string
|
||||
|
||||
protected ai: 藍;
|
||||
private doc: any;
|
||||
protected nullcatChan: NullcatChan
|
||||
private doc: any
|
||||
|
||||
public init(ai: 藍) {
|
||||
this.ai = ai;
|
||||
public init(nullcatChan: NullcatChan) {
|
||||
this.nullcatChan = nullcatChan
|
||||
|
||||
this.doc = this.ai.moduleData.findOne({
|
||||
module: this.name
|
||||
});
|
||||
this.doc = this.nullcatChan.moduleData.findOne({
|
||||
module: this.name,
|
||||
})
|
||||
|
||||
if (this.doc == null) {
|
||||
this.doc = this.ai.moduleData.insertOne({
|
||||
this.doc = this.nullcatChan.moduleData.insertOne({
|
||||
module: this.name,
|
||||
data: {}
|
||||
});
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public abstract install(): InstallerResult;
|
||||
public abstract install(): InstallerResult
|
||||
|
||||
@autobind
|
||||
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
|
||||
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
|
||||
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
|
||||
public setTimeoutWithPersistence(delay: number, data?: any) {
|
||||
this.ai.setTimeoutWithPersistence(this, delay, data);
|
||||
this.nullcatChan.setTimeoutWithPersistence(this, delay, data)
|
||||
}
|
||||
|
||||
@autobind
|
||||
protected getData() {
|
||||
return this.doc.data;
|
||||
return this.doc.data
|
||||
}
|
||||
|
||||
@autobind
|
||||
protected setData(data: any) {
|
||||
this.doc.data = data;
|
||||
this.ai.moduleData.update(this.doc);
|
||||
this.doc.data = data
|
||||
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 Module from '@/module';
|
||||
import Friend from '@/friend';
|
||||
import serifs from '@/serifs';
|
||||
import Friend from "@/friend"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
function zeroPadding(num: number, length: number): string {
|
||||
return ('0000000000' + num).slice(-length);
|
||||
return ("0000000000" + num).slice(-length)
|
||||
}
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'birthday';
|
||||
public readonly name = "birthday"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
this.crawleBirthday();
|
||||
setInterval(this.crawleBirthday, 1000 * 60 * 3);
|
||||
this.crawleBirthday()
|
||||
setInterval(this.crawleBirthday, 1000 * 60 * 3)
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,34 +23,34 @@ export default class extends Module {
|
||||
*/
|
||||
@autobind
|
||||
private crawleBirthday() {
|
||||
const now = new Date();
|
||||
const m = now.getMonth();
|
||||
const d = now.getDate();
|
||||
const now = new Date()
|
||||
const m = now.getMonth()
|
||||
const d = now.getDate()
|
||||
// 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({
|
||||
'user.birthday': { '$regex': new RegExp('-' + today + '$') }
|
||||
} as any);
|
||||
const birthFriends = this.nullcatChan.friends.find({
|
||||
"user.birthday": { $regex: new RegExp("-" + today + "$") },
|
||||
} as any)
|
||||
|
||||
birthFriends.forEach(f => {
|
||||
const friend = new Friend(this.ai, { doc: f });
|
||||
birthFriends.forEach((f) => {
|
||||
const friend = new Friend(this.nullcatChan, { doc: f })
|
||||
|
||||
// 親愛度が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;
|
||||
friend.setPerModulesData(this, data);
|
||||
data.lastBirthdayChecked = today
|
||||
friend.setPerModulesData(this, data)
|
||||
|
||||
const text = serifs.birthday.happyBirthday(friend.name);
|
||||
const text = serifs.birthday.happyBirthday(friend.name)
|
||||
|
||||
this.ai.sendMessage(friend.userId, {
|
||||
text: text
|
||||
});
|
||||
});
|
||||
this.nullcatChan.sendMessage(friend.userId, {
|
||||
text: text,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,159 +1,153 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs from '@/serifs';
|
||||
import { safeForInterpolate } from '@/utils/safe-for-interpolate';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import { safeForInterpolate } from "../../../../NullcatChan-old/src/utils/safe-for-interpolate"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
const titles = ['さん', 'くん', '君', 'ちゃん', '様', '先生'];
|
||||
const titles = ["さん", "くん", "君", "ちゃん", "様", "先生"]
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'core';
|
||||
public readonly name = "core"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
contextHook: this.contextHook
|
||||
};
|
||||
contextHook: this.contextHook,
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.text) return false
|
||||
|
||||
return (
|
||||
this.transferBegin(msg) ||
|
||||
this.transferEnd(msg) ||
|
||||
this.setName(msg) ||
|
||||
this.modules(msg) ||
|
||||
this.version(msg)
|
||||
);
|
||||
return this.transferBegin(msg) || this.transferEnd(msg) || this.setName(msg) || this.modules(msg) || this.version(msg)
|
||||
}
|
||||
|
||||
@autobind
|
||||
private transferBegin(msg: Message): boolean {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.includes(['引継', '引き継ぎ', '引越', '引っ越し'])) return false;
|
||||
if (!msg.text) return false
|
||||
if (!msg.includes(["引継", "引き継ぎ", "引越", "引っ越し"])) return false
|
||||
|
||||
// メッセージのみ
|
||||
if (!msg.isDm) {
|
||||
msg.reply(serifs.core.transferNeedDm);
|
||||
return true;
|
||||
msg.reply(serifs.core.transferNeedDm)
|
||||
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
|
||||
private transferEnd(msg: Message): boolean {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.text.startsWith('「') || !msg.text.endsWith('」')) return false;
|
||||
if (!msg.text) 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) {
|
||||
msg.reply(serifs.core.transferDone(msg.friend.name));
|
||||
msg.reply(serifs.core.transferDone(msg.friend.name))
|
||||
} else {
|
||||
msg.reply(serifs.core.transferFailed);
|
||||
msg.reply(serifs.core.transferFailed)
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
private setName(msg: Message): boolean {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.text.includes('って呼んで')) return false;
|
||||
if (msg.text.startsWith('って呼んで')) return false;
|
||||
if (!msg.text) return false
|
||||
if (!msg.text.includes("って呼んで")) 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) {
|
||||
msg.reply(serifs.core.tooLong);
|
||||
return true;
|
||||
msg.reply(serifs.core.tooLong)
|
||||
return true
|
||||
}
|
||||
|
||||
if (!safeForInterpolate(name)) {
|
||||
msg.reply(serifs.core.invalidName);
|
||||
return true;
|
||||
msg.reply(serifs.core.invalidName)
|
||||
return true
|
||||
}
|
||||
|
||||
const withSan = titles.some(t => name.endsWith(t));
|
||||
const withSan = titles.some((t) => name.endsWith(t))
|
||||
|
||||
if (withSan) {
|
||||
msg.friend.updateName(name);
|
||||
msg.reply(serifs.core.setNameOk(name));
|
||||
msg.friend.updateName(name)
|
||||
msg.reply(serifs.core.setNameOk(name))
|
||||
} 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, {
|
||||
name: name
|
||||
});
|
||||
});
|
||||
name: name,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
private modules(msg: Message): boolean {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.or(['modules'])) return false;
|
||||
if (!msg.text) return false
|
||||
if (!msg.or(["modules"])) return false
|
||||
|
||||
let text = '```\n';
|
||||
let text = "```\n"
|
||||
|
||||
for (const m of this.ai.modules) {
|
||||
text += `${m.name}\n`;
|
||||
for (const m of this.nullcatChan.modules) {
|
||||
text += `${m.name}\n`
|
||||
}
|
||||
|
||||
text += '```';
|
||||
text += "```"
|
||||
|
||||
msg.reply(text, {
|
||||
immediate: true
|
||||
});
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
private version(msg: Message): boolean {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.or(['v', 'version', 'バージョン'])) return false;
|
||||
if (!msg.text) return false
|
||||
if (!msg.or(["v", "version", "バージョン"])) return false
|
||||
|
||||
msg.reply(`\`\`\`\nv${this.ai.version}\n\`\`\``, {
|
||||
immediate: true
|
||||
});
|
||||
msg.reply(`\`\`\`\nv${this.nullcatChan.version}\n\`\`\``, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(key: any, msg: Message, data: any) {
|
||||
if (msg.text == null) return;
|
||||
if (msg.text == null) return
|
||||
|
||||
const done = () => {
|
||||
msg.reply(serifs.core.setNameOk(msg.friend.name));
|
||||
this.unsubscribeReply(key);
|
||||
};
|
||||
msg.reply(serifs.core.setNameOk(msg.friend.name))
|
||||
this.unsubscribeReply(key)
|
||||
}
|
||||
|
||||
if (msg.text.includes('はい')) {
|
||||
msg.friend.updateName(data.name + 'さん');
|
||||
done();
|
||||
} else if (msg.text.includes('いいえ')) {
|
||||
msg.friend.updateName(data.name);
|
||||
done();
|
||||
if (msg.text.includes("うん")) {
|
||||
msg.friend.updateName(data.name + "ちゃん")
|
||||
done()
|
||||
} else if (msg.text.includes("いいえ")) {
|
||||
msg.friend.updateName(data.name)
|
||||
done()
|
||||
} else {
|
||||
msg.reply(serifs.core.yesOrNo).then(reply => {
|
||||
this.subscribeReply(msg.userId, msg.isDm, reply.id, data);
|
||||
});
|
||||
msg.reply(serifs.core.yesOrNo).then((reply) => {
|
||||
this.subscribeReply(msg.userId, msg.isDm, reply.id, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +1,69 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { parse } from 'twemoji-parser';
|
||||
const delay = require('timeout-as-promise');
|
||||
import { Note } from "../../misskey/note"
|
||||
import Module from "@/module"
|
||||
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';
|
||||
import Module from '@/module';
|
||||
import Stream from '@/stream';
|
||||
import includes from '@/utils/includes';
|
||||
const gomamayo = require("gomamayo-js")
|
||||
|
||||
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
|
||||
public install() {
|
||||
this.htl = this.ai.connection.useSharedConnection('homeTimeline');
|
||||
this.htl.on('note', this.onNote);
|
||||
this.htl = this.nullcatChan.connection.useSharedConnection("homeTimeline")
|
||||
this.htl.on("note", this.onNote)
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: Note) {
|
||||
if (note.reply != null) return;
|
||||
if (note.text == null) return;
|
||||
if (note.text.includes('@')) return; // (自分または他人問わず)メンションっぽかったらreject
|
||||
if (note.reply != null) return
|
||||
if (note.text == null) return
|
||||
if (note.text.includes("@")) return // (自分または他人問わず)メンションっぽかったらreject
|
||||
|
||||
const react = async (reaction: string, immediate = false) => {
|
||||
if (!immediate) {
|
||||
await delay(1500);
|
||||
await delay(1500)
|
||||
}
|
||||
this.ai.api('notes/reactions/create', {
|
||||
this.nullcatChan.api("notes/reactions/create", {
|
||||
noteId: note.id,
|
||||
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]);
|
||||
reaction: reaction,
|
||||
})
|
||||
}
|
||||
|
||||
const emojis = parse(note.text).map(x => x.text);
|
||||
if (emojis.length > 0) {
|
||||
// 絵文字が複数種類ある場合はキャンセル
|
||||
if (!emojis.every((val, i, arr) => val === arr[0])) return;
|
||||
|
||||
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('🙌');
|
||||
if (await gomamayo.find(note.text)) return react(":bikkuribikkuri_:")
|
||||
if (includes(note.text, ["ぬるきゃっとちゃん", "ぬるきゃぼっと", "ぬるきゃっとぼっと"])) return react(":bibibi_nullcatchan:")
|
||||
if (
|
||||
includes(note.text, [
|
||||
"ねむい",
|
||||
"ねむたい",
|
||||
"ねたい",
|
||||
"ねれない",
|
||||
"ねれん",
|
||||
"ねれぬ",
|
||||
"ふむ",
|
||||
"つら",
|
||||
"死に",
|
||||
"つかれた",
|
||||
"疲れた",
|
||||
"しにたい",
|
||||
"きえたい",
|
||||
"消えたい",
|
||||
"やだ",
|
||||
"いやだ",
|
||||
"なきそう",
|
||||
"泣きそう",
|
||||
"辛い",
|
||||
])
|
||||
)
|
||||
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 Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'follow';
|
||||
public readonly name = "follow"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook
|
||||
};
|
||||
mentionHook: this.mentionHook,
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
if (msg.text && msg.includes(['フォロー', 'フォロバ', 'follow me'])) {
|
||||
if (msg.text && msg.includes(["フォロー", "フォロバ", "follow me"])) {
|
||||
if (!msg.user.isFollowing) {
|
||||
this.ai.api('following/create', {
|
||||
this.nullcatChan.api("following/create", {
|
||||
userId: msg.userId,
|
||||
});
|
||||
})
|
||||
msg.reply("これからよろしくね!", { immediate: true })
|
||||
return {
|
||||
reaction: msg.friend.love >= 0 ? 'like' : null
|
||||
};
|
||||
reaction: msg.friend.love >= 0 ? ":love_nullcatchan:" : null,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
reaction: msg.friend.love >= 0 ? 'hmm' : null
|
||||
};
|
||||
reaction: msg.friend.love >= 0 ? ":love_nullcatchan:" : null,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,36 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs from '@/serifs';
|
||||
import * as seedrandom from 'seedrandom';
|
||||
import { genItem } from '@/vocabulary';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import { genItem } from "@/vocabulary"
|
||||
import autobind from "autobind-decorator"
|
||||
import * as seedrandom from "seedrandom"
|
||||
|
||||
export const blessing = [
|
||||
'藍吉',
|
||||
'ヨタ吉',
|
||||
'ゼタ吉',
|
||||
'エクサ吉',
|
||||
'ペタ吉',
|
||||
'テラ吉',
|
||||
'ギガ吉',
|
||||
'メガ吉',
|
||||
'キロ吉',
|
||||
'ヘクト吉',
|
||||
'デカ吉',
|
||||
'デシ吉',
|
||||
'センチ吉',
|
||||
'ミリ吉',
|
||||
'マイクロ吉',
|
||||
'ナノ吉',
|
||||
'ピコ吉',
|
||||
'フェムト吉',
|
||||
'アト吉',
|
||||
'ゼプト吉',
|
||||
'ヨクト吉',
|
||||
'超吉',
|
||||
'大大吉',
|
||||
'大吉',
|
||||
'吉',
|
||||
'中吉',
|
||||
'小吉',
|
||||
'凶',
|
||||
'大凶',
|
||||
];
|
||||
export const blessing = ["にゃん吉🐈", "みゃ~吉🐾", "ぬるきゃっと吉:love_nullcatchan:", "なんかすごい吉✨", "特大吉✨", "大大吉🎊", "大吉🎊", "吉🎉", "中吉🎉", "小吉🎉", "凶🗿", "大凶🗿"]
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'fortune';
|
||||
public readonly name = "fortune"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook
|
||||
};
|
||||
mentionHook: this.mentionHook,
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
if (msg.includes(['占', 'うらな', '運勢', 'おみくじ'])) {
|
||||
const date = new Date();
|
||||
const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}@${msg.userId}`;
|
||||
const rng = seedrandom(seed);
|
||||
const omikuji = blessing[Math.floor(rng() * blessing.length)];
|
||||
const item = genItem(rng);
|
||||
msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, {
|
||||
cw: serifs.fortune.cw(msg.friend.name)
|
||||
});
|
||||
return true;
|
||||
if (msg.includes(["占", "うらな", "運勢", "おみくじ"])) {
|
||||
const date = new Date()
|
||||
const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}@${msg.userId}`
|
||||
const rng = seedrandom(seed)
|
||||
const omikuji = blessing[Math.floor(rng() * blessing.length)]
|
||||
const item = genItem(rng)
|
||||
msg.reply(`**${omikuji}**\nラッキーアイテム: ${item}`, {
|
||||
cw: serifs.fortune.cw(msg.friend.name),
|
||||
})
|
||||
return true
|
||||
} 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 * as loki from 'lokijs';
|
||||
import Module from '@/module';
|
||||
import config from '@/config';
|
||||
import serifs from '@/serifs';
|
||||
import { mecab } from './mecab';
|
||||
import config from "@/config"
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import NGWord from "../../ng-words";
|
||||
import autobind from "autobind-decorator"
|
||||
import * as loki from "lokijs"
|
||||
import { mecab } from "./mecab"
|
||||
|
||||
function kanaToHira(str: string) {
|
||||
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
||||
const chr = match.charCodeAt(0) - 0x60;
|
||||
return String.fromCharCode(chr);
|
||||
});
|
||||
return str.replace(/[\u30a1-\u30f6]/g, (match) => {
|
||||
const chr = match.charCodeAt(0) - 0x60
|
||||
return String.fromCharCode(chr)
|
||||
})
|
||||
}
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'keyword';
|
||||
public readonly name = "keyword"
|
||||
|
||||
private learnedKeywords: loki.Collection<{
|
||||
keyword: string;
|
||||
learnedAt: number;
|
||||
}>;
|
||||
keyword: string
|
||||
learnedAt: number
|
||||
}>
|
||||
|
||||
private ngWord = new NGWord()
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
if (!config.keywordEnabled) return {};
|
||||
if (!config.keywordEnabled) return {}
|
||||
|
||||
this.learnedKeywords = this.ai.getCollection('_keyword_learnedKeywords', {
|
||||
indices: ['userId']
|
||||
});
|
||||
this.learnedKeywords = this.nullcatChan.getCollection("_keyword_learnedKeywords", {
|
||||
indices: ["userId"],
|
||||
})
|
||||
|
||||
setInterval(this.learn, 1000 * 60 * 60);
|
||||
setInterval(this.learn, 1000 * 60 * 45)
|
||||
|
||||
return {};
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async learn() {
|
||||
const tl = await this.ai.api('notes/local-timeline', {
|
||||
limit: 30
|
||||
});
|
||||
const tl = await this.nullcatChan.api("notes/hybrid-timeline", {
|
||||
limit: 30,
|
||||
})
|
||||
|
||||
const interestedNotes = tl.filter(note =>
|
||||
note.userId !== this.ai.account.id &&
|
||||
note.text != null &&
|
||||
note.cw == null);
|
||||
const interestedNotes = tl.filter((note) => note.userId !== this.nullcatChan.account.id && note.text != null && note.cw == null)
|
||||
|
||||
let keywords: string[][] = [];
|
||||
let keywords: string[][] = []
|
||||
|
||||
for (const note of interestedNotes) {
|
||||
const tokens = await mecab(note.text, config.mecab, config.mecabDic);
|
||||
const keywordsInThisNote = tokens.filter(token => token[2] == '固有名詞' && token[8] != null);
|
||||
keywords = keywords.concat(keywordsInThisNote);
|
||||
const tokens = await mecab(note.text, config.mecab, config.mecabDic)
|
||||
const keywordsInThisNote = tokens.filter((token) => token[2] == "固有名詞" && token[8] != null)
|
||||
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 keyword = keywords.sort((a, b) => a[0].length < b[0].length ? 1 : -1)[rnd];
|
||||
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 exist = this.learnedKeywords.findOne({
|
||||
keyword: keyword[0]
|
||||
});
|
||||
keyword: keyword[0],
|
||||
})
|
||||
|
||||
let text: string;
|
||||
let text: string
|
||||
|
||||
if (exist) {
|
||||
return;
|
||||
return
|
||||
} else {
|
||||
this.learnedKeywords.insertOne({
|
||||
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({
|
||||
text: text
|
||||
});
|
||||
this.nullcatChan.post({
|
||||
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 * as util from 'util';
|
||||
import * as stream from 'stream';
|
||||
import * as memoryStreams from 'memory-streams';
|
||||
import { EOL } from 'os';
|
||||
import { spawn } from "child_process"
|
||||
import * as memoryStreams from "memory-streams"
|
||||
import { EOL } from "os"
|
||||
import * as stream from "stream"
|
||||
import * as util from "util"
|
||||
|
||||
const pipeline = util.promisify(stream.pipeline);
|
||||
const pipeline = util.promisify(stream.pipeline)
|
||||
|
||||
/**
|
||||
* Run MeCab
|
||||
@ -12,34 +12,34 @@ const pipeline = util.promisify(stream.pipeline);
|
||||
* @param mecab mecab bin
|
||||
* @param dic mecab dictionaly path
|
||||
*/
|
||||
export async function mecab(text: string, mecab = 'mecab', dic?: string): Promise<string[][]> {
|
||||
const args: string[] = [];
|
||||
if (dic) args.push('-d', dic);
|
||||
export async function mecab(text: string, mecab = "mecab", dic?: string): Promise<string[][]> {
|
||||
const args: string[] = []
|
||||
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) {
|
||||
if (line === 'EOS') break;
|
||||
const [word, value = ''] = line.split('\t');
|
||||
const array = value.split(',');
|
||||
array.unshift(word);
|
||||
results.push(array);
|
||||
if (line === "EOS") break
|
||||
const [word, value = ""] = line.split("\t")
|
||||
const array = value.split(",")
|
||||
array.unshift(word)
|
||||
results.push(array)
|
||||
}
|
||||
|
||||
return results;
|
||||
return results
|
||||
}
|
||||
|
||||
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.end();
|
||||
mecab.stdin.write(stdin)
|
||||
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 Module from '@/module';
|
||||
import serifs from '@/serifs';
|
||||
import { genItem } from '@/vocabulary';
|
||||
import config from '@/config';
|
||||
import config from "@/config"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'noting';
|
||||
public readonly name = "noting"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
if (config.notingEnabled === false) return {};
|
||||
if (config.notingEnabled === false) return {}
|
||||
|
||||
setInterval(() => {
|
||||
if (Math.random() < 0.04) {
|
||||
this.post();
|
||||
if (Math.random() < 0.1) {
|
||||
this.post()
|
||||
}
|
||||
}, 1000 * 60 * 10);
|
||||
}, 1000 * 60 * 10)
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private post() {
|
||||
const 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 notes = serifs.noting.notes
|
||||
|
||||
const note = notes[Math.floor(Math.random() * notes.length)];
|
||||
const note = notes[Math.floor(Math.random() * notes.length)]
|
||||
|
||||
// TODO: 季節に応じたセリフ
|
||||
|
||||
this.ai.post({
|
||||
text: typeof note === 'function' ? note() : note
|
||||
});
|
||||
this.nullcatChan.post({
|
||||
text: note,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'ping';
|
||||
public readonly name = "ping"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook
|
||||
};
|
||||
mentionHook: this.mentionHook,
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
if (msg.text && msg.text.includes('ping')) {
|
||||
msg.reply('PONG!', {
|
||||
immediate: true
|
||||
});
|
||||
return true;
|
||||
if (msg.text && msg.text.includes("ping")) {
|
||||
msg.reply("$[x2 :bibibi_nullcatchan:]", {
|
||||
immediate: true,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,178 +1,172 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import * as loki from 'lokijs';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs, { getSerif } from '@/serifs';
|
||||
import { acct } from '@/utils/acct';
|
||||
import config from '@/config';
|
||||
import config from "@/config"
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import serifs, { getSerif } from "@/serifs"
|
||||
import { acct } from "../../../../NullcatChan-old/src/utils/acct"
|
||||
import autobind from "autobind-decorator"
|
||||
import * as loki from "lokijs"
|
||||
|
||||
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
|
||||
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 1
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'reminder';
|
||||
public readonly name = "reminder"
|
||||
|
||||
private reminds: loki.Collection<{
|
||||
userId: string;
|
||||
id: string;
|
||||
isDm: boolean;
|
||||
thing: string | null;
|
||||
quoteId: string | null;
|
||||
times: number; // 催促した回数(使うのか?)
|
||||
createdAt: number;
|
||||
}>;
|
||||
userId: string
|
||||
id: string
|
||||
isDm: boolean
|
||||
thing: string | null
|
||||
quoteId: string | null
|
||||
times: number // 催促した回数(使うのか?)
|
||||
createdAt: number
|
||||
}>
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
this.reminds = this.ai.getCollection('reminds', {
|
||||
indices: ['userId', 'id']
|
||||
});
|
||||
this.reminds = this.nullcatChan.getCollection("reminds", {
|
||||
indices: ["userId", "id"],
|
||||
})
|
||||
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
contextHook: this.contextHook,
|
||||
timeoutCallback: this.timeoutCallback,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
let text = msg.extractedText.toLowerCase();
|
||||
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
|
||||
let text = msg.extractedText.toLowerCase()
|
||||
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({
|
||||
userId: msg.userId,
|
||||
});
|
||||
})
|
||||
|
||||
const getQuoteLink = id => `[${id}](${config.host}/notes/${id})`;
|
||||
|
||||
msg.reply(serifs.reminder.reminds + '\n' + reminds.map(remind => `・${remind.thing ? remind.thing : getQuoteLink(remind.quoteId)}`).join('\n'));
|
||||
return true;
|
||||
const getQuoteLink = (id) => `[${id}](${config.host}/notes/${id})`
|
||||
if(reminds.length === 0) {
|
||||
msg.reply(serifs.reminder.none)
|
||||
}
|
||||
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(.+)/)) {
|
||||
text = text.replace(/^(.+?)\s/, '');
|
||||
text = text.replace(/^(.+?)\s/, "")
|
||||
} else {
|
||||
text = '';
|
||||
text = ""
|
||||
}
|
||||
|
||||
const separatorIndex = text.indexOf(' ') > -1 ? text.indexOf(' ') : text.indexOf('\n');
|
||||
const thing = text.substr(separatorIndex + 1).trim();
|
||||
const separatorIndex = text.indexOf(" ") > -1 ? text.indexOf(" ") : text.indexOf("\n")
|
||||
const thing = text.substr(separatorIndex + 1).trim()
|
||||
|
||||
if (thing === '' && msg.quoteId == null || msg.visibility === 'followers') {
|
||||
msg.reply(serifs.reminder.invalid);
|
||||
return {
|
||||
reaction: '🆖',
|
||||
immediate: true,
|
||||
};
|
||||
if (thing === "" && msg.quoteId == null) {
|
||||
msg.reply(serifs.reminder.invalid)
|
||||
return true
|
||||
}
|
||||
|
||||
const remind = this.reminds.insertOne({
|
||||
id: msg.id,
|
||||
userId: msg.userId,
|
||||
isDm: msg.isDm,
|
||||
thing: thing === '' ? null : thing,
|
||||
thing: thing === "" ? null : thing,
|
||||
quoteId: msg.quoteId,
|
||||
times: 0,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
})
|
||||
|
||||
// メンションをsubscribe
|
||||
this.subscribeReply(remind!.id, msg.isDm, msg.isDm ? msg.userId : msg.id, {
|
||||
id: remind!.id
|
||||
});
|
||||
id: remind!.id,
|
||||
})
|
||||
|
||||
if (msg.quoteId) {
|
||||
// 引用元をsubscribe
|
||||
this.subscribeReply(remind!.id, false, msg.quoteId, {
|
||||
id: remind!.id
|
||||
});
|
||||
id: remind!.id,
|
||||
})
|
||||
}
|
||||
|
||||
// タイマーセット
|
||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||
id: remind!.id,
|
||||
});
|
||||
})
|
||||
|
||||
return {
|
||||
reaction: '🆗',
|
||||
reaction: ":ok:",
|
||||
immediate: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(key: any, msg: Message, data: any) {
|
||||
if (msg.text == null) return;
|
||||
if (msg.text == null) return
|
||||
|
||||
const remind = this.reminds.findOne({
|
||||
id: data.id,
|
||||
});
|
||||
})
|
||||
|
||||
if (remind == null) {
|
||||
this.unsubscribeReply(key);
|
||||
return;
|
||||
this.unsubscribeReply(key)
|
||||
return
|
||||
}
|
||||
|
||||
const done = msg.includes(['done', 'やった', 'やりました', 'はい']);
|
||||
const cancel = msg.includes(['やめる', 'やめた', 'キャンセル']);
|
||||
const isOneself = msg.userId === remind.userId;
|
||||
const done = msg.includes(["done", "やった", "やりました", "はい", "どね", "ドネ"])
|
||||
const cancel = msg.includes(["やめる", "やめた", "キャンセル"])
|
||||
|
||||
if ((done || cancel) && isOneself) {
|
||||
this.unsubscribeReply(key);
|
||||
this.reminds.remove(remind);
|
||||
msg.reply(done ? getSerif(serifs.reminder.done(msg.friend.name)) : serifs.reminder.cancel);
|
||||
return;
|
||||
} else if (isOneself === false) {
|
||||
msg.reply(serifs.reminder.doneFromInvalidUser);
|
||||
return;
|
||||
if (done || cancel) {
|
||||
this.unsubscribeReply(key)
|
||||
this.reminds.remove(remind)
|
||||
msg.reply(done ? getSerif(serifs.reminder.done(msg.friend.name)) : serifs.reminder.cancel)
|
||||
return
|
||||
} else {
|
||||
if (msg.isDm) this.unsubscribeReply(key);
|
||||
return false;
|
||||
if (msg.isDm) this.unsubscribeReply(key)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async timeoutCallback(data) {
|
||||
const remind = this.reminds.findOne({
|
||||
id: data.id
|
||||
});
|
||||
if (remind == null) return;
|
||||
id: data.id,
|
||||
})
|
||||
if (remind == null) return
|
||||
|
||||
remind.times++;
|
||||
this.reminds.update(remind);
|
||||
remind.times++
|
||||
this.reminds.update(remind)
|
||||
|
||||
const friend = this.ai.lookupFriend(remind.userId);
|
||||
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
const friend = this.nullcatChan.lookupFriend(remind.userId)
|
||||
if (friend == null) return // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
|
||||
let reply;
|
||||
let reply
|
||||
if (remind.isDm) {
|
||||
this.ai.sendMessage(friend.userId, {
|
||||
text: serifs.reminder.notifyWithThing(remind.thing, friend.name)
|
||||
});
|
||||
this.nullcatChan.sendMessage(friend.userId, {
|
||||
text: serifs.reminder.notifyWithThing(remind.thing, friend.name),
|
||||
})
|
||||
} else {
|
||||
try {
|
||||
reply = await this.ai.post({
|
||||
reply = await this.nullcatChan.post({
|
||||
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) {
|
||||
// renote対象が消されていたらリマインダー解除
|
||||
if (err.statusCode === 400) {
|
||||
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);
|
||||
this.reminds.remove(remind);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
// TODO: renote対象が消されていたらリマインダー解除
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.subscribeReply(remind.id, remind.isDm, remind.isDm ? remind.userId : reply.id, {
|
||||
id: remind.id
|
||||
});
|
||||
id: remind.id,
|
||||
})
|
||||
|
||||
// タイマーセット
|
||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||
id: remind.id,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +1,79 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Module from '@/module';
|
||||
import serifs from '@/serifs';
|
||||
import config from '@/config';
|
||||
import config from "@/config"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'server';
|
||||
public readonly name = "server"
|
||||
|
||||
private connection?: any;
|
||||
private recentStat: any;
|
||||
private warned = false;
|
||||
private lastWarnedAt: number;
|
||||
private connection?: any
|
||||
private recentStat: any
|
||||
private warned = false
|
||||
private lastWarnedAt: number
|
||||
|
||||
/**
|
||||
* 1秒毎のログ1分間分
|
||||
*/
|
||||
private statsLogs: any[] = [];
|
||||
private statsLogs: any[] = []
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
if (!config.serverMonitoring) return {};
|
||||
if (!config.serverMonitoring) return {}
|
||||
|
||||
this.connection = this.ai.connection.useSharedConnection('serverStats');
|
||||
this.connection.on('stats', this.onStats);
|
||||
this.connection = this.nullcatChan.connection.useSharedConnection("serverStats")
|
||||
this.connection.on("stats", this.onStats)
|
||||
|
||||
setInterval(() => {
|
||||
this.statsLogs.unshift(this.recentStat);
|
||||
if (this.statsLogs.length > 60) this.statsLogs.pop();
|
||||
}, 1000);
|
||||
this.statsLogs.unshift(this.recentStat)
|
||||
if (this.statsLogs.length > 60) this.statsLogs.pop()
|
||||
}, 1000)
|
||||
|
||||
setInterval(() => {
|
||||
this.check();
|
||||
}, 3000);
|
||||
this.check()
|
||||
}, 3000)
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
@autobind
|
||||
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 cpuPercentage = average(cpuPercentages);
|
||||
const cpuPercentages = this.statsLogs.map((s) => (s && (s.cpu_usage || s.cpu) * 100) || 0)
|
||||
const cpuPercentage = average(cpuPercentages)
|
||||
if (cpuPercentage >= 70) {
|
||||
this.warn();
|
||||
this.warn()
|
||||
} else if (cpuPercentage <= 30) {
|
||||
this.warned = false;
|
||||
this.warned = false
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onStats(stats: any) {
|
||||
this.recentStat = stats;
|
||||
this.recentStat = stats
|
||||
}
|
||||
|
||||
@autobind
|
||||
private warn() {
|
||||
//#region 前に警告したときから一旦落ち着いた状態を経験していなければ警告しない
|
||||
// 常に負荷が高いようなサーバーで無限に警告し続けるのを防ぐため
|
||||
if (this.warned) return;
|
||||
if (this.warned) return
|
||||
//#endregion
|
||||
|
||||
//#region 前の警告から1時間経っていない場合は警告しない
|
||||
const now = Date.now();
|
||||
const now = Date.now()
|
||||
|
||||
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
|
||||
|
||||
this.ai.post({
|
||||
text: serifs.server.cpu
|
||||
});
|
||||
this.nullcatChan.post({
|
||||
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 serifs from '@/serifs';
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'sleepReport';
|
||||
public readonly name = "sleepReport"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
this.report();
|
||||
this.report()
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
@autobind
|
||||
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) {
|
||||
this.ai.post({
|
||||
text: serifs.sleepReport.report(Math.round(sleepHours))
|
||||
});
|
||||
this.nullcatChan.post({
|
||||
text: serifs.sleepReport.report(Math.round(sleepHours)),
|
||||
})
|
||||
} else {
|
||||
this.ai.post({
|
||||
text: serifs.sleepReport.reportUtatane
|
||||
});
|
||||
this.nullcatChan.post({
|
||||
text: serifs.sleepReport.reportUtatane,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { HandlerResult } from '@/ai';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs, { getSerif } from '@/serifs';
|
||||
import getDate from '@/utils/get-date';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import { HandlerResult } from "../../nullcat-chan"
|
||||
import serifs, { getSerif } from "@/serifs"
|
||||
import getDate from "../../../../NullcatChan-old/src/utils/get-date"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'talk';
|
||||
public readonly name = "talk"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
if (!msg.text) return false;
|
||||
if (!msg.text) return false
|
||||
|
||||
return (
|
||||
this.greet(msg) ||
|
||||
@ -30,304 +30,314 @@ export default class extends Module {
|
||||
this.humu(msg) ||
|
||||
this.batou(msg) ||
|
||||
this.itai(msg) ||
|
||||
this.turai(msg) ||
|
||||
this.kurusii(msg) ||
|
||||
this.ote(msg) ||
|
||||
this.ponkotu(msg) ||
|
||||
this.rmrf(msg) ||
|
||||
this.shutdown(msg)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@autobind
|
||||
private greet(msg: Message): boolean {
|
||||
if (msg.text == null) return false;
|
||||
if (msg.text == null) return false
|
||||
|
||||
const incLove = () => {
|
||||
//#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;
|
||||
msg.friend.setPerModulesData(this, data);
|
||||
data.lastGreetedAt = today
|
||||
msg.friend.setPerModulesData(this, data)
|
||||
|
||||
msg.friend.incLove();
|
||||
msg.friend.incLove()
|
||||
//#endregion
|
||||
};
|
||||
}
|
||||
|
||||
// 末尾のエクスクラメーションマーク
|
||||
const tension = (msg.text.match(/[!!]{2,}/g) || [''])
|
||||
.sort((a, b) => a.length < b.length ? 1 : -1)[0]
|
||||
.substr(1);
|
||||
const tension = (msg.text.match(/[!!]{2,}/g) || [""]).sort((a, b) => (a.length < b.length ? 1 : -1))[0].substr(1)
|
||||
|
||||
if (msg.includes(['こんにちは', 'こんにちわ'])) {
|
||||
msg.reply(serifs.core.hello(msg.friend.name));
|
||||
incLove();
|
||||
return true;
|
||||
if (msg.includes(["こんにちは", "こんにちわ"])) {
|
||||
msg.reply(serifs.core.hello(msg.friend.name))
|
||||
incLove()
|
||||
return true
|
||||
}
|
||||
|
||||
if (msg.includes(['こんばんは', 'こんばんわ'])) {
|
||||
msg.reply(serifs.core.helloNight(msg.friend.name));
|
||||
incLove();
|
||||
return true;
|
||||
if (msg.includes(["こんばんは", "こんばんわ"])) {
|
||||
msg.reply(serifs.core.helloNight(msg.friend.name))
|
||||
incLove()
|
||||
return true
|
||||
}
|
||||
|
||||
if (msg.includes(['おは', 'おっは', 'お早う'])) {
|
||||
msg.reply(serifs.core.goodMorning(tension, msg.friend.name));
|
||||
incLove();
|
||||
return true;
|
||||
if (msg.includes(["おは", "おっは", "お早う"])) {
|
||||
msg.reply(serifs.core.goodMorning(tension, msg.friend.name))
|
||||
incLove()
|
||||
return true
|
||||
}
|
||||
|
||||
if (msg.includes(['おやすみ', 'お休み'])) {
|
||||
msg.reply(serifs.core.goodNight(msg.friend.name));
|
||||
incLove();
|
||||
return true;
|
||||
if (msg.includes(["おやすみ", "お休み"])) {
|
||||
msg.reply(serifs.core.goodNight(msg.friend.name))
|
||||
incLove()
|
||||
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.friend.love >= 7
|
||||
? serifs.core.itterassyai.love(msg.friend.name)
|
||||
: serifs.core.itterassyai.normal(msg.friend.name));
|
||||
incLove();
|
||||
return true;
|
||||
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
|
||||
}
|
||||
|
||||
if (msg.includes(['ただいま'])) {
|
||||
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;
|
||||
return false
|
||||
}
|
||||
|
||||
@autobind
|
||||
private erait(msg: Message): boolean {
|
||||
const match = msg.extractedText.match(/(.+?)た(から|ので)(褒|ほ)めて/);
|
||||
const match = msg.extractedText.match(/(.+?)た(から|ので)(褒|ほ)めて/)
|
||||
if (match) {
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match[1], msg.friend.name)));
|
||||
return true;
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match[1], msg.friend.name)))
|
||||
return true
|
||||
}
|
||||
|
||||
const match2 = msg.extractedText.match(/(.+?)る(から|ので)(褒|ほ)めて/);
|
||||
const match2 = msg.extractedText.match(/(.+?)る(から|ので)(褒|ほ)めて/)
|
||||
if (match2) {
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match2[1], msg.friend.name)));
|
||||
return true;
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match2[1], msg.friend.name)))
|
||||
return true
|
||||
}
|
||||
|
||||
const match3 = msg.extractedText.match(/(.+?)だから(褒|ほ)めて/);
|
||||
const match3 = msg.extractedText.match(/(.+?)だから(褒|ほ)めて/)
|
||||
if (match3) {
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match3[1], msg.friend.name)));
|
||||
return true;
|
||||
msg.reply(getSerif(serifs.core.erait.specify(match3[1], msg.friend.name)))
|
||||
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
|
||||
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
|
||||
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回だけ親愛度を上げる(嫌われてない場合のみ)
|
||||
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) {
|
||||
data.lastNadenadeAt = today;
|
||||
msg.friend.setPerModulesData(this, data);
|
||||
data.lastNadenadeAt = today
|
||||
msg.friend.setPerModulesData(this, data)
|
||||
|
||||
msg.friend.incLove();
|
||||
msg.friend.incLove()
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
msg.reply(getSerif(
|
||||
msg.friend.love >= 10 ? serifs.core.nadenade.love3 :
|
||||
msg.friend.love >= 5 ? serifs.core.nadenade.love2 :
|
||||
msg.friend.love <= -15 ? 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
|
||||
));
|
||||
msg.reply(
|
||||
getSerif(
|
||||
msg.friend.love >= 10
|
||||
? serifs.core.nadenade.love3
|
||||
: msg.friend.love >= 5
|
||||
? serifs.core.nadenade.love2
|
||||
: msg.friend.love <= -15
|
||||
? 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
|
||||
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.friend.love >= 5 ? serifs.core.kawaii.love :
|
||||
msg.friend.love <= -3 ? serifs.core.kawaii.hate :
|
||||
serifs.core.kawaii.normal));
|
||||
msg.reply(getSerif(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
|
||||
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.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.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)
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
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分経ってない場合は返信しない
|
||||
// これは、「ハグ」と言って「ぎゅー」と返信したとき、相手が
|
||||
// それに対してさらに「ぎゅー」と返信するケースがあったため。
|
||||
// そうするとその「ぎゅー」に対してもマッチするため、また
|
||||
// 藍がそれに返信してしまうことになり、少し不自然になる。
|
||||
// ぬるきゃっとちゃんがそれに返信してしまうことになり、少し不自然になる。
|
||||
// これを防ぐために前にハグしてから少し時間が経っていないと
|
||||
// 返信しないようにする
|
||||
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 (now - data.lastHuggedAt < (1000 * 60)) return true;
|
||||
if (now - data.lastHuggedAt < 1000 * 60) return true
|
||||
}
|
||||
|
||||
data.lastHuggedAt = now;
|
||||
msg.friend.setPerModulesData(this, data);
|
||||
data.lastHuggedAt = now
|
||||
msg.friend.setPerModulesData(this, data)
|
||||
//#endregion
|
||||
|
||||
msg.reply(
|
||||
msg.friend.love >= 5 ? serifs.core.hug.love :
|
||||
msg.friend.love <= -3 ? serifs.core.hug.hate :
|
||||
serifs.core.hug.normal);
|
||||
msg.reply(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
|
||||
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.friend.love >= 5 ? serifs.core.humu.love :
|
||||
msg.friend.love <= -3 ? serifs.core.humu.hate :
|
||||
serifs.core.humu.normal);
|
||||
msg.reply(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
|
||||
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.friend.love >= 5 ? serifs.core.batou.love :
|
||||
msg.friend.love <= -5 ? serifs.core.batou.hate :
|
||||
serifs.core.batou.normal);
|
||||
msg.reply(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
|
||||
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
|
||||
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.friend.love >= 10 ? serifs.core.ote.love2 :
|
||||
msg.friend.love >= 5 ? serifs.core.ote.love1 :
|
||||
serifs.core.ote.normal);
|
||||
msg.reply(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
|
||||
private ponkotu(msg: Message): boolean | HandlerResult {
|
||||
if (!msg.includes(['ぽんこつ'])) return false;
|
||||
if (!msg.includes(["ぽんこつ"])) return false
|
||||
|
||||
msg.friend.decLove();
|
||||
msg.friend.decLove()
|
||||
|
||||
return {
|
||||
reaction: 'angry'
|
||||
};
|
||||
reaction: "angry",
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
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 {
|
||||
reaction: 'angry'
|
||||
};
|
||||
reaction: "angry",
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
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 {
|
||||
reaction: 'confused'
|
||||
};
|
||||
reaction: "confused",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,75 +1,72 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs from '@/serifs';
|
||||
import Message from "@/message"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'timer';
|
||||
public readonly name = "timer"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
timeoutCallback: this.timeoutCallback,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
const secondsQuery = (msg.text || '').match(/([0-9]+)秒/);
|
||||
const minutesQuery = (msg.text || '').match(/([0-9]+)分/);
|
||||
const hoursQuery = (msg.text || '').match(/([0-9]+)時間/);
|
||||
const secondsQuery = (msg.text || "").match(/([0-9]+)秒/)
|
||||
const minutesQuery = (msg.text || "").match(/([0-9]+)分/)
|
||||
const hoursQuery = (msg.text || "").match(/([0-9]+)時間/)
|
||||
|
||||
const seconds = secondsQuery ? parseInt(secondsQuery[1], 10) : 0;
|
||||
const minutes = minutesQuery ? parseInt(minutesQuery[1], 10) : 0;
|
||||
const hours = hoursQuery ? parseInt(hoursQuery[1], 10) : 0;
|
||||
const seconds = secondsQuery ? parseInt(secondsQuery[1], 10) : 0
|
||||
const minutes = minutesQuery ? parseInt(minutesQuery[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) {
|
||||
msg.reply(serifs.timer.invalid);
|
||||
return true;
|
||||
if (seconds + minutes + hours == 0) {
|
||||
msg.reply(serifs.timer.invalid)
|
||||
return true
|
||||
}
|
||||
|
||||
const time =
|
||||
(1000 * seconds) +
|
||||
(1000 * 60 * minutes) +
|
||||
(1000 * 60 * 60 * hours);
|
||||
const time = 1000 * seconds + 1000 * 60 * minutes + 1000 * 60 * 60 * hours
|
||||
|
||||
if (time > 86400000) {
|
||||
msg.reply(serifs.timer.tooLong);
|
||||
return true;
|
||||
msg.reply(serifs.timer.tooLong)
|
||||
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, {
|
||||
isDm: msg.isDm,
|
||||
msgId: msg.id,
|
||||
userId: msg.friend.userId,
|
||||
time: str
|
||||
});
|
||||
time: str,
|
||||
})
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@autobind
|
||||
private timeoutCallback(data) {
|
||||
const friend = this.ai.lookupFriend(data.userId);
|
||||
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
const text = serifs.timer.notify(data.time, friend.name);
|
||||
const friend = this.nullcatChan.lookupFriend(data.userId)
|
||||
if (friend == null) return // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
const text = serifs.timer.notify(data.time, friend.name)
|
||||
if (data.isDm) {
|
||||
this.ai.sendMessage(friend.userId, {
|
||||
text: text
|
||||
});
|
||||
this.nullcatChan.sendMessage(friend.userId, {
|
||||
text: text,
|
||||
})
|
||||
} else {
|
||||
this.ai.post({
|
||||
this.nullcatChan.post({
|
||||
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 Module from '@/module';
|
||||
import Friend from '@/friend';
|
||||
import serifs from '@/serifs';
|
||||
import Friend from "@/friend"
|
||||
import Module from "@/module"
|
||||
import serifs from "@/serifs"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'valentine';
|
||||
public readonly name = "valentine"
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
this.crawleValentine();
|
||||
setInterval(this.crawleValentine, 1000 * 60 * 3);
|
||||
this.crawleValentine()
|
||||
setInterval(this.crawleValentine, 1000 * 60 * 3)
|
||||
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,33 +19,33 @@ export default class extends Module {
|
||||
*/
|
||||
@autobind
|
||||
private crawleValentine() {
|
||||
const now = new Date();
|
||||
const now = new Date()
|
||||
|
||||
const isValentine = now.getMonth() == 1 && now.getDate() == 14;
|
||||
if (!isValentine) return;
|
||||
const isValentine = now.getMonth() == 1 && now.getDate() == 14
|
||||
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 => {
|
||||
const friend = new Friend(this.ai, { doc: f });
|
||||
friends.forEach((f) => {
|
||||
const friend = new Friend(this.nullcatChan, { doc: f })
|
||||
|
||||
// 親愛度が5以上必要
|
||||
if (friend.love < 5) return;
|
||||
// 親愛度が7以上必要
|
||||
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;
|
||||
friend.setPerModulesData(this, data);
|
||||
data.lastChocolated = date
|
||||
friend.setPerModulesData(this, data)
|
||||
|
||||
const text = serifs.valentine.chocolateForYou(friend.name);
|
||||
const text = serifs.valentine.chocolateForYou(friend.name)
|
||||
|
||||
this.ai.sendMessage(friend.userId, {
|
||||
text: text
|
||||
});
|
||||
});
|
||||
this.nullcatChan.sendMessage(friend.userId, {
|
||||
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 {
|
||||
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: {
|
||||
@ -22,88 +22,88 @@ export default {
|
||||
},
|
||||
*/
|
||||
|
||||
goodNight: name => name ? `おやすみなさい、${name}!` : 'おやすみなさい!',
|
||||
goodNight: name => name ? `おやすみ${name}!` : 'おやすみ!',
|
||||
|
||||
omedeto: name => name ? `ありがとうございます、${name}♪` : 'ありがとうございます♪',
|
||||
omedeto: name => name ? `ありがと~${name}!` : 'ありがと~!',
|
||||
|
||||
erait: {
|
||||
general: name => name ? [
|
||||
`${name}、今日もえらいです!`,
|
||||
`${name}、今日もえらいですよ~♪`
|
||||
`${name}、今日もえらい!`,
|
||||
`${name}、今日もえらいね!`
|
||||
] : [
|
||||
`今日もえらいです!`,
|
||||
`今日もえらいですよ~♪`
|
||||
`今日もえらい!`,
|
||||
`今日もえらいね!`
|
||||
],
|
||||
|
||||
specify: (thing, name) => name ? [
|
||||
`${name}、${thing}てえらいです!`,
|
||||
`${name}、${thing}てえらいですよ~♪`
|
||||
`${name}、${thing}てえらい!`,
|
||||
`${name}、${thing}てえらいね!`
|
||||
] : [
|
||||
`${thing}てえらいです!`,
|
||||
`${thing}てえらいですよ~♪`
|
||||
`${thing}てえらい!`,
|
||||
`${thing}てえらいね!`
|
||||
],
|
||||
|
||||
specify2: (thing, name) => name ? [
|
||||
`${name}、${thing}でえらいです!`,
|
||||
`${name}、${thing}でえらいですよ~♪`
|
||||
`${name}、${thing}でえらい!`,
|
||||
`${name}、${thing}でえらいね!`
|
||||
] : [
|
||||
`${thing}でえらいです!`,
|
||||
`${thing}でえらいですよ~♪`
|
||||
`${thing}でえらい!`,
|
||||
`${thing}でえらいね!`
|
||||
],
|
||||
},
|
||||
|
||||
okaeri: {
|
||||
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: {
|
||||
love: name => name ? `いってらっしゃい、${name}♪` : 'いってらっしゃい♪',
|
||||
love: name => name ? `いってらっしゃい${name}!` : 'いってらっしゃい!',
|
||||
|
||||
normal: name => name ? `いってらっしゃい、${name}!` : 'いってらっしゃい!',
|
||||
normal: name => name ? `いってらっしゃい${name}!` : 'いってらっしゃい!',
|
||||
},
|
||||
|
||||
tooLong: '長すぎる気がします...',
|
||||
tooLong: '長すぎる..',
|
||||
|
||||
invalidName: '発音が難しい気がします',
|
||||
invalidName: '発音が難しいよぉ...',
|
||||
|
||||
nadenade: {
|
||||
normal: 'ひゃっ…! びっくりしました',
|
||||
normal: 'うにゃ…?! びっくりした...',
|
||||
|
||||
love2: ['わわっ… 恥ずかしいです', 'あうぅ… 恥ずかしいです…', 'ふやぁ…?'],
|
||||
love2: ['あぅ… 恥ずかしいよぉ', 'あぅ… 恥ずかしぃ…', 'ふみゃ…!?'],
|
||||
|
||||
love3: ['んぅ… ありがとうございます♪', 'わっ、なんだか落ち着きますね♪', 'くぅんっ… 安心します…', '眠くなってきました…'],
|
||||
love3: ['んへへぇ ありがと:love_nullcatchan:', 'にへぇ~~', 'んみゅっ… ', 'もっともっとぉ...'],
|
||||
|
||||
hate1: '…っ! やめてほしいです...',
|
||||
hate1: 'やめて',
|
||||
|
||||
hate2: '触らないでください',
|
||||
hate2: '触んないで',
|
||||
|
||||
hate3: '近寄らないでください',
|
||||
hate3: 'きもい',
|
||||
|
||||
hate4: 'やめてください。通報しますよ?',
|
||||
hate4: '..?',
|
||||
},
|
||||
|
||||
kawaii: {
|
||||
normal: ['ありがとうございます♪', '照れちゃいます...'],
|
||||
normal: ['そんなことないよ?', 'えへへへうれしい。'],
|
||||
|
||||
love: ['嬉しいです♪', '照れちゃいます...'],
|
||||
love: ['えへへ。うれしいな', 'んむぅ~~...うれしい。'],
|
||||
|
||||
hate: '…ありがとうございます'
|
||||
hate: 'は?きも。'
|
||||
},
|
||||
|
||||
suki: {
|
||||
normal: 'えっ… ありがとうございます…♪',
|
||||
normal: 'えへへ。ありがと~!',
|
||||
|
||||
love: name => `私もその… ${name}のこと好きですよ!`,
|
||||
love: name => `僕も${name}のこと好き!`,
|
||||
|
||||
hate: null
|
||||
},
|
||||
@ -113,360 +113,163 @@ export default {
|
||||
|
||||
love: 'ぎゅーっ♪',
|
||||
|
||||
hate: '離れてください...'
|
||||
hate: '無理...やめて...'
|
||||
},
|
||||
|
||||
humu: {
|
||||
love: 'え、えっと…… ふみふみ……… どうですか…?',
|
||||
love: 'もふもふ!ふみふみ!',
|
||||
|
||||
normal: 'えぇ... それはちょっと...',
|
||||
normal: 'ふみふみ!',
|
||||
|
||||
hate: '……'
|
||||
hate: '?'
|
||||
},
|
||||
|
||||
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: {
|
||||
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: {
|
||||
learned: (word, reading) => `(${word}..... ${reading}..... 覚えました)`,
|
||||
learned: (word, reading) => `え~っと...${word}...${reading}...僕覚えた!!!`,
|
||||
|
||||
remembered: (word) => `${word}`
|
||||
},
|
||||
|
||||
dice: {
|
||||
done: res => `${res} です!`
|
||||
},
|
||||
|
||||
birthday: {
|
||||
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}`,
|
||||
happyBirthday: name => name ? `お誕生日おめでと~~~!!!${name}!!!!!!` : 'お誕生日おめでと~~~~~!!!',
|
||||
},
|
||||
|
||||
/**
|
||||
* 占い
|
||||
*/
|
||||
fortune: {
|
||||
cw: name => name ? `私が今日の${name}の運勢を占いました...` : '私が今日のあなたの運勢を占いました...',
|
||||
cw: name => name ? `今日の${name}の運勢を占ったよ!` : '今日のきみの運勢を占ったよ!',
|
||||
},
|
||||
|
||||
/**
|
||||
* タイマー
|
||||
*/
|
||||
timer: {
|
||||
set: 'わかりました!',
|
||||
set: 'OK!',
|
||||
|
||||
invalid: 'うーん...?',
|
||||
invalid: 'うむむ?',
|
||||
|
||||
tooLong: '長すぎます…',
|
||||
tooLong: '長すぎる…',
|
||||
|
||||
notify: (time, name) => name ? `${name}、${time}経ちましたよ!` : `${time}経ちましたよ!`
|
||||
notify: (time, name) => name ? `${name}!!${time}経ったよ!` : `${time}経ったよ!`
|
||||
},
|
||||
|
||||
/**
|
||||
* リマインダー
|
||||
*/
|
||||
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 ? [
|
||||
`よく出来ました、${name}♪`,
|
||||
`${name}、さすがですっ!`,
|
||||
`${name}、えらすぎます...!`,
|
||||
`すごい!!天才!!${name}えらい!!`,
|
||||
`${name}さすがすぎる!!!`,
|
||||
`${name}えらすぎる!!`,
|
||||
] : [
|
||||
`よく出来ました♪`,
|
||||
`さすがですっ!`,
|
||||
`えらすぎます...!`,
|
||||
`すごい!!天才!!えらい!!`,
|
||||
`さすがすぎる!!!`,
|
||||
`えらすぎる!!`,
|
||||
],
|
||||
|
||||
cancel: `わかりました。`,
|
||||
cancel: `OK!`,
|
||||
},
|
||||
|
||||
server: {
|
||||
cpu: 'サーバーざぁこ♡♡♡'
|
||||
},
|
||||
|
||||
/**
|
||||
* ろぐぼ
|
||||
*/
|
||||
rogubo: 'ログボ!!',
|
||||
|
||||
/**
|
||||
* バレンタイン
|
||||
*/
|
||||
valentine: {
|
||||
chocolateForYou: name => name ? `${name}、その... チョコレート作ったのでよかったらどうぞ!🍫` : 'チョコレート作ったのでよかったらどうぞ!🍫',
|
||||
},
|
||||
|
||||
server: {
|
||||
cpu: 'サーバーの負荷が高そうです。大丈夫でしょうか...?'
|
||||
},
|
||||
|
||||
maze: {
|
||||
post: '今日の迷路です! #AiMaze',
|
||||
foryou: '描きました!'
|
||||
},
|
||||
|
||||
chart: {
|
||||
post: 'インスタンスの投稿数です!',
|
||||
foryou: '描きました!'
|
||||
chocolateForYou: name => name ? `${name}!チョコあげる!` : 'チョコあげる!',
|
||||
},
|
||||
|
||||
sleepReport: {
|
||||
report: hours => `んぅ、${hours}時間くらい寝ちゃってたみたいです`,
|
||||
reportUtatane: 'ん... うたた寝しちゃってました',
|
||||
report: hours => `んぬぁ~、${hours}時間くらいねちゃってたかも`,
|
||||
reportUtatane: 'ぬぁ... ',
|
||||
},
|
||||
|
||||
noting: {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
249
src/stream.ts
249
src/stream.ts
@ -1,63 +1,63 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as WebSocket from 'ws';
|
||||
const ReconnectingWebsocket = require('reconnecting-websocket');
|
||||
import config from './config';
|
||||
import autobind from "autobind-decorator"
|
||||
import { EventEmitter } from "events"
|
||||
import * as WebSocket from "ws"
|
||||
import config from "./config"
|
||||
const ReconnectingWebsocket = require("reconnecting-websocket")
|
||||
|
||||
/**
|
||||
* Misskey stream connection
|
||||
*/
|
||||
export default class Stream extends EventEmitter {
|
||||
private stream: any;
|
||||
private state: string;
|
||||
private buffer: any[];
|
||||
private sharedConnectionPools: Pool[] = [];
|
||||
private sharedConnections: SharedConnection[] = [];
|
||||
private nonSharedConnections: NonSharedConnection[] = [];
|
||||
private stream: any
|
||||
private state: string
|
||||
private buffer: any[]
|
||||
private sharedConnectionPools: Pool[] = []
|
||||
private sharedConnections: SharedConnection[] = []
|
||||
private nonSharedConnections: NonSharedConnection[] = []
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
super()
|
||||
|
||||
this.state = 'initializing';
|
||||
this.buffer = [];
|
||||
this.state = "initializing"
|
||||
this.buffer = []
|
||||
|
||||
this.stream = new ReconnectingWebsocket(`${config.wsUrl}/streaming?i=${config.i}`, [], {
|
||||
WebSocket: WebSocket
|
||||
});
|
||||
this.stream.addEventListener('open', this.onOpen);
|
||||
this.stream.addEventListener('close', this.onClose);
|
||||
this.stream.addEventListener('message', this.onMessage);
|
||||
WebSocket: WebSocket,
|
||||
})
|
||||
this.stream.addEventListener("open", this.onOpen)
|
||||
this.stream.addEventListener("close", this.onClose)
|
||||
this.stream.addEventListener("message", this.onMessage)
|
||||
}
|
||||
|
||||
@autobind
|
||||
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) {
|
||||
pool = new Pool(this, channel);
|
||||
this.sharedConnectionPools.push(pool);
|
||||
pool = new Pool(this, channel)
|
||||
this.sharedConnectionPools.push(pool)
|
||||
}
|
||||
|
||||
const connection = new SharedConnection(this, channel, pool);
|
||||
this.sharedConnections.push(connection);
|
||||
return connection;
|
||||
const connection = new SharedConnection(this, channel, pool)
|
||||
this.sharedConnections.push(connection)
|
||||
return connection
|
||||
}
|
||||
|
||||
@autobind
|
||||
public removeSharedConnection(connection: SharedConnection) {
|
||||
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
|
||||
this.sharedConnections = this.sharedConnections.filter((c) => c !== connection)
|
||||
}
|
||||
|
||||
@autobind
|
||||
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
||||
const connection = new NonSharedConnection(this, channel, params);
|
||||
this.nonSharedConnections.push(connection);
|
||||
return connection;
|
||||
const connection = new NonSharedConnection(this, channel, params)
|
||||
this.nonSharedConnections.push(connection)
|
||||
return connection
|
||||
}
|
||||
|
||||
@autobind
|
||||
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
|
||||
private onOpen() {
|
||||
const isReconnect = this.state == 'reconnecting';
|
||||
const isReconnect = this.state == "reconnecting"
|
||||
|
||||
this.state = 'connected';
|
||||
this.emit('_connected_');
|
||||
this.state = "connected"
|
||||
this.emit("_connected_")
|
||||
|
||||
// バッファーを処理
|
||||
const _buffer = [...this.buffer]; // Shallow copy
|
||||
this.buffer = []; // Clear buffer
|
||||
const _buffer = [...this.buffer] // Shallow copy
|
||||
this.buffer = [] // Clear buffer
|
||||
for (const data of _buffer) {
|
||||
this.send(data); // Resend each buffered messages
|
||||
this.send(data) // Resend each buffered messages
|
||||
}
|
||||
|
||||
// チャンネル再接続
|
||||
if (isReconnect) {
|
||||
this.sharedConnectionPools.forEach(p => {
|
||||
p.connect();
|
||||
});
|
||||
this.nonSharedConnections.forEach(c => {
|
||||
c.connect();
|
||||
});
|
||||
this.sharedConnectionPools.forEach((p) => {
|
||||
p.connect()
|
||||
})
|
||||
this.nonSharedConnections.forEach((c) => {
|
||||
c.connect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,8 +93,8 @@ export default class Stream extends EventEmitter {
|
||||
*/
|
||||
@autobind
|
||||
private onClose() {
|
||||
this.state = 'reconnecting';
|
||||
this.emit('_disconnected_');
|
||||
this.state = "reconnecting"
|
||||
this.emit("_disconnected_")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,26 +102,26 @@ export default class Stream extends EventEmitter {
|
||||
*/
|
||||
@autobind
|
||||
private onMessage(message) {
|
||||
const { type, body } = JSON.parse(message.data);
|
||||
const { type, body } = JSON.parse(message.data)
|
||||
|
||||
if (type == 'channel') {
|
||||
const id = body.id;
|
||||
if (type == "channel") {
|
||||
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) {
|
||||
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)) {
|
||||
c!.emit(body.type, body.body);
|
||||
c!.emit('*', { type: body.type, body: body.body });
|
||||
for (const c of connections.filter((c) => c != null)) {
|
||||
c!.emit(body.type, body.body)
|
||||
c!.emit("*", { type: body.type, body: body.body })
|
||||
}
|
||||
} 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
|
||||
public send(typeOrPayload, payload?) {
|
||||
const data = payload === undefined ? typeOrPayload : {
|
||||
const data =
|
||||
payload === undefined
|
||||
? typeOrPayload
|
||||
: {
|
||||
type: typeOrPayload,
|
||||
body: payload
|
||||
};
|
||||
|
||||
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
||||
if (this.state != 'connected') {
|
||||
this.buffer.push(data);
|
||||
return;
|
||||
body: payload,
|
||||
}
|
||||
|
||||
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
|
||||
public close() {
|
||||
this.stream.removeEventListener('open', this.onOpen);
|
||||
this.stream.removeEventListener('message', this.onMessage);
|
||||
this.stream.removeEventListener("open", this.onOpen)
|
||||
this.stream.removeEventListener("message", this.onMessage)
|
||||
}
|
||||
}
|
||||
|
||||
class Pool {
|
||||
public channel: string;
|
||||
public id: string;
|
||||
protected stream: Stream;
|
||||
private users = 0;
|
||||
private disposeTimerId: any;
|
||||
private isConnected = false;
|
||||
public channel: string
|
||||
public id: string
|
||||
protected stream: Stream
|
||||
private users = 0
|
||||
private disposeTimerId: any
|
||||
private isConnected = false
|
||||
|
||||
constructor(stream: Stream, channel: string) {
|
||||
this.channel = channel;
|
||||
this.stream = stream;
|
||||
this.channel = channel
|
||||
this.stream = stream
|
||||
|
||||
this.id = Math.random().toString();
|
||||
this.id = Math.random().toString()
|
||||
}
|
||||
|
||||
@autobind
|
||||
public inc() {
|
||||
if (this.users === 0 && !this.isConnected) {
|
||||
this.connect();
|
||||
this.connect()
|
||||
}
|
||||
|
||||
this.users++;
|
||||
this.users++
|
||||
|
||||
// タイマー解除
|
||||
if (this.disposeTimerId) {
|
||||
clearTimeout(this.disposeTimerId);
|
||||
this.disposeTimerId = null;
|
||||
clearTimeout(this.disposeTimerId)
|
||||
this.disposeTimerId = null
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
public dec() {
|
||||
this.users--;
|
||||
this.users--
|
||||
|
||||
// そのコネクションの利用者が誰もいなくなったら
|
||||
if (this.users === 0) {
|
||||
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
||||
// 新たな利用者が現れなければコネクションを切断する
|
||||
this.disposeTimerId = setTimeout(() => {
|
||||
this.disconnect();
|
||||
}, 3000);
|
||||
this.disconnect()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
public connect() {
|
||||
this.isConnected = true;
|
||||
this.stream.send('connect', {
|
||||
this.isConnected = true
|
||||
this.stream.send("connect", {
|
||||
channel: this.channel,
|
||||
id: this.id
|
||||
});
|
||||
id: this.id,
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
private disconnect() {
|
||||
this.isConnected = false;
|
||||
this.disposeTimerId = null;
|
||||
this.stream.send('disconnect', { id: this.id });
|
||||
this.isConnected = false
|
||||
this.disposeTimerId = null
|
||||
this.stream.send("disconnect", { id: this.id })
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Connection extends EventEmitter {
|
||||
public channel: string;
|
||||
protected stream: Stream;
|
||||
public abstract id: string;
|
||||
public channel: string
|
||||
protected stream: Stream
|
||||
public abstract id: string
|
||||
|
||||
constructor(stream: Stream, channel: string) {
|
||||
super();
|
||||
super()
|
||||
|
||||
this.stream = stream;
|
||||
this.channel = channel;
|
||||
this.stream = stream
|
||||
this.channel = channel
|
||||
}
|
||||
|
||||
@autobind
|
||||
public send(id: string, typeOrPayload, payload?) {
|
||||
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
|
||||
const body = payload === undefined ? typeOrPayload.body : payload;
|
||||
const type = payload === undefined ? typeOrPayload.type : typeOrPayload
|
||||
const body = payload === undefined ? typeOrPayload.body : payload
|
||||
|
||||
this.stream.send('ch', {
|
||||
this.stream.send("ch", {
|
||||
id: id,
|
||||
type: type,
|
||||
body: body
|
||||
});
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
public abstract dispose(): void;
|
||||
public abstract dispose(): void
|
||||
}
|
||||
|
||||
class SharedConnection extends Connection {
|
||||
private pool: Pool;
|
||||
private pool: Pool
|
||||
|
||||
public get id(): string {
|
||||
return this.pool.id;
|
||||
return this.pool.id
|
||||
}
|
||||
|
||||
constructor(stream: Stream, channel: string, pool: Pool) {
|
||||
super(stream, channel);
|
||||
super(stream, channel)
|
||||
|
||||
this.pool = pool;
|
||||
this.pool.inc();
|
||||
this.pool = pool
|
||||
this.pool.inc()
|
||||
}
|
||||
|
||||
@autobind
|
||||
public send(typeOrPayload, payload?) {
|
||||
super.send(this.pool.id, typeOrPayload, payload);
|
||||
super.send(this.pool.id, typeOrPayload, payload)
|
||||
}
|
||||
|
||||
@autobind
|
||||
public dispose() {
|
||||
this.pool.dec();
|
||||
this.removeAllListeners();
|
||||
this.stream.removeSharedConnection(this);
|
||||
this.pool.dec()
|
||||
this.removeAllListeners()
|
||||
this.stream.removeSharedConnection(this)
|
||||
}
|
||||
}
|
||||
|
||||
class NonSharedConnection extends Connection {
|
||||
public id: string;
|
||||
protected params: any;
|
||||
public id: string
|
||||
protected params: any
|
||||
|
||||
constructor(stream: Stream, channel: string, params?: any) {
|
||||
super(stream, channel);
|
||||
super(stream, channel)
|
||||
|
||||
this.params = params;
|
||||
this.id = Math.random().toString();
|
||||
this.params = params
|
||||
this.id = Math.random().toString()
|
||||
|
||||
this.connect();
|
||||
this.connect()
|
||||
}
|
||||
|
||||
@autobind
|
||||
public connect() {
|
||||
this.stream.send('connect', {
|
||||
this.stream.send("connect", {
|
||||
channel: this.channel,
|
||||
id: this.id,
|
||||
params: this.params
|
||||
});
|
||||
params: this.params,
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
public send(typeOrPayload, payload?) {
|
||||
super.send(this.id, typeOrPayload, payload);
|
||||
super.send(this.id, typeOrPayload, payload)
|
||||
}
|
||||
|
||||
@autobind
|
||||
public dispose() {
|
||||
this.removeAllListeners();
|
||||
this.stream.send('disconnect', { id: this.id });
|
||||
this.stream.disconnectToChannel(this);
|
||||
this.removeAllListeners()
|
||||
this.stream.send("disconnect", { id: this.id })
|
||||
this.stream.disconnectToChannel(this)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,7 @@
|
||||
import * as seedrandom from 'seedrandom';
|
||||
|
||||
export const itemPrefixes = [
|
||||
'プラチナ製',
|
||||
'新鮮な',
|
||||
'最新式の',
|
||||
'古代の',
|
||||
'手作り',
|
||||
'時計じかけの',
|
||||
'伝説の',
|
||||
'焼き',
|
||||
'生の',
|
||||
'藍謹製',
|
||||
'ポケットサイズ',
|
||||
'3日前の',
|
||||
'そこらへんの',
|
||||
'偽の',
|
||||
'使用済み',
|
||||
'壊れた',
|
||||
'市販の',
|
||||
@ -22,52 +9,24 @@ export const itemPrefixes = [
|
||||
'業務用の',
|
||||
'Microsoft製',
|
||||
'Apple製',
|
||||
'人類の技術を結集して作った',
|
||||
'2018年製', // TODO ランダム
|
||||
'500kgくらいある',
|
||||
'高級',
|
||||
'腐った',
|
||||
'人工知能搭載',
|
||||
'反重力',
|
||||
'折り畳み式',
|
||||
'携帯型',
|
||||
'遺伝子組み換え',
|
||||
'飛行能力を獲得した',
|
||||
'純金製',
|
||||
'透明な',
|
||||
'光る',
|
||||
'ハート型の',
|
||||
'動く',
|
||||
'半分にカットされた',
|
||||
'USBコネクタ付きの',
|
||||
'いにしえの',
|
||||
'呪われた',
|
||||
'エンチャントされた',
|
||||
'一日分のビタミンが入った',
|
||||
'かじりかけ',
|
||||
'幻の',
|
||||
'仮想的な',
|
||||
'原子力',
|
||||
'高度に訓練された',
|
||||
'遺伝子組み換えでない',
|
||||
'ダンジョン最深部で見つかった',
|
||||
'異世界の',
|
||||
'異星の',
|
||||
'謎の',
|
||||
'時空を歪める',
|
||||
'異音がする',
|
||||
'霧散する',
|
||||
'プラズマ化した',
|
||||
'衝撃を与えると低確率で爆発する',
|
||||
'ズッキーニに擬態した',
|
||||
'仮説上の',
|
||||
'毒の',
|
||||
'真の',
|
||||
'究極の',
|
||||
'チョコ入り',
|
||||
'異臭を放つ',
|
||||
'4次元',
|
||||
'脈動する',
|
||||
'得体の知れない',
|
||||
'四角い',
|
||||
'暴れ回る',
|
||||
@ -75,70 +34,27 @@ export const itemPrefixes = [
|
||||
'闇の',
|
||||
'暗黒の',
|
||||
'封印されし',
|
||||
'死の',
|
||||
'凍った',
|
||||
'魔の',
|
||||
'禁断の',
|
||||
'ホログラフィックな',
|
||||
'油圧式',
|
||||
'辛そうで辛くない少し辛い',
|
||||
'焦げた',
|
||||
'宇宙',
|
||||
'電子',
|
||||
'陽電子',
|
||||
'量子力学的',
|
||||
'シュレディンガーの',
|
||||
'分散型',
|
||||
'卵かけ',
|
||||
'次世代',
|
||||
'帯電',
|
||||
'太古の',
|
||||
'WiFi対応',
|
||||
'高反発',
|
||||
'【令和最新版】',
|
||||
'廉価版',
|
||||
'ねばねば',
|
||||
'どろどろ',
|
||||
'パサパサの',
|
||||
'湿気った',
|
||||
'賞味期限切れ',
|
||||
'地獄から来た',
|
||||
'ニンニクマシ',
|
||||
'放射性',
|
||||
'フラクタルな',
|
||||
'再帰的',
|
||||
'ときどき分裂する',
|
||||
'3G対応',
|
||||
'消費期限切れ',
|
||||
'消える',
|
||||
'等速直線運動する',
|
||||
'X線照射',
|
||||
'蠢く',
|
||||
'形而上学的',
|
||||
'もちもち',
|
||||
'冷やし',
|
||||
'あつあつ',
|
||||
'巨大',
|
||||
'ナノサイズ',
|
||||
'やわらかい',
|
||||
'人の手に負えない',
|
||||
'バグった',
|
||||
'人工',
|
||||
'天然',
|
||||
'祀られた',
|
||||
'チョコレートコーティング',
|
||||
'抗菌仕様',
|
||||
'耐火',
|
||||
'激',
|
||||
'猛',
|
||||
'超',
|
||||
'群生する',
|
||||
'軽量',
|
||||
'国宝級',
|
||||
'流行りの',
|
||||
'8カラットの',
|
||||
'中古の',
|
||||
'新品の',
|
||||
'愛妻',
|
||||
'ブランドものの',
|
||||
'増殖する',
|
||||
'ぷるぷる',
|
||||
'ぐにゃぐにゃ',
|
||||
'多目的',
|
||||
@ -146,315 +62,54 @@ export const itemPrefixes = [
|
||||
'激辛',
|
||||
'先進的な',
|
||||
'レトロな',
|
||||
'ヴィンテージ',
|
||||
'合法',
|
||||
'違法',
|
||||
'プレミア付き',
|
||||
'デカ',
|
||||
'ギガ',
|
||||
'穢れた',
|
||||
'品質保証付き',
|
||||
'AppleCare+加入済み',
|
||||
'えっちな',
|
||||
'デザイナーズ',
|
||||
'蠱惑的な',
|
||||
'霊験灼かな',
|
||||
'つやつや',
|
||||
'べとべと',
|
||||
'ムキムキ',
|
||||
'オーバークロックされた',
|
||||
'無機質な',
|
||||
'前衛的な',
|
||||
'怪しい',
|
||||
'妖しい',
|
||||
'カビの生えた',
|
||||
'熟成',
|
||||
'アルミダイキャスト',
|
||||
'養殖',
|
||||
'やばい',
|
||||
'すごい',
|
||||
'かわいい',
|
||||
'デジタル',
|
||||
'アナログ',
|
||||
'彁な',
|
||||
'カラフルな',
|
||||
'電動',
|
||||
'当たり判定のない',
|
||||
'めり込んだ',
|
||||
'100年に一度の',
|
||||
'ジューシーな',
|
||||
'Hi-Res',
|
||||
'確変',
|
||||
'食用',
|
||||
'THE ',
|
||||
'某',
|
||||
'朽ちゆく',
|
||||
'滅びの',
|
||||
'反発係数がe>1の',
|
||||
'摩擦係数0の',
|
||||
'解き放たれし',
|
||||
'大きな',
|
||||
'小さな',
|
||||
'強欲な',
|
||||
'うねうね',
|
||||
'水没',
|
||||
'燃え盛る',
|
||||
'高圧',
|
||||
'異常',
|
||||
];
|
||||
|
||||
export const items = [
|
||||
'ナス',
|
||||
'トマト',
|
||||
'きゅうり',
|
||||
'じゃがいも',
|
||||
'焼きビーフン',
|
||||
'腰',
|
||||
'寿司',
|
||||
'かぼちゃ',
|
||||
'諭吉',
|
||||
'キロバー',
|
||||
'アルミニウム',
|
||||
'ナトリウム',
|
||||
'マグネシウム',
|
||||
'プルトニウム',
|
||||
'ちいさなメダル',
|
||||
'牛乳パック',
|
||||
'ペットボトル',
|
||||
'クッキー',
|
||||
'チョコレート',
|
||||
'メイド服',
|
||||
'オレンジ',
|
||||
'ニーソ',
|
||||
'反物質コンデンサ',
|
||||
'粒子加速器',
|
||||
'マイクロプロセッサ(4コア8スレッド)',
|
||||
'原子力発電所',
|
||||
'レイヤ4スイッチ',
|
||||
'緩衝チェーン',
|
||||
'陽電子頭脳',
|
||||
'惑星',
|
||||
'テルミン',
|
||||
'虫歯車',
|
||||
'マウンター',
|
||||
'バケットホイールエクスカベーター',
|
||||
'デーモンコア',
|
||||
'ゲームボーイアドバンス',
|
||||
'右足',
|
||||
'左足',
|
||||
'お金',
|
||||
'金パブ',
|
||||
'ブロン',
|
||||
'ぬるきゃっとちゃん!',
|
||||
'この世のすべて',
|
||||
'量子コンピューター',
|
||||
'アナモルフィックレンズ',
|
||||
'押し入れの奥から出てきた謎の生き物',
|
||||
'スマートフォン',
|
||||
'時計',
|
||||
'プリン',
|
||||
'ガブリエルのラッパ',
|
||||
'メンガーのスポンジ',
|
||||
'ハンドスピナー',
|
||||
'超立方体',
|
||||
'建築物',
|
||||
'エナジードリンク',
|
||||
'マウスカーソル',
|
||||
'メガネ',
|
||||
'まぐろ',
|
||||
'ゴミ箱',
|
||||
'つまようじ',
|
||||
'お弁当に入ってる緑の仕切りみたいなやつ',
|
||||
'割りばし',
|
||||
'換気扇',
|
||||
'ペットボトルのキャップ',
|
||||
'消波ブロック',
|
||||
'ピザ',
|
||||
'歯磨き粉',
|
||||
'空き缶',
|
||||
'キーホルダー',
|
||||
'金髪碧眼の美少女',
|
||||
'SDカード',
|
||||
'リップクリーム',
|
||||
'チョコ無しチョココロネ',
|
||||
'鳥インフルエンザ',
|
||||
'自動販売機',
|
||||
'重いもの',
|
||||
'ノートパソコン',
|
||||
'ビーフジャーキー',
|
||||
'さけるチーズ',
|
||||
'ダイヤモンド',
|
||||
'物体',
|
||||
'月の石',
|
||||
'特異点',
|
||||
'中性子星',
|
||||
'液体',
|
||||
'衛星',
|
||||
'ズッキーニ',
|
||||
'黒いもの',
|
||||
'白いもの',
|
||||
'赤いもの',
|
||||
'丸いもの',
|
||||
'四角いもの',
|
||||
'カード状のもの',
|
||||
'気体',
|
||||
'鉛筆',
|
||||
'消しゴム',
|
||||
'つるぎ',
|
||||
'棒状のもの',
|
||||
'農産物',
|
||||
'メタルスライム',
|
||||
'タコの足',
|
||||
'きのこ',
|
||||
'なめこ',
|
||||
'缶チューハイ',
|
||||
'爪切り',
|
||||
'耳かき',
|
||||
'スマホ',
|
||||
'PC',
|
||||
'モンスター',
|
||||
'好きなもの',
|
||||
'ぬいぐるみ',
|
||||
'ティラノサウルス',
|
||||
'尿路結石',
|
||||
'エンターキー',
|
||||
'壺',
|
||||
'水銀',
|
||||
'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 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秒」のように単位を混ぜることもできるよ
|
||||
|
||||
### リマインダー
|
||||
```
|
||||
@ai remind 部屋の掃除
|
||||
```
|
||||
のようにメンションを飛ばすと12時間置きに責付かれます。その飛ばしたメンションか、藍ちゃんからの催促に「やった」または「やめた」と返信することでリマインダー解除されます。
|
||||
また、引用Renoteでメンションすることもできます。
|
||||
`@nullcat todo(リマインド、これやる) 寝る` みたいに言ってくれたら1時間置きにリマインドするよ。その飛ばしたメンションか、僕からの催促に「やった」「やめた」など返信するとリマインダー解除されるよ<br>
|
||||
引用Renoteでメンションすることもできるよ<br>
|
||||
リマインダーの一覧は `@nullcat todos` で見れるよ
|
||||
|
||||
### 福笑い
|
||||
藍に「絵文字」と言うと、藍が考えた絵文字の組み合わせを教えてくれます。
|
||||
### GitHub Status
|
||||
僕に「GitHub」って言ってくれたら今のStatusを教えるよ
|
||||
|
||||
### サイコロ
|
||||
ダイスノーテーションを伝えるとサイコロを振ってくれます。
|
||||
例: "2d6" (6面サイコロを2回振る)、"3d5" (5面サイコロを3回振る)
|
||||
### Cloudflare Status
|
||||
僕に「Cloudflare」って言ってくれたら今のStatusを教えるよ
|
||||
|
||||
### 迷路
|
||||
「迷路」と言うと迷路を描いてくれます。「難しい」「簡単」などの言葉を添えることで、難易度も調整できます。
|
||||
### シェル芸機能
|
||||
僕に #シェル芸、#shellge をつけてコマンドを送ってくれたら実行結果を返すよ
|
||||
|
||||
### 数当てゲーム
|
||||
藍にメッセージで「数当てゲーム」と言うと遊べます。
|
||||
藍の考えている数字を当てるゲームです。
|
||||
### 怪レい曰本语変換
|
||||
僕に `#怪しい日本語変換` っていうタグ付きで変換してほしい文章をメンションしてくれたら怪レい曰本语に変換するよ
|
||||
|
||||
### 数取りゲーム
|
||||
藍に「数取りゲーム」と言うと遊べます。
|
||||
複数人で行うゲームで、もっとも大きい数字を言った人が勝ちです。
|
||||
### やること決める
|
||||
僕に「なにしよ」って言ってくれたらやることを決めるよ
|
||||
|
||||
### リバーシ
|
||||
藍とリバーシで対局できます。(この機能はインスタンスによっては無効になっている可能性があります)
|
||||
藍に「リバーシ」と言うか、リバーシで藍を指名すれば対局できます。
|
||||
強さも調整できます。
|
||||
|
||||
### 覚える
|
||||
たまにタイムラインにあるキーワードを「覚え」ます。
|
||||
(この機能はインスタンスによっては無効になっている可能性があります)
|
||||
### 気圧
|
||||
僕に「気圧教えて」って言ってくれたら今の気圧を教えるよ
|
||||
|
||||
### 呼び方を教える
|
||||
藍があなたのことをなんて呼べばいいか教えられます。
|
||||
ただし後述の親愛度が一定の値に達している必要があります。
|
||||
(トークでのみ反応します)
|
||||
|
||||
### いらっしゃい
|
||||
Misskeyにアカウントを作成して初めて投稿を行うと、藍がネコミミアンテナでそれを補足し、Renoteしてみんなに知らせてくれる機能です。
|
||||
|
||||
### Follow me
|
||||
藍に「フォローして」と言うとフォローしてくれます。
|
||||
僕が君のことをなんて呼べばいいか教えてくれたら、その名前で呼ぶよ!<br>
|
||||
親愛度が一定の値に達している必要があるよ<br>
|
||||
(チャットのみで反応するよ)
|
||||
|
||||
### HappyBirthday
|
||||
藍があなたの誕生日を祝ってくれます。
|
||||
誕生日になったら僕が君の誕生日を祝うよ
|
||||
|
||||
### バレンタイン
|
||||
藍がチョコレートをくれます。
|
||||
|
||||
### チャート
|
||||
インスタンスの投稿チャートなどを投稿してくれます。
|
||||
|
||||
### サーバー監視
|
||||
サーバーの状態を監視し、負荷が高くなっているときは教えてくれます。
|
||||
バレンタインになったら仲のいい子に僕がチョコレートをあげるよ
|
||||
|
||||
### ping
|
||||
PONGを返します。生存確認にどうぞ
|
||||
僕に「ping」って言ってくれたらフォローするよで起きてるとき返信するよ!寝てるときは返信できないかも...
|
||||
|
||||
### その他反応するフレーズ (トークのみ)
|
||||
* かわいい
|
||||
* なでなで
|
||||
* 好き
|
||||
* ぎゅー
|
||||
* 罵って
|
||||
* 踏んで
|
||||
* 痛い
|
||||
### 親愛度
|
||||
僕は君に対する親愛度を持っているよ<br>
|
||||
僕にお話ししてくれたりすると、少しずつ上がるよ<br>
|
||||
親愛度によって反応が変化するよ!親愛度がある程度ないとしてくれないこともあるよ<br>
|
||||
たくさん話しかけてね
|
||||
|
||||
## 親愛度
|
||||
藍はあなたに対する親愛度を持っています。
|
||||
藍に挨拶したりすると、少しずつ上がっていきます。
|
||||
親愛度によって反応や各種セリフが変化します。親愛度がある程度ないとしてくれないこともあります。
|
||||
|
||||
僕のリポジトリは[ここ](https://github.com/nullnyat/NullcatChan)だよ
|
||||
|
@ -14,10 +14,10 @@
|
||||
"noLib": false,
|
||||
"outDir": "built",
|
||||
"rootDir": "src",
|
||||
"baseUrl": ".",
|
||||
"baseUrl": "../NullcatChan-old",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
}
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"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 => {
|
||||
/**
|
||||
* テキストの余分な部分を取り除く
|
||||
* 例えば「藍ちゃん好き!」のようなテキストを「好き」にする
|
||||
* 例えば「ぬるきゃっとちゃん好き!」のようなテキストを「好き」にする
|
||||
*/
|
||||
function denoise(text: string): string {
|
||||
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(/、+$/, '');
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user