diff --git a/main.ts b/main.ts index 964ca25..46b4bf8 100644 --- a/main.ts +++ b/main.ts @@ -58,5 +58,6 @@ console.log("BOTサーバーが起動しました"); import config from "./config.js"; if (config.debug !== undefined) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; console.log(styleText(["bgRed", "cyan", "bold"], "デバッグモードで起動中")); } diff --git a/miq/fonts/LICENSE b/miq/fonts/COPYING similarity index 96% rename from miq/fonts/LICENSE rename to miq/fonts/COPYING index 927d970..08f3216 100644 --- a/miq/fonts/LICENSE +++ b/miq/fonts/COPYING @@ -1,5 +1,3 @@ -Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source' - This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: https://openfontlicense.org diff --git a/miq/fonts/MPLUS.ttf b/miq/fonts/MPLUS.ttf new file mode 100644 index 0000000..82cb336 Binary files /dev/null and b/miq/fonts/MPLUS.ttf differ diff --git a/miq/fonts/NotoSansJP.ttf b/miq/fonts/NotoSansJP.ttf deleted file mode 100644 index 4769abc..0000000 Binary files a/miq/fonts/NotoSansJP.ttf and /dev/null differ diff --git a/miq/main.ts b/miq/main.ts index 4157474..289ba3b 100644 --- a/miq/main.ts +++ b/miq/main.ts @@ -1,8 +1,13 @@ import { createCanvas, loadImage, registerFont } from "canvas"; -import { writeFileSync } from "fs"; import sharp from "sharp"; import { MiQOptions } from "./miq"; +// フォント読み込み +registerFont("miq/fonts/MPLUS.ttf", { + family: "M PLUS Rounded 1c", + weight: "400", +}); + function maxLengthCut( text: string, maxLength: number, @@ -18,7 +23,7 @@ function maxLengthCut( function autoLineBreak( text: string, maxWidth: number, - font: string = "48px Noto Sans JP" + font: string = '48px "M PLUS Rounded 1c"' ): string { const ctx = createCanvas(maxWidth, 100).getContext("2d"); ctx.font = font; @@ -52,18 +57,23 @@ function autoLineBreak( function textReplace( text: string, - maxWidth: number + maxWidth: number, + font?: string ) { + // 改行削除 text = text.replaceAll("\n", ""); + text = text.replaceAll("\r", ""); + // 自動改行 + text = autoLineBreak(text, maxWidth, font); + // 100文字以上を省略 text = maxLengthCut(text, 100); - text = autoLineBreak(text, maxWidth); return text; } async function iconReplace( color: boolean, - iconURL: string, + iconURL: string ) { let result = ""; @@ -102,9 +112,6 @@ export default async function MiQ({ userName, userID, }: MiQOptions) { - // フォント読み込み - registerFont("miq/fonts/NotoSansJP.ttf", { family: "Noto Sans JP" }); - // 初期化 const canvas = createCanvas(1200, 630); const ctx = canvas.getContext("2d"); @@ -122,26 +129,26 @@ export default async function MiQ({ ctx.drawImage(iconImg, 0, 0, iconSize, iconSize); // ユーザー名描画 - ctx.font = "38px Noto Sans JP"; + ctx.font = '38px "M PLUS Rounded 1c"'; ctx.fillStyle = "white"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; let x = 910; let y = 480; - ctx.fillText(`- ${userName}`, x, y, canvas.width-x); + ctx.fillText(`- ${userName}`, x, y, canvas.width-iconSize); // ユーザーID描画 - ctx.font = "28px Noto Sans JP"; - ctx.fillStyle = "#3c3c3c"; - ctx.fillText(`@${userID}`, x, y+50, canvas.width-x); + ctx.font = '28px "M PLUS Rounded 1c"'; + ctx.fillStyle = "#b4b4b4"; + ctx.fillText(`@${userID}`, x, y+50, canvas.width-iconSize); // 本文描画 const maxWidth = canvas.width - iconSize; - text = textReplace(text, maxWidth); - ctx.font = "48px Noto Sans JP"; + ctx.font = '48px "M PLUS Rounded 1c"'; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "white"; + text = textReplace(text, maxWidth, ctx.font); ctx.fillText(text, x, 80); // フェード描画 @@ -153,29 +160,15 @@ export default async function MiQ({ ctx.fillRect(0, 0, iconSize, canvas.height); // 返答 - if (type === "Buffer") { - return canvas.toBuffer(); - } else if (type === "Base64Data") { - return canvas.toDataURL() - .replace("data:image/png;base64,", ""); - } else if (type === "Base64URL") { - return canvas.toDataURL(); - } else { - return "Error: The type property is invalid."; + switch (type) { + case "Buffer": + return canvas.toBuffer(); + case "Base64Data": + return canvas.toDataURL() + .replace("data:image/png;base64,", ""); + case "Base64URL": + return canvas.toDataURL(); + default: + return "Error: The type property is invalid."; } } - -/*(async () => { - writeFileSync( - "miq/a.png", - await MiQ({ - type: "Buffer", - color: false, - text: "テスト", - iconURL: "https://media.uwuzu.net/uwuzu-net/files/3ov7dghkn7.webp", - userName: "Last2014", - userID: "last2014", - }), - "utf-8" - ); -})()*/ diff --git a/package.json b/package.json index 9271d73..26b0bba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "notice-uwuzu", - "version": "v25.8.6@uwuzu1.6.4", - "tag": "v25.8.6", + "version": "v25.8.7@uwuzu1.6.4", + "tag": "v25.8.7", "description": "Notice Bot for uwuzu", "main": "dist/main.js", "scripts": { diff --git a/scripts/commands/delete.ts b/scripts/commands/delete.ts new file mode 100644 index 0000000..6a392e3 --- /dev/null +++ b/scripts/commands/delete.ts @@ -0,0 +1,52 @@ +import { meApi, ueuse } from "../../types/types"; +import config from "../../config.js"; +import { Reply } from "./main.js"; + +export default async function Delete(data: ueuse) { + const me: meApi = await (await fetch(`${config.uwuzu.host}/api/me/`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + }), + })).json() + + const replyUeuse: ueuse = (await (await fetch(`${config.uwuzu.host}/api/ueuse/get`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: data.replyid, + }), + })).json())["0"]; + + if (me.userid === replyUeuse.account.userid) { + const ueuseDelete = await (await fetch(`${config.uwuzu.host}/api/ueuse/delete`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: data.replyid, + }), + })).json(); + + console.log("削除:", ueuseDelete); + + if (ueuseDelete.success === true) { + console.log("削除通知:", await Reply(` + 対象のユーズを削除しました。 + `, data.uniqid)); + } else { + console.log("削除失敗通知:", await Reply(` + 対象のユーズを削除できませんでした。 + `, data.uniqid)); + } + return; + } else { + console.log("削除失敗通知(他人):", await Reply(` + 削除するユーズが${me.username}のものではありません。 + そのため削除できませんでした。 + `, data.uniqid)); + return; + } +} diff --git a/scripts/commands/help.ts b/scripts/commands/help.ts index 4d7e811..4718bcc 100644 --- a/scripts/commands/help.ts +++ b/scripts/commands/help.ts @@ -8,7 +8,10 @@ const helpsMin = { "follow": "コマンド送信者をフォローします。", "unfollow": "コマンド送信者をフォロー解除します。", "weather": "天気を返信します。", - "miq": "Make it a quoteを生成します。", + "miq": "Make it a Quoteを生成します。", + "miq permission": "あなたに対してのMake it a Quoteを生成する要求者を制限できます。", + "miq allow": "Make it a Quoteの許可制にて生成を許可します。", + "delete": "ユーズを削除します。", "report": "運営者に不具合などを報告します。", "legal terms": "利用規約を返信します。", "legal privacy": "プライバシーポリシーを返信します。", @@ -37,8 +40,24 @@ const helpsFull = { 再取得して返信します。 `, "miq": ` - Make it a quoteを生成します。 + Make it a Quoteを生成します。 追記に\`color: true\`と入力することでカラーモードに変更可能です。 + \`/delete\`コマンドを使用して削除できます。 + `, + "miq permission": ` + あなたに対してのMake it a Quoteを生成する要求者を制限できます。 + 確認するには追記なしで実行してください。 + 変更するには以下のいずれかを追記に入力して実行してください。 + - \`me\`: 自分自身のみになります。あなたのみが生成でき、あなた以外が生成を要求すると拒否されます。 + - \`everyone\`: 全体公開になります。全てのユーザーが許可なしにあなたのユーズでMake it a Quoteを生成できます。 + - \`consent\`: 許可制になります。生成を要求されるとあなたにメンションが届き許可するかを選択できます。 + `, + "miq allow": ` + Make it a Quoteの許可制にて生成を許可します。 + 形式が異なる場合生成されません。 + `, + "delete": ` + BOTのユーズにて返信で使用すると返信先のユーズを削除します。 `, "report": ` 不具合などを運営者にメールで報告できます。 @@ -74,11 +93,20 @@ export default async function Help(data: ueuse) { console.log("ヘルプ:", ueuse); } else { - const ueuse = await Reply(` - ${helpsFull[data.abi]} - 機能を見る:${packageJson.repository.url}/wiki - `, data.uniqid); + if (Object.keys(helpsFull).indexOf(data.abi) === -1) { + const ueuse = await Reply(` + 不明なコマンドです。 + 機能を見る:${packageJson.repository.url}/wiki + `, data.uniqid); - console.log("ヘルプ:", ueuse); + console.log("ヘルプ(不明コマンド):", ueuse); + } else { + const ueuse = await Reply(` + ${helpsFull[data.abi]} + 機能を見る:${packageJson.repository.url}/wiki + `, data.uniqid); + + console.log("ヘルプ:", ueuse); + } } } diff --git a/scripts/commands/main.ts b/scripts/commands/main.ts index b5124ca..ca697f4 100644 --- a/scripts/commands/main.ts +++ b/scripts/commands/main.ts @@ -10,10 +10,13 @@ import Help from "./help.js"; import Follow from "./follow.js"; import UnFollow from "./unfollow.js"; import Weather from "./weather.js"; -import MakeItAQuote from "./miq.js"; import Report from "./report.js"; import Terms from "./legal/terms.js" import PrivacyPolicy from "./legal/privacy.js"; +import Delete from "./delete.js"; +import MakeItAQuote from "./miq/main.js"; +import MiQPermission from "./miq/permission.js"; +import MiQAllow from "./miq/allow.js"; // 初期化 if (!fs.existsSync("data/alreadyCommands.json")) { @@ -145,7 +148,6 @@ export default async function Commands() { console.log("--------"); const commandName = await commandSearch(data.text); - console.log(commandName); alreadyAdd(data.uniqid); @@ -177,6 +179,15 @@ export default async function Commands() { case "miq": MakeItAQuote(data); break; + case "miq permission": + MiQPermission(data); + break; + case "miq allow": + MiQAllow(data); + break; + case "delete": + Delete(data); + break; default: const reply = await Reply(` 不明なコマンドです。 diff --git a/scripts/commands/miq.ts b/scripts/commands/miq.ts deleted file mode 100644 index 901dedc..0000000 --- a/scripts/commands/miq.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ueuse } from "../../types/types"; -import MiQ from "../../miq/main.js"; -import config from "../../config.js"; - -export default async function MakeItAQuote(data: ueuse) { - let color: boolean; - let msg: string; - - if (data.abi === "color: true") { - msg = "カラーモードでMake it a quoteを生成しました。"; - color = true; - } else if (data.abi === "color: false") { - msg = "モノクロモードでMake it a quoteを生成しました。"; - color = false; - } else { - msg = "ご指定がないためモノクロモードでMake it a quoteを生成しました。"; - color = false; - } - - const ueuseData: ueuse = (await ( - await fetch(`${config.uwuzu.host}/api/ueuse/get`, { - method: "POST", - cache: "no-store", - body: JSON.stringify({ - token: config.uwuzu.apiToken, - uniqid: data.replyid, - }), - }) - ).json())["0"]; - - console.log(ueuseData) - - const img = await MiQ({ - type: "Base64Data", - color: color, - text: ueuseData.text, - iconURL: ueuseData.account.user_icon, - userName: ueuseData.account.username, - userID: ueuseData.account.userid, - }); - - const req = await fetch(`${config.uwuzu.host}/api/ueuse/create`, { - method: "POST", - body: JSON.stringify({ - token: config.uwuzu.apiToken, - text: msg, - image1: img, - replyid: data.uniqid, - }), - cache: "no-store", - }); - - const res = await req.json(); - - console.log("MiQ:", res); -} diff --git a/scripts/commands/miq/allow.ts b/scripts/commands/miq/allow.ts new file mode 100644 index 0000000..613b0de --- /dev/null +++ b/scripts/commands/miq/allow.ts @@ -0,0 +1,130 @@ +import { meApi, ueuse } from "../../../types/types"; +import { readFileSync } from "fs"; +import config from "../../../config.js"; +import { Reply } from "../main.js"; +import { Permission } from "./permission"; +import MiQ from "../../../miq/main.js"; + +export default async function MiQAllow(data: ueuse) { + if (!config.miq) { + console.log("MiQ(管理者無効):", await Reply(` + BOT管理者によってMake it a quoteが無効化されています。 + そのため\`/miq\`はご利用いただけません。 + `, data.uniqid)); + return; + } + + // 権限一覧取得 + const permissions: { [user: string]: Permission } = + JSON.parse(readFileSync("data/miqPermissions.json", "utf-8")); + + if (permissions[data.account.userid] !== "consent") { + console.log("MiQ許可制(許可制以外):", await Reply(` + あなたに対してのMake it a Quoteの生成要求者が許可制に設定されていません。 + そのため、\`/miq allow\`はご利用いただけません。 + `, data.uniqid)); + return; + } + + const confirmUeuse: ueuse = (await ( + await fetch(`${config.uwuzu.host}/api/ueuse/get`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: data.replyid, + }), + }) + ).json())["0"]; + + const me: meApi = await ( + await fetch(`${config.uwuzu.host}/api/me`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken + }), + }) + ).json() + + if (confirmUeuse.account.userid !== me.userid) { + console.log("MiQ許可制(誤アカウント):", await Reply(` + 返信先がこのBOTではありません。 + `, data.uniqid)); + return; + } + + if (confirmUeuse.replyid === "") { + console.log("MiQ許可制(誤ユーズ):", await Reply(` + 形式が正規ではありません。 + `, data.uniqid)); + return; + } + + const requestUeuse: ueuse = (await ( + await fetch(`${config.uwuzu.host}/api/ueuse/get`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: confirmUeuse.replyid, + }), + }) + ).json())["0"]; + + if (requestUeuse.replyid === "") { + console.log("MiQ許可制(誤ユーズ):", await Reply(` + 形式が正規ではありません。 + `, data.uniqid)); + return; + } + + const miqUeuse: ueuse = (await ( + await fetch(`${config.uwuzu.host}/api/ueuse/get`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: requestUeuse.replyid, + }), + }) + ).json())["0"]; + + let color: boolean; + let msg: string; + if (requestUeuse.abi === "color: true") { + msg = "カラーモードでMake it a Quoteを生成しました。"; + color = true; + } else if (requestUeuse.abi === "color: false") { + msg = "モノクロモードでMake it a Quoteを生成しました。"; + color = false; + } else { + msg = "ご指定がないためモノクロモードでMake it a Quoteを生成しました。"; + color = false; + } + + const img = await MiQ({ + type: "Base64Data", + color: color, + text: miqUeuse.text, + iconURL: miqUeuse.account.user_icon, + userName: miqUeuse.account.username, + userID: miqUeuse.account.userid, + }); + + const req = await fetch(`${config.uwuzu.host}/api/ueuse/create`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + text: msg, + image1: img, + nsfw: miqUeuse.nsfw, + replyid: data.uniqid, + }), + cache: "no-store", + }); + + const res = await req.json(); + + console.log("MiQ(許可制):", res); +} diff --git a/scripts/commands/miq/main.ts b/scripts/commands/miq/main.ts new file mode 100644 index 0000000..c4893a1 --- /dev/null +++ b/scripts/commands/miq/main.ts @@ -0,0 +1,102 @@ +import { ueuse } from "../../../types/types"; +import MiQ from "../../../miq/main.js"; +import config from "../../../config.js"; +import { Reply } from "../main.js"; +import { readFileSync, writeFileSync } from "fs"; +import { Permission } from "./permission"; + +export default async function MakeItAQuote(data: ueuse) { + if (!config.miq) { + console.log("MiQ(管理者無効):", await Reply(` + BOT管理者によってMake it a quoteが無効化されています。 + そのため\`/miq\`はご利用いただけません。 + `, data.uniqid)); + return; + } + + let color: boolean; + let msg: string; + + if (data.abi === "color: true") { + msg = "カラーモードでMake it a Quoteを生成しました。"; + color = true; + } else if (data.abi === "color: false") { + msg = "モノクロモードでMake it a Quoteを生成しました。"; + color = false; + } else { + msg = "ご指定がないためモノクロモードでMake it a Quoteを生成しました。"; + color = false; + } + + const ueuseData: ueuse = (await ( + await fetch(`${config.uwuzu.host}/api/ueuse/get`, { + method: "POST", + cache: "no-store", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + uniqid: data.replyid, + }), + }) + ).json())["0"]; + + console.log(ueuseData); + + // 権限一覧取得 + const permissions: { [user: string]: Permission } = + JSON.parse(readFileSync("data/miqPermissions.json", "utf-8")); + + // 初期化 + if (permissions[ueuseData.account.userid] === undefined) { + permissions[ueuseData.account.userid] = "consent"; + writeFileSync( + "data/miqPermissions.json", + JSON.stringify(permissions), + "utf-8" + ); + } + + if ( + permissions[ueuseData.account.userid] === "me" && + ueuseData.account.userid !== data.account.userid + ) { + console.log("MiQ(自分自身専用):", await Reply(` + 生成元ユーズの投稿者が生成要求者を自分自身のみに設定しています。 + しかし、あなたは投稿者自身ではないためこのユーズにMake it a Quoteを使用することはできません。 + `, data.uniqid)); + return; + } + + if (permissions[ueuseData.account.userid] === "consent") { + console.log("MiQ(許可制):", await Reply(` + 生成元ユーズの投稿者が生成要求者を許可制に設定しています。 + このユーズにMake it a Quoteを使用するには、 + @${ueuseData.account.userid}さんがこのユーズに返信で\`/miq allow\`を使用する必要があります。 + `, data.uniqid)); + return; + } + + const img = await MiQ({ + type: "Base64Data", + color: color, + text: ueuseData.text, + iconURL: ueuseData.account.user_icon, + userName: ueuseData.account.username, + userID: ueuseData.account.userid, + }); + + const req = await fetch(`${config.uwuzu.host}/api/ueuse/create`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + text: msg, + image1: img, + nsfw: data.nsfw, + replyid: data.uniqid, + }), + cache: "no-store", + }); + + const res = await req.json(); + + console.log("MiQ:", res); +} diff --git a/scripts/commands/miq/permission.ts b/scripts/commands/miq/permission.ts new file mode 100644 index 0000000..e813c23 --- /dev/null +++ b/scripts/commands/miq/permission.ts @@ -0,0 +1,79 @@ +import { ueuse } from "../../../types/types"; +import { Reply } from "../main.js"; +import config from "../../../config"; +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { request } from "express"; + +// 初期化 +const initialFile = {}; +if (!existsSync("data/miqPermissions.json")) { + writeFileSync( + "data/miqPermissions.json", + JSON.stringify(initialFile), + "utf-8" + ); +} + +export type Permission = +"consent" | +"everyone" | +"me"; + +const PermissionsNames: { [name: string]: string } = { + "consent": "許可制", + "everyone": "全体公開", + "me": "自身のみ", +} + +export default async function MiQPermission(data: ueuse) { + if (!config.miq) { + await Reply(` + BOT管理者によってMake it a quoteが無効化されています。 + そのため\`/miq\`はご利用いただけません。 + `, data.uniqid); + return; + } + + const permissions: { [user: string]: string } = + JSON.parse(readFileSync("data/miqPermissions.json", "utf-8")); + + // 初期化 + if (permissions[data.account.userid] === undefined) { + permissions[data.account.userid] = "consent"; + writeFileSync( + "data/miqPermissions.json", + JSON.stringify(permissions), + "utf-8" + ); + } + + if ( + data.abi === "" || + data.abi === "none" + ) { + await Reply(` + あなたに対してのMake it a Quoteを生成するための権限は「${PermissionsNames[permissions[data.account.userid]]}」です。 + `, data.uniqid); + return; + } + + const requestPermission = data.abi.trimEnd(); + + if (PermissionsNames[requestPermission] === undefined) { + await Reply(` + 変更希望の権限「${requestPermission}」はご指定できません。 + \`consent\`、\`everyone\`、\`me\`のいずれかからご選択ください。 + `, data.uniqid); + return; + } + + permissions[data.account.userid] = requestPermission; + writeFileSync( + "data/miqPermissions.json", + JSON.stringify(permissions), + "utf-8" + ); + await Reply(` + あなたに対してのMake it a Quoteを生成するための権限を「${PermissionsNames[requestPermission]}」に変更しました。 + `, data.uniqid); +}