This commit is contained in:
Last2014 2025-08-26 22:29:05 +09:00
parent ca96f83a44
commit fb3e233244
13 changed files with 446 additions and 108 deletions

View File

@ -58,5 +58,6 @@ console.log("BOTサーバーが起動しました");
import config from "./config.js"; import config from "./config.js";
if (config.debug !== undefined) { if (config.debug !== undefined) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(styleText(["bgRed", "cyan", "bold"], "デバッグモードで起動中")); console.log(styleText(["bgRed", "cyan", "bold"], "デバッグモードで起動中"));
} }

View File

@ -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 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: This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org https://openfontlicense.org

BIN
miq/fonts/MPLUS.ttf Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,13 @@
import { createCanvas, loadImage, registerFont } from "canvas"; import { createCanvas, loadImage, registerFont } from "canvas";
import { writeFileSync } from "fs";
import sharp from "sharp"; import sharp from "sharp";
import { MiQOptions } from "./miq"; import { MiQOptions } from "./miq";
// フォント読み込み
registerFont("miq/fonts/MPLUS.ttf", {
family: "M PLUS Rounded 1c",
weight: "400",
});
function maxLengthCut( function maxLengthCut(
text: string, text: string,
maxLength: number, maxLength: number,
@ -18,7 +23,7 @@ function maxLengthCut(
function autoLineBreak( function autoLineBreak(
text: string, text: string,
maxWidth: number, maxWidth: number,
font: string = "48px Noto Sans JP" font: string = '48px "M PLUS Rounded 1c"'
): string { ): string {
const ctx = createCanvas(maxWidth, 100).getContext("2d"); const ctx = createCanvas(maxWidth, 100).getContext("2d");
ctx.font = font; ctx.font = font;
@ -52,18 +57,23 @@ function autoLineBreak(
function textReplace( function textReplace(
text: string, text: string,
maxWidth: number maxWidth: number,
font?: string
) { ) {
// 改行削除
text = text.replaceAll("\n", ""); text = text.replaceAll("\n", "");
text = text.replaceAll("\r", "");
// 自動改行
text = autoLineBreak(text, maxWidth, font);
// 100文字以上を省略
text = maxLengthCut(text, 100); text = maxLengthCut(text, 100);
text = autoLineBreak(text, maxWidth);
return text; return text;
} }
async function iconReplace( async function iconReplace(
color: boolean, color: boolean,
iconURL: string, iconURL: string
) { ) {
let result = ""; let result = "";
@ -102,9 +112,6 @@ export default async function MiQ({
userName, userName,
userID, userID,
}: MiQOptions) { }: MiQOptions) {
// フォント読み込み
registerFont("miq/fonts/NotoSansJP.ttf", { family: "Noto Sans JP" });
// 初期化 // 初期化
const canvas = createCanvas(1200, 630); const canvas = createCanvas(1200, 630);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
@ -122,26 +129,26 @@ export default async function MiQ({
ctx.drawImage(iconImg, 0, 0, iconSize, iconSize); ctx.drawImage(iconImg, 0, 0, iconSize, iconSize);
// ユーザー名描画 // ユーザー名描画
ctx.font = "38px Noto Sans JP"; ctx.font = '38px "M PLUS Rounded 1c"';
ctx.fillStyle = "white"; ctx.fillStyle = "white";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
let x = 910; let x = 910;
let y = 480; let y = 480;
ctx.fillText(`- ${userName}`, x, y, canvas.width-x); ctx.fillText(`- ${userName}`, x, y, canvas.width-iconSize);
// ユーザーID描画 // ユーザーID描画
ctx.font = "28px Noto Sans JP"; ctx.font = '28px "M PLUS Rounded 1c"';
ctx.fillStyle = "#3c3c3c"; ctx.fillStyle = "#b4b4b4";
ctx.fillText(`@${userID}`, x, y+50, canvas.width-x); ctx.fillText(`@${userID}`, x, y+50, canvas.width-iconSize);
// 本文描画 // 本文描画
const maxWidth = canvas.width - iconSize; const maxWidth = canvas.width - iconSize;
text = textReplace(text, maxWidth); ctx.font = '48px "M PLUS Rounded 1c"';
ctx.font = "48px Noto Sans JP";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.fillStyle = "white"; ctx.fillStyle = "white";
text = textReplace(text, maxWidth, ctx.font);
ctx.fillText(text, x, 80); ctx.fillText(text, x, 80);
// フェード描画 // フェード描画
@ -153,29 +160,15 @@ export default async function MiQ({
ctx.fillRect(0, 0, iconSize, canvas.height); ctx.fillRect(0, 0, iconSize, canvas.height);
// 返答 // 返答
if (type === "Buffer") { switch (type) {
return canvas.toBuffer(); case "Buffer":
} else if (type === "Base64Data") { return canvas.toBuffer();
return canvas.toDataURL() case "Base64Data":
.replace("data:image/png;base64,", ""); return canvas.toDataURL()
} else if (type === "Base64URL") { .replace("data:image/png;base64,", "");
return canvas.toDataURL(); case "Base64URL":
} else { return canvas.toDataURL();
return "Error: The type property is invalid."; 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"
);
})()*/

View File

@ -1,7 +1,7 @@
{ {
"name": "notice-uwuzu", "name": "notice-uwuzu",
"version": "v25.8.6@uwuzu1.6.4", "version": "v25.8.7@uwuzu1.6.4",
"tag": "v25.8.6", "tag": "v25.8.7",
"description": "Notice Bot for uwuzu", "description": "Notice Bot for uwuzu",
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {

View File

@ -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;
}
}

View File

@ -8,7 +8,10 @@ const helpsMin = {
"follow": "コマンド送信者をフォローします。", "follow": "コマンド送信者をフォローします。",
"unfollow": "コマンド送信者をフォロー解除します。", "unfollow": "コマンド送信者をフォロー解除します。",
"weather": "天気を返信します。", "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": "運営者に不具合などを報告します。", "report": "運営者に不具合などを報告します。",
"legal terms": "利用規約を返信します。", "legal terms": "利用規約を返信します。",
"legal privacy": "プライバシーポリシーを返信します。", "legal privacy": "プライバシーポリシーを返信します。",
@ -37,8 +40,24 @@ const helpsFull = {
`, `,
"miq": ` "miq": `
Make it a quoteを生成します Make it a Quoteを生成します
\`color: true\`と入力することでカラーモードに変更可能です。 \`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": ` "report": `
@ -74,11 +93,20 @@ export default async function Help(data: ueuse) {
console.log("ヘルプ:", ueuse); console.log("ヘルプ:", ueuse);
} else { } else {
const ueuse = await Reply(` if (Object.keys(helpsFull).indexOf(data.abi) === -1) {
${helpsFull[data.abi]} const ueuse = await Reply(`
${packageJson.repository.url}/wiki
`, data.uniqid); ${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);
}
} }
} }

View File

@ -10,10 +10,13 @@ import Help from "./help.js";
import Follow from "./follow.js"; import Follow from "./follow.js";
import UnFollow from "./unfollow.js"; import UnFollow from "./unfollow.js";
import Weather from "./weather.js"; import Weather from "./weather.js";
import MakeItAQuote from "./miq.js";
import Report from "./report.js"; import Report from "./report.js";
import Terms from "./legal/terms.js" import Terms from "./legal/terms.js"
import PrivacyPolicy from "./legal/privacy.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")) { if (!fs.existsSync("data/alreadyCommands.json")) {
@ -145,7 +148,6 @@ export default async function Commands() {
console.log("--------"); console.log("--------");
const commandName = await commandSearch(data.text); const commandName = await commandSearch(data.text);
console.log(commandName);
alreadyAdd(data.uniqid); alreadyAdd(data.uniqid);
@ -177,6 +179,15 @@ export default async function Commands() {
case "miq": case "miq":
MakeItAQuote(data); MakeItAQuote(data);
break; break;
case "miq permission":
MiQPermission(data);
break;
case "miq allow":
MiQAllow(data);
break;
case "delete":
Delete(data);
break;
default: default:
const reply = await Reply(` const reply = await Reply(`

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}