First commit

This commit is contained in:
2026-05-26 19:57:31 +09:00
commit c4e0d2763b
31 changed files with 27242 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
import config from "@/lib/config";
import { EOL } from "node:os";
type SuccessPosted<T = string> = {
ok: true;
data: {
status: "SUCCESS";
id: T;
source: "api";
deduped: boolean;
post: {
id: T;
content: string;
zone: "normal" | string;
created_at: string;
};
};
}
const BASE_URL = "https://collapse.jp/api/v3/bot";
export const createPost = async (data: {
text: string;
zone?: "normal" | string;
replyTarget?: "all" | string;
replyid?: string;
images?: [Blob, string][];
}, title: string) => {
const excessedMessage = "**👉返信に続きがあります。**";
let lines = data.text.split(EOL);
let firstUniqid = "";
let count = 0;
const results: SuccessPosted[] = [];
while (lines.length > 0) {
count++;
let currentText = "";
const maxLength = config.post.maxLength;
const limit = maxLength - (excessedMessage.length + EOL.length * 2);
while (lines.length > 0) {
const nextLine = lines[0];
if (nextLine === undefined)
break;
if (nextLine.length > limit && currentText === "") {
const targetLine = lines.shift() || "";
currentText = targetLine.slice(0, limit);
lines.unshift(targetLine.slice(limit));
break;
}
const potentialText = currentText ? currentText + EOL + nextLine : nextLine;
if (potentialText.length <= limit) {
currentText = potentialText;
lines.shift();
} else {
break;
}
}
currentText = currentText.trimEnd();
if (lines.length > 0) {
currentText += EOL.repeat(2) + excessedMessage;
}
let postedUniqid = "";
let success = false;
for (let attempt = 1; attempt <= config.post.maxRetries; attempt++) {
try {
const replyid = data.replyid === undefined && firstUniqid !== ""
? firstUniqid
: data.replyid;
const reply = replyid !== undefined
? `/${replyid}/reply`
: "";
const url = new URL(`/posts${reply}`, BASE_URL);
const body = new FormData();
body.append("content", data.text);
if (!replyid) {
body.append("zone", data.zone ?? "normal");
body.append("reply_restriction", data.replyTarget ?? "all");
}
if (data.images) {
data.images.forEach(img => body.append("images", img[0], img[1]));
}
const req = await fetch(url, {
method: "POST",
body: JSON.stringify({
content: data.text,
zone: data.zone ?? "normal",
reply_restriction: data.replyTarget ?? "all",
}),
headers: {
"Authorization": `Bearer ${config.mtweet.token}`,
},
});
const res = await req.json();
if (res.ok) {
success = true;
postedUniqid = res.data.id;
results.push(res);
break;
}
console.warn(`${title}の投稿に失敗しました (試行 ${attempt}/${config.post.maxRetries}):`, res.error.message);
if (attempt < config.post.maxRetries) {
const interval = res.error.details?.retry_after
? res.error.details.retry_after * 1000
: config.post.retryInterval;
await new Promise(resolve => setTimeout(resolve, interval));
}
} catch (err) {
console.error(err);
}
}
if (!success) {
console.error(`${title}の全試行が失敗したため、処理を中断します。`);
break;
}
if (firstUniqid === "")
firstUniqid = postedUniqid;
console.log(`${title}を投稿(${count}):`, postedUniqid);
while (lines.length > 0 && lines[0]?.trim() === "") {
lines.shift();
}
}
return results;
}