Compare commits

..

5 Commits

Author SHA1 Message Date
last2014 b9a4fe8763 v25.8.9 2025-08-26 22:54:56 +09:00
last2014 ed3606778e v25.8.8 2025-08-26 22:43:07 +09:00
last2014 fb3e233244 v25.8.7 2025-08-26 22:29:05 +09:00
last2014 ca96f83a44 Git LFS廃止 2025-08-25 16:51:05 +09:00
last2014 97af8da4dd v25.8.6 2025-08-25 16:30:46 +09:00
15 changed files with 509 additions and 116 deletions
BIN
View File
Binary file not shown.
+1
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"], "デバッグモードで起動中"));
} }
-2
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
Binary file not shown.
Binary file not shown.
+81 -41
View File
@@ -1,38 +1,79 @@
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";
function textReplace( // フォント読み込み
text: string registerFont("miq/fonts/MPLUS.ttf", {
family: "M PLUS Rounded 1c",
weight: "400",
});
function maxLengthCut(
text: string,
maxLength: number,
) { ) {
let result = "";
// 改行削除
text = text.replaceAll("\n", "");
// 10文字/1回で改行を追加
let maxLength = 10;
if (text.length > maxLength) { if (text.length > maxLength) {
for (let i = 0; i < text.length; i += maxLength) { text = text.substring(0, maxLength)
result += text.substring(i, i + maxLength) + "\n";
}
result = result.trimEnd();
}
// 80文字以上は「...」で省略
maxLength = 80;
if (result.length > maxLength) {
result = result.substring(0, maxLength)
+ "..."; + "...";
} }
return result; return text;
}
function autoLineBreak(
text: string,
maxWidth: number,
font: string = '48px "M PLUS Rounded 1c"'
): string {
const ctx = createCanvas(maxWidth, 100).getContext("2d");
ctx.font = font;
const lines: string[] = [];
let currentLine = "";
for (let i = 0; i < text.length; i++) {
const char = text[i];
const testLine = currentLine + char;
const width = ctx.measureText(testLine).width;
if (width > maxWidth) {
lines.push(currentLine);
currentLine = char;
while (ctx.measureText(currentLine).width > maxWidth && currentLine.length > 1) {
const cutPoint = currentLine.length - 1;
lines.push(currentLine.slice(0, cutPoint));
currentLine = currentLine.slice(cutPoint);
}
} else {
currentLine = testLine;
}
}
if (currentLine) lines.push(currentLine);
return lines.join("\n");
}
function textReplace(
text: string,
maxWidth: number,
font?: string
) {
// 改行削除
text = text.replaceAll("\n", "");
text = text.replaceAll("\r", "");
// 自動改行
text = autoLineBreak(text, maxWidth, font);
// 100文字以上を省略
text = maxLengthCut(text, 100);
return text;
} }
async function iconReplace( async function iconReplace(
color: boolean, color: boolean,
iconURL: string, iconURL: string
) { ) {
let result = ""; let result = "";
@@ -71,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");
@@ -91,29 +129,27 @@ 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 = "left"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
let x = 670; let x = 910;
let y = 480; let y = 480;
ctx.fillText("-", x, y); ctx.fillText(`- ${userName}`, x, y, canvas.width-iconSize);
ctx.fillText(userName, x+40, y, canvas.width-(x+40));
// ユーザー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+120, y+50, canvas.width-(x+120)); ctx.fillText(`@${userID}`, x, y+50, canvas.width-iconSize);
// 本文描画 // 本文描画
ctx.font = "48px Noto Sans JP"; const maxWidth = canvas.width - iconSize;
ctx.font = '48px "M PLUS Rounded 1c"';
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.fillStyle = "white"; ctx.fillStyle = "white";
text = textReplace(text); text = textReplace(text, maxWidth, ctx.font);
x = 870; ctx.fillText(text, x, 80);
y = 30;
ctx.fillText(text, x, y+50);
// フェード描画 // フェード描画
const fadeColor = "black"; const fadeColor = "black";
@@ -124,11 +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) {
case "Buffer":
return canvas.toBuffer(); return canvas.toBuffer();
} else if (type === "Base64") { case "Base64Data":
return canvas.toDataURL()
.replace("data:image/png;base64,", "");
case "Base64URL":
return canvas.toDataURL(); return canvas.toDataURL();
} else { default:
return "Error: The type property is invalid."; return "Error: The type property is invalid.";
} }
} }
+3 -2
View File
@@ -1,6 +1,7 @@
type MiQType = type MiQType =
"Buffer" | "Buffer" |
"Base64" "Base64Data" |
"Base64URL";
export interface MiQOptions { export interface MiQOptions {
type: MiQType; type: MiQType;
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "notice-uwuzu", "name": "notice-uwuzu",
"version": "v25.8.5@uwuzu1.6.4", "version": "v25.8.9@uwuzu1.6.4",
"tag": "v25.8.5", "tag": "v25.8.9",
"description": "Notice Bot for uwuzu", "description": "Notice Bot for uwuzu",
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {
+52
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;
}
}
+33
View File
@@ -8,6 +8,10 @@ const helpsMin = {
"follow": "コマンド送信者をフォローします。", "follow": "コマンド送信者をフォローします。",
"unfollow": "コマンド送信者をフォロー解除します。", "unfollow": "コマンド送信者をフォロー解除します。",
"weather": "天気を返信します。", "weather": "天気を返信します。",
"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": "プライバシーポリシーを返信します。",
@@ -35,6 +39,26 @@ const helpsFull = {
毎日7:00の天気を再投稿するわけではなく、 毎日7:00の天気を再投稿するわけではなく、
再取得して返信します。 再取得して返信します。
`, `,
"miq": `
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": ` "report": `
不具合などを運営者にメールで報告できます。 不具合などを運営者にメールで報告できます。
運営者によって有効化されていないと使用できません。 運営者によって有効化されていないと使用できません。
@@ -68,6 +92,14 @@ export default async function Help(data: ueuse) {
`, data.uniqid); `, data.uniqid);
console.log("ヘルプ:", ueuse); console.log("ヘルプ:", ueuse);
} else {
if (Object.keys(helpsFull).indexOf(data.abi) === -1) {
const ueuse = await Reply(`
不明なコマンドです。
機能を見る:${packageJson.repository.url}/wiki
`, data.uniqid);
console.log("ヘルプ(不明コマンド)", ueuse);
} else { } else {
const ueuse = await Reply(` const ueuse = await Reply(`
${helpsFull[data.abi]} ${helpsFull[data.abi]}
@@ -76,4 +108,5 @@ export default async function Help(data: ueuse) {
console.log("ヘルプ:", ueuse); console.log("ヘルプ:", ueuse);
} }
}
} }
+13 -2
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(`
不明なコマンドです。 不明なコマンドです。
-56
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: "Base64",
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);
}
+136
View File
@@ -0,0 +1,136 @@
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;
}
if (data.replyid === "") {
console.log("MiQ許可制(誤ユーズ)", await Reply(`
形式が正規ではありません。
`, data.uniqid));
}
// 権限一覧取得
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);
}
+102
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);
}
+78
View File
@@ -0,0 +1,78 @@
import { ueuse } from "../../../types/types";
import { Reply } from "../main.js";
import config from "../../../config.js";
import { readFileSync, writeFileSync, existsSync } from "fs";
// 初期化
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);
}