v25.8.5@uwuzu1.6.4
This commit is contained in:
parent
db719b8312
commit
bbf2d66b57
|
|
@ -19,6 +19,7 @@ Automatic notification bot for uwuzu
|
||||||
- Follow back
|
- Follow back
|
||||||
- Unfollow
|
- Unfollow
|
||||||
- Weather Repost
|
- Weather Repost
|
||||||
|
- Make it a quote
|
||||||
- Startup requirements check
|
- Startup requirements check
|
||||||
- Check package existence
|
- Check package existence
|
||||||
- Required package version check
|
- Required package version check
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ const config: configTypes = {
|
||||||
weather: {
|
weather: {
|
||||||
splitCount: 4, // 返信の分割数
|
splitCount: 4, // 返信の分割数
|
||||||
},
|
},
|
||||||
|
// Make it a quote設定
|
||||||
|
miq: true, // 有効/無効
|
||||||
|
|
||||||
// 緊急時設定
|
// 緊急時設定
|
||||||
emergency: {
|
emergency: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { createCanvas, loadImage, registerFont } from "canvas";
|
||||||
|
import { writeFileSync } from "fs";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import { MiQOptions } from "./miq";
|
||||||
|
|
||||||
|
function textReplace(
|
||||||
|
text: string
|
||||||
|
) {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
// 改行削除
|
||||||
|
text = text.replaceAll("\n", "");
|
||||||
|
|
||||||
|
// 10文字/1回で改行を追加
|
||||||
|
let maxLength = 10;
|
||||||
|
if (text.length > maxLength) {
|
||||||
|
for (let i = 0; i < text.length; i += 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function iconReplace(
|
||||||
|
color: boolean,
|
||||||
|
iconURL: string,
|
||||||
|
) {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
const buffer = await(await fetch(iconURL, {
|
||||||
|
method: "GET",
|
||||||
|
cache: "no-store",
|
||||||
|
})).arrayBuffer();
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
const img = await sharp(Buffer.from(buffer))
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
result = `data:image/png;base64,${img.toString("base64")}`;
|
||||||
|
} else {
|
||||||
|
const img = await sharp(Buffer.from(buffer))
|
||||||
|
.png()
|
||||||
|
.grayscale()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
result = `data:image/png;base64,${img.toString("base64")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to generate
|
||||||
|
* Make it a quote on Node.js.
|
||||||
|
*/
|
||||||
|
export default async function MiQ({
|
||||||
|
type,
|
||||||
|
color,
|
||||||
|
text,
|
||||||
|
iconURL,
|
||||||
|
userName,
|
||||||
|
userID,
|
||||||
|
}: MiQOptions) {
|
||||||
|
// フォント読み込み
|
||||||
|
registerFont("miq/fonts/NotoSansJP.ttf", { family: "Noto Sans JP" });
|
||||||
|
|
||||||
|
// 初期化
|
||||||
|
const canvas = createCanvas(1200, 630);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// 背景描画
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// アイコン描画
|
||||||
|
const iconImg = await loadImage(await iconReplace(
|
||||||
|
color,
|
||||||
|
iconURL,
|
||||||
|
));
|
||||||
|
const iconSize = canvas.height;
|
||||||
|
ctx.drawImage(iconImg, 0, 0, iconSize, iconSize);
|
||||||
|
|
||||||
|
// ユーザー名描画
|
||||||
|
ctx.font = "38px Noto Sans JP";
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
let x = 670;
|
||||||
|
let y = 480;
|
||||||
|
ctx.fillText("-", x, y);
|
||||||
|
ctx.fillText(userName, x+40, y, canvas.width-(x+40));
|
||||||
|
|
||||||
|
// ユーザーID描画
|
||||||
|
ctx.font = "28px Noto Sans JP";
|
||||||
|
ctx.fillStyle = "#3c3c3c";
|
||||||
|
ctx.fillText(`@${userID}`, x+120, y+50, canvas.width-(x+120));
|
||||||
|
|
||||||
|
// 本文描画
|
||||||
|
ctx.font = "48px Noto Sans JP";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
text = textReplace(text);
|
||||||
|
x = 870;
|
||||||
|
y = 30;
|
||||||
|
ctx.fillText(text, x, y+50);
|
||||||
|
|
||||||
|
// フェード描画
|
||||||
|
const fadeColor = "black";
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
||||||
|
gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
|
||||||
|
gradient.addColorStop(0.5, fadeColor);
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fillRect(0, 0, iconSize, canvas.height);
|
||||||
|
|
||||||
|
// 返答
|
||||||
|
if (type === "Buffer") {
|
||||||
|
return canvas.toBuffer();
|
||||||
|
} else if (type === "Base64") {
|
||||||
|
return canvas.toDataURL();
|
||||||
|
} else {
|
||||||
|
return "Error: The type property is invalid.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
type MiQType =
|
||||||
|
"Buffer" |
|
||||||
|
"Base64"
|
||||||
|
|
||||||
|
export interface MiQOptions {
|
||||||
|
type: MiQType;
|
||||||
|
color: boolean;
|
||||||
|
text: string;
|
||||||
|
iconURL: string;
|
||||||
|
userName: string;
|
||||||
|
userID: string;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "notice-uwuzu",
|
"name": "notice-uwuzu",
|
||||||
"version": "v8.1.2@uwuzu1.6.1",
|
"version": "v25.8.5@uwuzu1.6.4",
|
||||||
"tag": "v8.1.2",
|
"tag": "v25.8.5",
|
||||||
"description": "Notice Bot for uwuzu",
|
"description": "Notice Bot for uwuzu",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -42,13 +42,16 @@
|
||||||
"@types/node": "^24.0.7",
|
"@types/node": "^24.0.7",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
"@types/sharp": "^0.31.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
|
"canvas": "^3.2.0",
|
||||||
"child_process": "^1.0.2",
|
"child_process": "^1.0.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"node-cron": "^4.1.1",
|
"node-cron": "^4.1.1",
|
||||||
"nodemailer": "^7.0.4",
|
"nodemailer": "^7.0.4",
|
||||||
|
"sharp": "^0.34.3",
|
||||||
"timers": "^0.1.1",
|
"timers": "^0.1.1",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ const initialFile: Array<string> = [];
|
||||||
|
|
||||||
// コマンド読み込み
|
// コマンド読み込み
|
||||||
import Info from "./info.js";
|
import Info from "./info.js";
|
||||||
|
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 Help from "./help.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";
|
||||||
|
|
@ -36,6 +37,43 @@ function cutAfterChar(str: string, char: string) {
|
||||||
return str.substring(index + 1);
|
return str.substring(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function commandSearch(text: string) {
|
||||||
|
// /のある行を特定
|
||||||
|
const lines = text.split(/\n/);
|
||||||
|
let slashLine: number = -1;
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].indexOf("/") !== -1) {
|
||||||
|
slashLine = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /がない場合は無を返答
|
||||||
|
if (slashLine === -1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOTのユーザーIDを取得
|
||||||
|
const userid: string = (await (await fetch(`${config.uwuzu.host}/api/me/`, {
|
||||||
|
method: "POST",
|
||||||
|
cache: "no-store",
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: config.uwuzu.apiToken,
|
||||||
|
}),
|
||||||
|
})).json()).userid;
|
||||||
|
|
||||||
|
// BOTへのメンションを削除
|
||||||
|
let slashLineText = lines[slashLine];
|
||||||
|
slashLineText = slashLineText.replace(`@${userid}`, "");
|
||||||
|
|
||||||
|
// /以降の文字を取得
|
||||||
|
slashLineText = cutAfterChar(slashLineText, "/");
|
||||||
|
|
||||||
|
// 前後の空白を削除
|
||||||
|
slashLineText = slashLineText.trimStart().trimEnd();
|
||||||
|
|
||||||
|
return slashLineText;
|
||||||
|
}
|
||||||
|
|
||||||
export async function Reply(text: string, reply: string) {
|
export async function Reply(text: string, reply: string) {
|
||||||
const req = await fetch(`${config.uwuzu.host}/api/ueuse/create`, {
|
const req = await fetch(`${config.uwuzu.host}/api/ueuse/create`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -99,16 +137,19 @@ export default async function Commands() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.text.indexOf("/") === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// コマンド処理
|
// コマンド処理
|
||||||
console.log("--------");
|
console.log("--------");
|
||||||
|
|
||||||
const commandName = cutAfterChar(data.text, "/");
|
const commandName = await commandSearch(data.text);
|
||||||
|
console.log(commandName);
|
||||||
|
|
||||||
alreadyAdd(data.uniqid);
|
alreadyAdd(data.uniqid);
|
||||||
|
|
||||||
switch (commandName) {
|
switch (commandName) {
|
||||||
case "":
|
|
||||||
break;
|
|
||||||
case "info":
|
case "info":
|
||||||
Info(data);
|
Info(data);
|
||||||
break;
|
break;
|
||||||
|
|
@ -133,6 +174,9 @@ export default async function Commands() {
|
||||||
case "weather":
|
case "weather":
|
||||||
Weather(data);
|
Weather(data);
|
||||||
break;
|
break;
|
||||||
|
case "miq":
|
||||||
|
MakeItAQuote(data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
const reply = await Reply(`
|
const reply = await Reply(`
|
||||||
不明なコマンドです。
|
不明なコマンドです。
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -76,6 +76,7 @@ export interface configTypes {
|
||||||
time: timeTypes,
|
time: timeTypes,
|
||||||
earthquake: earthquakeTypes;
|
earthquake: earthquakeTypes;
|
||||||
weather: weatherTypes;
|
weather: weatherTypes;
|
||||||
|
miq: boolean;
|
||||||
|
|
||||||
emergency: emergencyFullTypes | emergencyMinTypes;
|
emergency: emergencyFullTypes | emergencyMinTypes;
|
||||||
report: reportTypes;
|
report: reportTypes;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export interface meApi {
|
||||||
|
|
||||||
export interface ueuse {
|
export interface ueuse {
|
||||||
uniqid: string;
|
uniqid: string;
|
||||||
relpyid: string;
|
replyid: string;
|
||||||
reuseid: string;
|
reuseid: string;
|
||||||
text: string;
|
text: string;
|
||||||
account: {
|
account: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue