1
0
Fork 0
mirror of https://github.com/kou029w/daraz-san.git synced 2025-01-18 16:08:06 +00:00

TypeScriptへの移行

This commit is contained in:
Nebel 2022-04-27 00:24:46 +09:00
parent bffb243526
commit f56f9aca15
34 changed files with 943 additions and 324 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
/node_modules/ .vercel
dist
node_modules

View file

@ -1,22 +1,44 @@
# だらずさん ⚡ # だらずさん ⚡
[Bolt](https://github.com/slackapi/bolt-js) で作り直した [だらずさん](https://github.com/daraz-tek/daraz-bot) [Bolt](https://github.com/slackapi/bolt-js) で作られた [だらずさん](https://github.com/daraz-tek/daraz-bot)
## つかいかた 1. デプロイ
2. Slack アプリの作成
3. 環境変数の設定
### Slack アプリの作成 ## デプロイ
- Redirect URLs を設定 (例: https://{ホスト名}/ ) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fkou029w%2Fdaraz-san)
- Bot Users を設定
- (デプロイ後) Event Subscriptions を設定
- Request URL を設定 (例: https://{ホスト名}/slack/events )
- Bot Events を登録
- message.channels (パブリックチャンネルのメッセージをリスニング) など
### 起動 ## Slack アプリの作成
次の環境変数を与えてから `yarn && yarn start` [Slack Applications](https://api.slack.com/apps) → Create New App → From an app manifest
- `SLACK_BOT_TOKEN` ... OAuth & Permissions ページにあるボット (xoxb) トークン 下記の Manifest の `{Vercelのプロジェクト名}` の部分を変更してコピペして、Slack アプリを作成します。
- `SLACK_SIGNING_SECRET` ... Basic Information ページにある Signing Secret
- (Optional) `PORT` ... ポート番号。デフォルトは 8080。 ```yaml
display_information:
name: だらずさん
features:
bot_user:
display_name: daraz-san
oauth_config:
scopes:
bot:
- channels:history
- chat:write
settings:
event_subscriptions:
request_url: https://{Vercelのプロジェクト名}.vercel.app/api/slack/events
bot_events:
- message.channels
```
## 環境変数の設定
[Vercel Dashboard](https://vercel.com/dashboard) → 対象のプロジェクト → Settings → Environment Variables
- `SLACK_BOT_TOKEN` ... [Slack Applications](https://api.slack.com/apps) → だらずさん → Permissions ページにある `xoxb-` から始まるボットトークン
- `SLACK_SIGNING_SECRET` ... [Slack Applications](https://api.slack.com/apps) → だらずさん → Basic Information ページにある Signing Secret
これらの環境変数を設定後、再びデプロイして反映します。

10
api/slack/events.ts Normal file
View file

@ -0,0 +1,10 @@
import { AwsLambdaReceiver } from "@slack/bolt";
import App from "../../dist/app";
const token = process.env.SLACK_BOT_TOKEN;
const signingSecret = process.env.SLACK_SIGNING_SECRET;
const receiver = new AwsLambdaReceiver({ signingSecret });
new App({ token, receiver });
export const handler = receiver.toHandler();

17
app.js
View file

@ -1,17 +0,0 @@
require("cross-fetch/polyfill");
const { App: BoltApp } = require("@slack/bolt");
class App extends BoltApp {
/**
* @param {import("@slack/bolt").AppOptions} opt
*/
constructor(opt) {
super(opt);
const scripts = require("glob").sync("./scripts/*.js").map(require);
scripts.forEach((script) => {
if (Array.isArray(script)) return this.message(...script);
if (script instanceof Function) return script(this);
});
}
}
module.exports = App;

18
main.js
View file

@ -1,18 +0,0 @@
const App = require("./app");
const Receiver = require("./receiver");
async function main() {
const receiver = new Receiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
receiver,
});
await app.start(process.env.PORT || 8080);
console.log("Daraz-san ⚡ running");
}
if (require.main === module) main();

View file

@ -11,19 +11,24 @@
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node main.js" "build": "echo no front-end build specified",
"vercel-build": "tsup src --clean"
}, },
"dependencies": { "devDependencies": {
"@slack/bolt": "3.x", "@slack/bolt": "^3.11.0",
"@types/kuromoji": "^0.1.1",
"@types/tldjs": "^2.3.1",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"date-fns": "2.x", "date-fns": "^2.28.0",
"date-fns-tz": "1.x", "date-fns-tz": "^1.3.3",
"get-video-id": "3.x", "get-video-id": "^3.5.3",
"glob": "8.x", "glob": "^8.0.1",
"kuromoji": "0.1.x", "kuromoji": "^0.1.2",
"tldjs": "2.x" "tldjs": "^2.3.1",
"tsup": "^5.12.6"
}, },
"engines": { "engines": {
"yarn": "1.x" "node": ">=14.19.0"
} },
"packageManager": "yarn@1.22.15"
} }

1
public/index.txt Normal file
View file

@ -0,0 +1 @@
OK

View file

@ -1,18 +0,0 @@
const { promisify } = require("util");
const { ExpressReceiver } = require("@slack/bolt");
class Receiver extends ExpressReceiver {
/**
* @param {import("express").Request} req
* @param {import("express").Response} res
*/
requestHandler(req, res) {
// NOTE: See also https://api.slack.com/events-api#errors
res.header("x-slack-no-retry", "1");
if (req.headers["x-slack-retry-reason"] === "http_timeout")
return promisify(res.end)();
return super.requestHandler(req, res);
}
}
module.exports = Receiver;

View file

@ -1,9 +1 @@
{ { "extends": ["config:base", "config:semverAllMonthly", ":automergeMinor"] }
"extends": [
"config:base",
":preserveSemverRanges",
":maintainLockFilesWeekly"
],
"automerge": true,
"requiredStatusChecks": null
}

View file

@ -1,10 +0,0 @@
const { directMention } = require("@slack/bolt");
module.exports = [
directMention(),
/(ニャンちゅう) +(.*)/,
({ context, say }) => {
const oon = context.matches[2].split("").map((c) => `${c}`);
return say(`お゛ぉ゛ん!${oon.join("")}た゛に゛ゃあ゛ん! > :nyanchu:`);
},
];

View file

@ -1,30 +0,0 @@
const { useTokenize } = require("./util/morpheme");
const random = require("./util/random");
const tellme = require("./util/tellme");
// ときどきうんちくを語ります
module.exports = [
/[^\x01-\x7e]{4,}/,
async ({ context, say }) => {
if (random([...Array(15).keys()]) !== 0) return;
try {
const tokenize = await useTokenize();
const words = tokenize(context.matches[0])
.filter(({ pos }) => pos === "名詞")
.map(({ surface_form }) => surface_form)
.filter((t) => !/^[\u3040-\u309F]$/.test(t)) //ひらがな1文字 http://www.unicode.org/charts/PDF/U3040.pdf
.filter((t) => !/^[\u30A0-\u30FF]$/.test(t)) //かたかな1文字 http://www.unicode.org/charts/PDF/U30A0.pdf
.filter((t) => !/^[\uFF65-\uFF9F]$/.test(t)) //半角カナ1文字 http://www.unicode.org/charts/PDF/UFF00.pdf
.filter((t) => !/[、・…]/.test(t));
const word = random(words);
const ans = await tellme(word);
if (/^(|…|しらないにゃーん)$/.test(ans)) {
throw new Error(`don't know ${word} : ${ans}`);
}
return say(`:nya-n: < 【う・ん・ち・く】${ans}`);
} catch (e) {
console.error(e);
}
},
];

View file

@ -1,5 +0,0 @@
const random = require("./random");
const nyans = [":nya-n1:", ":nya-n2:", ":nya-n3:", ":nya-n4:", ":nya-n5:"];
module.exports = nyanco = () => random(nyans);

View file

@ -1 +0,0 @@
module.exports = (array) => array[Math.floor(Math.random() * array.length)];

23
src/app.ts Normal file
View file

@ -0,0 +1,23 @@
import { App as BoltApp, AppOptions } from "@slack/bolt";
import path from "node:path";
import glob from "glob";
// TODO: Node.js v18+ ならば不要
import "cross-fetch/polyfill";
const scriptsPattern = path.join(__dirname, "scripts/*.js");
const scripts = glob
.sync(scriptsPattern)
.map(require)
.map((m) => m.default ?? m);
class App extends BoltApp {
constructor(options: AppOptions) {
super(options);
scripts.forEach((script) => {
if (Array.isArray(script)) return this.message(...script);
if (script instanceof Function) return script(this);
});
}
}
export default App;

View file

@ -1,10 +1,10 @@
const get_video_id = require("get-video-id"); import getVideoId from "get-video-id";
const API_KEY = "Q8mtkFkP4Zru4mlDd812iw2vcQwx5B0qIsUKsxit"; const API_KEY = "Q8mtkFkP4Zru4mlDd812iw2vcQwx5B0qIsUKsxit";
module.exports = [ export default [
/apod|galaxy|spa+ce|宇宙|コスモ|銀河/i, /apod|galaxy|spa+ce|宇宙|コスモ|銀河/i,
async ({ say }) => { async ({ say }: any) => {
const response = await fetch( const response = await fetch(
`https://api.nasa.gov/planetary/apod?api_key=${API_KEY}` `https://api.nasa.gov/planetary/apod?api_key=${API_KEY}`
); );
@ -14,7 +14,7 @@ module.exports = [
const dict = await response.json(); const dict = await response.json();
const url = const url =
dict.media_type === "video" dict.media_type === "video"
? `https://www.youtube.com/watch?v=${get_video_id(dict.url)}` ? `https://www.youtube.com/watch?v=${getVideoId(dict.url)}`
: dict.url; : dict.url;
return say(`宇宙って良いにゃーん\n${dict.title}\n${url}`); return say(`宇宙って良いにゃーん\n${dict.title}\n${url}`);
}, },

View file

@ -1,19 +1,15 @@
const dns = require("dns"); import { tldExists } from "tldjs";
const { tldExists } = require("tldjs"); import dns from "node:dns";
module.exports = [ export default [
/(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)+([a-z]+)/, /(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)+([a-z]+)/,
async ({ context, say }) => { async ({ context, say }: any) => {
const domain = context.matches[0]; const domain = context.matches[0];
const ignores = ["daraz-tek.slack.com"]; const ignores = ["daraz-tek.slack.com"];
if (ignores.includes(domain)) return; if (ignores.includes(domain)) return;
if (!tldExists(domain)) return; if (!tldExists(domain)) return;
try { try {
const records = await new Promise((resolve, reject) => const records = await dns.promises.resolve(domain);
dns.resolve(domain, (err, records) =>
err ? reject(err) : resolve(records)
)
);
return say( return say(
`:nya-n: < ${domain}${records.join(" *,* ")} ですにゃん` `:nya-n: < ${domain}${records.join(" *,* ")} ですにゃん`
); );

View file

@ -1,5 +1,5 @@
module.exports = (app) => { export default (app: any) => {
app.message(/555/, ({ say }) => app.message(/555/, ({ say }: any) =>
say( say(
[ [
"Standing by... > :nya-n:", "Standing by... > :nya-n:",
@ -10,7 +10,7 @@ module.exports = (app) => {
].join("\n") ].join("\n")
) )
); );
app.message(/551/, ({ say }) => app.message(/551/, ({ say }: any) =>
say( say(
[ [
"551の豚まんがあるときー > :nya-n:", "551の豚まんがあるときー > :nya-n:",

View file

@ -1,5 +1,5 @@
module.exports = [ export default [
/([\s\S]*(ぱい|パイ)[\s\S]*)/, /([\s\S]*(ぱい|パイ)[\s\S]*)/,
({ context, say }) => ({ context, say }: any) =>
say(`:goku: < ${context.matches[0].replace(/ぱい|パイ/g, "ぺぇ")}`), say(`:goku: < ${context.matches[0].replace(/ぱい|パイ/g, "ぺぇ")}`),
]; ];

View file

@ -1,6 +1,6 @@
module.exports = [ export default [
/(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/, /(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/,
async ({ context, say }) => { async ({ context, say }: any) => {
const ip = context.matches[0]; const ip = context.matches[0];
const url = `https://ipinfo.io/${ip}`; const url = `https://ipinfo.io/${ip}`;
const options = { const options = {

View file

@ -1,5 +1,5 @@
const { directMention } = require("@slack/bolt"); import { directMention } from "@slack/bolt";
const nyanco = require("./util/nyanco"); import nyanco from "./utils/nyanco";
// Description: // Description:
// だらずさんは JSON のお掃除がだいすきです // だらずさんは JSON のお掃除がだいすきです
@ -7,10 +7,10 @@ const nyanco = require("./util/nyanco");
// Synopsis: // Synopsis:
// json <jsonstring> - あなたの JSON をぷりちーにするにゃん // json <jsonstring> - あなたの JSON をぷりちーにするにゃん
module.exports = [ export default [
directMention(), directMention(),
/json +(.*)/i, /json +(.*)/i,
({ context, say }) => { ({ context, say }: any) => {
try { try {
const json = JSON.parse(context.matches[1]); const json = JSON.parse(context.matches[1]);
const res = JSON.stringify(json, null, 2); const res = JSON.stringify(json, null, 2);

View file

@ -1,6 +1,6 @@
const { directMention } = require("@slack/bolt"); import { directMention } from "@slack/bolt";
const { useTokenize, toCSV } = require("./util/morpheme"); import { makeTokenize, toCSV } from "./utils/morpheme";
const nyanco = require("./util/nyanco"); import nyanco from "./utils/nyanco";
// Description: // Description:
// すもももももももものうち // すもももももももものうち
@ -8,14 +8,14 @@ const nyanco = require("./util/nyanco");
// Synopsis: // Synopsis:
// morpheme <phrase> - <phrase> を形態素解析器にかけるにゃーん // morpheme <phrase> - <phrase> を形態素解析器にかけるにゃーん
module.exports = [ export default [
directMention(), directMention(),
/morpheme (.*)/i, /morpheme (.*)/i,
async ({ context, say }) => { async ({ context, say }: any) => {
try { try {
const tokenize = await useTokenize(); const tokenize = await makeTokenize();
const tokens = tokenize(context.matches[1]); const tokens = tokenize(context.matches[1]);
const readings = tokens.map(({ reading }) => reading); const readings = tokens.map(({ reading }: any) => reading);
return say( return say(
[`${nyanco()} ${readings.join("")}`, toCSV(tokens)].join("\n") [`${nyanco()} ${readings.join("")}`, toCSV(tokens)].join("\n")
); );

View file

@ -1,49 +1,49 @@
// @ts-check // @ts-check
const random = require("./util/random"); import random from "./utils/random";
const nyanco = require("./util/nyanco"); import nyanco from "./utils/nyanco";
const patterns = [ const patterns = [
[/ぬ.*る.*ぽ/, ({ say }) => say(`${nyanco()} < にゃーん`)], [/ぬ.*る.*ぽ/, ({ say }: any) => say(`${nyanco()} < にゃーん`)],
[ [
/だらず((さん)?.*)/, /だらず((さん)?.*)/,
({ context, say }) => ({ context, say }: any) =>
say( say(
`${nyanco()} < ${ `${nyanco()} < ${
context.matches[2] ? "にゃーん" : "さんを付けろよデコスケ野郎っ!" context.matches[2] ? "にゃーん" : "さんを付けろよデコスケ野郎っ!"
}` }`
), ),
], ],
[/こたつ/, ({ say }) => say(`${nyanco()} < しまえ`)], [/こたつ/, ({ say }: any) => say(`${nyanco()} < しまえ`)],
[/(しお|塩)/, ({ say }) => say(`${nyanco()} < しお`)], [/(しお|塩)/, ({ say }: any) => say(`${nyanco()} < しお`)],
[ [
/(らーめん|ラーメン|拉麺|らうめん)/, /(らーめん|ラーメン|拉麺|らうめん)/,
({ say }) => say(`${nyanco()} < :ramen:`), ({ say }: any) => say(`${nyanco()} < :ramen:`),
], ],
[/しりとり/, ({ say }) => say(`${nyanco()} < うどん。`)], [/しりとり/, ({ say }: any) => say(`${nyanco()} < うどん。`)],
[ [
/(糞|くそ|クソ)(すれ|スレ)/, /(糞|くそ|クソ)(すれ|スレ)/,
({ say }) => say(`${nyanco()} < クソスレで悪かったな!!`), ({ say }: any) => say(`${nyanco()} < クソスレで悪かったな!!`),
], ],
[ [
/(カレー|かれー|華麗)/, /(カレー|かれー|華麗)/,
({ say }) => say("https://pbs.twimg.com/media/C-RVt9pUAAARRVe.jpg"), ({ say }: any) => say("https://pbs.twimg.com/media/C-RVt9pUAAARRVe.jpg"),
], ],
[ [
/(すし|鮨|寿司|スシ|まぐろ|マグロ|sushi)/i, /(すし|鮨|寿司|スシ|まぐろ|マグロ|sushi)/i,
({ say }) => say(`${nyanco()} < あいよ っ :sushi:`), ({ say }: any) => say(`${nyanco()} < あいよ っ :sushi:`),
], ],
[/ちゃ|茶/, ({ say }) => say(`お茶どぞー < ${nyanco()}っ :tea:`)], [/ちゃ|茶/, ({ say }: any) => say(`お茶どぞー < ${nyanco()}っ :tea:`)],
[ [
/風邪|かぜ|カゼ|体調|つらい|くるしい|痛い|ひぎぃ|うぐぅ/, /風邪|かぜ|カゼ|体調|つらい|くるしい|痛い|ひぎぃ|うぐぅ/,
({ say }) => say(`おくすりどぞー < ${nyanco()}っ :pill:`), ({ say }: any) => say(`おくすりどぞー < ${nyanco()}っ :pill:`),
], ],
[ [
/(ちらし|チラシ|広告)/, /(ちらし|チラシ|広告)/,
({ say }) => say(`${nyanco()} < スタンプラリーやめれ`), ({ say }: any) => say(`${nyanco()} < スタンプラリーやめれ`),
], ],
[ [
/進捗どうですか/, /進捗どうですか/,
({ message, say }) => { ({ message, say }: any) => {
const from = `<@${message.user}>`; const from = `<@${message.user}>`;
return say( return say(
[ [
@ -56,7 +56,7 @@ const patterns = [
], ],
[ [
/(のむら|さちよ|野村|沙知代|さっちー|サッチー|のむさん|ノムサン)/, /(のむら|さちよ|野村|沙知代|さっちー|サッチー|のむさん|ノムサン)/,
({ say }) => ({ say }: any) =>
say( say(
[ [
":nomura-exodia-1::nomura-exodia-2::nomura-exodia-3:", ":nomura-exodia-1::nomura-exodia-2::nomura-exodia-3:",
@ -67,7 +67,7 @@ const patterns = [
], ],
[ [
/(肉|にく|ニク)/, /(肉|にく|ニク)/,
({ say }) => { ({ say }: any) => {
if (random([...Array(3).keys()]) !== 0) return; if (random([...Array(3).keys()]) !== 0) return;
return say( return say(
[ [
@ -81,7 +81,7 @@ const patterns = [
], ],
[ [
/(野球|やきゅう|やきう)/, /(野球|やきゅう|やきう)/,
({ say }) => { ({ say }: any) => {
if (random([...Array(10).keys()]) !== 0) return; if (random([...Array(10).keys()]) !== 0) return;
return say( return say(
[ [
@ -94,7 +94,7 @@ const patterns = [
], ],
[ [
/.*(ね|ネ).+(ハム|はむ)(たろう|たろー|タロウ|タロー|太郎)/, /.*(ね|ネ).+(ハム|はむ)(たろう|たろー|タロウ|タロー|太郎)/,
({ context, say }) => { ({ context, say }: any) => {
if ( if (
/(死|亡|殺)/.test(context.matches[0]) || /(死|亡|殺)/.test(context.matches[0]) ||
random([...Array(10).keys()]) === 0 random([...Array(10).keys()]) === 0
@ -107,9 +107,9 @@ const patterns = [
], ],
[ [
/^(?=.*[eE]macs)(?=.*[vV]i)/, /^(?=.*[eE]macs)(?=.*[vV]i)/,
({ say }) => say(`${nyanco()} < Emacs vs. Vi ファイ!`), ({ say }: any) => say(`${nyanco()} < Emacs vs. Vi ファイ!`),
], ],
]; ];
module.exports = (app) => export default (app: any) =>
patterns.forEach((pattern) => app.message(...pattern)); patterns.forEach((pattern) => app.message(...pattern));

10
src/scripts/nyanchu.ts Normal file
View file

@ -0,0 +1,10 @@
import { directMention } from "@slack/bolt";
export default [
directMention(),
/(ニャンちゅう) +(.*)/,
({ context, say }: any) => {
const oon = context.matches[2].split("").map((c: any) => `${c}`);
return say(`お゛ぉ゛ん!${oon.join("")}た゛に゛ゃあ゛ん! > :nyanchu:`);
},
];

View file

@ -1,5 +1,5 @@
const { directMention } = require("@slack/bolt"); import { directMention } from "@slack/bolt";
const random = require("./util/random"); import random from "./utils/random";
// Description: // Description:
// だらずさんはランダムにどれか選べます // だらずさんはランダムにどれか選べます
@ -10,10 +10,10 @@ const random = require("./util/random");
// えらべ <word1> <word2> ... <wordN> - どれか選ぶにゃん、 word の区切りは空白あるいはカンマにゃん。 // えらべ <word1> <word2> ... <wordN> - どれか選ぶにゃん、 word の区切りは空白あるいはカンマにゃん。
// 選べ <word1> <word2> ... <wordN> - どれか選ぶにゃん、 word の区切りは空白あるいはカンマにゃん。 // 選べ <word1> <word2> ... <wordN> - どれか選ぶにゃん、 word の区切りは空白あるいはカンマにゃん。
module.exports = [ export default [
directMention(), directMention(),
/(choice|random|えらべ|選べ) +(.*)/, /(choice|random|えらべ|選べ) +(.*)/,
({ context, say }) => { ({ context, say }: any) => {
const words = [ const words = [
...context.matches[2].split(/(?:,|\s)+/), ...context.matches[2].split(/(?:,|\s)+/),
"人に決められるだけの人生でいいのか?自分で決めようず", "人に決められるだけの人生でいいのか?自分で決めようず",

View file

@ -1,6 +1,6 @@
const { directMention } = require("@slack/bolt"); import { directMention } from "@slack/bolt";
const tellme = require("./util/tellme"); import tellme from "./utils/tellme";
const nyanco = require("./util/nyanco"); import nyanco from "./utils/nyanco";
// Description: // Description:
// だらずさんは何でも知っているので教えてくれます // だらずさんは何でも知っているので教えてくれます
@ -8,10 +8,10 @@ const nyanco = require("./util/nyanco");
// Synopsis: // Synopsis:
// tell me <phrase> - <phrase> について教えてあげよう、妖怪ウィキウィキペディアは使ってないよ! // tell me <phrase> - <phrase> について教えてあげよう、妖怪ウィキウィキペディアは使ってないよ!
module.exports = [ export default [
directMention(), directMention(),
/tell( ?me)? (.*)/i, /tell( ?me)? (.*)/i,
async ({ context, say }) => { async ({ context, say }: any) => {
const ans = await tellme(context.matches[2]); const ans = await tellme(context.matches[2]);
if (ans != null) return say(`${nyanco()} ${ans}`); if (ans != null) return say(`${nyanco()} ${ans}`);
}, },

30
src/scripts/unchiku.ts Normal file
View file

@ -0,0 +1,30 @@
import { makeTokenize } from "./utils/morpheme";
import random from "./utils/random";
import tellme from "./utils/tellme";
// ときどきうんちくを語ります
export default [
/[^\x01-\x7e]{4,}/,
async ({ context, say }: any) => {
if (random([...Array(15).keys()]) !== 0) return;
try {
const tokenize = await makeTokenize();
const words = tokenize(context.matches[0])
.filter(({ pos }: any) => pos === "名詞")
.map(({ surface_form }: any) => surface_form)
.filter((t: any) => !/^[\u3040-\u309F]$/.test(t)) //ひらがな1文字 http://www.unicode.org/charts/PDF/U3040.pdf
.filter((t: any) => !/^[\u30A0-\u30FF]$/.test(t)) //かたかな1文字 http://www.unicode.org/charts/PDF/U30A0.pdf
.filter((t: any) => !/^[\uFF65-\uFF9F]$/.test(t)) //半角カナ1文字 http://www.unicode.org/charts/PDF/UFF00.pdf
.filter((t: any) => !/[、・…]/.test(t));
const word = random(words);
const ans = await tellme(word);
if (/^(|…|しらないにゃーん)$/.test(ans)) {
throw new Error(`don't know ${word} : ${ans}`);
}
return say(`:nya-n: < 【う・ん・ち・く】${ans}`);
} catch (e) {
console.error(e);
}
},
];

View file

@ -1,11 +1,11 @@
const kuromoji = require("kuromoji"); import kuromoji, { Tokenizer, IpadicFeatures } from "kuromoji";
import path from "node:path";
const dicPath = require("path").resolve( const dicPath = path.resolve(require.resolve("kuromoji"), "../../dict");
require.resolve("kuromoji"),
"../../dict"
);
const useTokenize = () => export const makeTokenize = (): Promise<
Tokenizer<IpadicFeatures>["tokenize"]
> =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
kuromoji kuromoji
.builder({ dicPath }) .builder({ dicPath })
@ -27,12 +27,10 @@ const features = new Map([
["pronunciation", "発音"], ["pronunciation", "発音"],
]); ]);
const toCSV = (tokens) => export const toCSV = (tokens: any) =>
[ [
[...features.values()].join(","), [...features.values()].join(","),
...tokens.map((token) => ...tokens.map((token: any) =>
[...features.keys()].map((feature) => token[feature]).join(",") [...features.keys()].map((feature) => token[feature]).join(",")
), ),
].join("\n"); ].join("\n");
module.exports = { useTokenize, toCSV };

View file

@ -0,0 +1,5 @@
import random from "./random";
const nyans = [":nya-n1:", ":nya-n2:", ":nya-n3:", ":nya-n4:", ":nya-n5:"];
export default () => random(nyans);

View file

@ -0,0 +1 @@
export default (array: any) => array[Math.floor(Math.random() * array.length)];

View file

@ -1,4 +1,4 @@
module.exports = async (titles) => { export default async (titles: any) => {
const url = new URL("https://ja.wikipedia.org/w/api.php"); const url = new URL("https://ja.wikipedia.org/w/api.php");
const params = new URLSearchParams({ const params = new URLSearchParams({
action: "query", action: "query",
@ -6,7 +6,7 @@ module.exports = async (titles) => {
prop: "extracts", prop: "extracts",
titles, titles,
redirects: "", redirects: "",
exchars: 120, exchars: "120",
explaintext: "", explaintext: "",
}); });
try { try {
@ -18,12 +18,13 @@ module.exports = async (titles) => {
if (json == null || json.query == null || json.query.pages == null) { if (json == null || json.query == null || json.query.pages == null) {
throw new Error(["response is invalid", JSON.stringify(json)].join(":")); throw new Error(["response is invalid", JSON.stringify(json)].join(":"));
} }
const pages = new Map(Object.entries(json.query.pages)); const pages: Map<string, { extract: string }> = new Map(
Object.entries(json.query.pages)
);
return pages.size > 0 return pages.size > 0
? [...pages.values()].map(({ extract }) => extract).join("\n") ? [...pages.values()].map(({ extract }) => extract).join("\n")
: "しらないにゃーん"; : "しらないにゃーん";
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
return null;
}; };

View file

@ -1,22 +1,20 @@
const subMinutes = require("date-fns/subMinutes"); import { subMinutes, roundToNearestMinutes } from "date-fns";
const roundToNearestMinutes = require("date-fns/roundToNearestMinutes"); import { format, utcToZonedTime } from "date-fns-tz";
const format = require("date-fns-tz/format"); import nyanco from "./utils/nyanco";
const utcToZonedTime = require("date-fns-tz/utcToZonedTime");
const nyanco = require("./util/nyanco");
const timeZone = "Asia/Tokyo"; const timeZone = "Asia/Tokyo";
const prefNumber = 34; const prefNumber = 34;
const pageURL = `https://www.tenki.jp/radar/7/${prefNumber}/`; const pageURL = `https://www.tenki.jp/radar/7/${prefNumber}/`;
const imgURL = (target) => const imgURL = (target: any) =>
[ [
"https://static.tenki.jp/static-images/radar/", "https://static.tenki.jp/static-images/radar/",
format(target, "yyyy/MM/dd/HH/mm/ss", { timeZone }), format(target, "yyyy/MM/dd/HH/mm/ss", { timeZone }),
`/pref-${prefNumber}-large.jpg`, `/pref-${prefNumber}-large.jpg`,
].join(""); ].join("");
module.exports = [ export default [
/天気/, /天気/,
async ({ say }) => { async ({ say }: any) => {
const target = utcToZonedTime( const target = utcToZonedTime(
roundToNearestMinutes(subMinutes(new Date(), 5), { roundToNearestMinutes(subMinutes(new Date(), 5), {
nearestTo: 5, nearestTo: 5,
@ -26,11 +24,9 @@ module.exports = [
return say( return say(
[ [
`${nyanco()} ${format( `${nyanco()} ${format(target, "HH時mm分の雨雲の様子にゃーん", {
target, timeZone,
"HH時mm分の雨雲の様子にゃーん", })}`,
timeZone
)}`,
imgURL(target), imgURL(target),
pageURL, pageURL,
].join("\n") ].join("\n")

View file

@ -1,5 +1,5 @@
const random = require("./util/random"); import random from "./utils/random";
const nyanco = require("./util/nyanco"); import nyanco from "./utils/nyanco";
const yakitoris = [ const yakitoris = [
"―ロ@ロ@ロ- ヤキトリ", "―ロ@ロ@ロ- ヤキトリ",
@ -13,8 +13,8 @@ const yakitoris = [
"―>゚)))彡- 魚丸焼き", "―>゚)))彡- 魚丸焼き",
]; ];
module.exports = [ export default [
/焼鳥|焼き鳥|やきとり|ヤキトリ|串|プロキシ|プロクシ|proxy|Proxy|PROXY|ピロシキ/, /焼鳥|焼き鳥|やきとり|ヤキトリ|串|プロキシ|プロクシ|proxy|Proxy|PROXY|ピロシキ/,
({ say }) => ({ say }: any) =>
say(`串焼きでも食べるにゃん < ${nyanco()}${random(yakitoris)}`), say(`串焼きでも食べるにゃん < ${nyanco()}${random(yakitoris)}`),
]; ];

6
vercel.json Normal file
View file

@ -0,0 +1,6 @@
{
"functions": {
"api/slack/events.ts": { "includeFiles": "{dist,node_modules/kuromoji}/**" }
},
"build": { "env": { "NODEJS_AWS_HANDLER_NAME": "handler" } }
}

816
yarn.lock

File diff suppressed because it is too large Load diff