🐴 CPU時間を節約しないと動かないんだ!YO! (worker.js)

This commit is contained in:
ひでまる 2025-03-11 14:35:00 +09:00
parent aaf9a6dd20
commit 728f19c23d

167
worker.js
View file

@ -1,36 +1,39 @@
// @ts-check // @ts-check
// いじりやすいようにここに変数を集約させたい // 設定値をグループ化
const BLOCKLIST = ["hacker@example.com", "spammer@example.com"]; const CONFIG = {
const FORWARD_TO = "mymail@example.com"; BLOCKLIST: ["hacker@example.com", "spammer@example.com"],
const WEBHOOK_URL = "https://discord.com/api/webhooks/xxx/xxx"; FORWARD_TO: "mymail@example.com",
// trendcreate icon };
const WEBHOOK_ICON =
"https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/webhook_icon.jpg"; const WEBHOOK = {
// mail letter icon URL: "https://discord.com/api/webhooks/xxx/xxx",
const AUTHOR_ICON = ICON: "https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/webhook_icon.jpg",
"https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/email_icon.png"; SENDER: "contact@trendcreate.net",
// cloudflare icon };
const FOOTER_ICON =
"https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/cloudflare_icon.ico"; const ICONS = {
AUTHOR:
"https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/email_icon.png",
FOOTER:
"https://git.v-sli.me/HidemaruOwO/email-worker/raw/branch/main/assets/cloudflare_icon.ico",
};
export default { export default {
async email(message, env, ctx) { async email(message, env, ctx) {
if (BLOCKLIST.includes(message.from)) { if (CONFIG.BLOCKLIST.includes(message.from)) {
message.setReject("Address is blocked"); message.setReject("Address is blocked");
return; return;
} }
try { try {
const [result] = await Promise.all([ const [notifyResult] = await Promise.allSettled([
notify(message), notify(message),
message.forward(FORWARD_TO), message.forward(CONFIG.FORWARD_TO),
]); ]);
if (!result.ok) { if (notifyResult.status === "fulfilled" && !notifyResult.value.ok) {
console.log(await result.text()); console.error("Discord通知失敗:", await notifyResult.value.text());
console.log(await result.json());
return;
} }
} catch (err) { } catch (err) {
console.error("処理エラー:", err); console.error("処理エラー:", err);
@ -38,79 +41,79 @@ export default {
}, },
}; };
// async function notify(from, subject, text, date) {
async function notify(message) { async function notify(message) {
const from = message.headers.get("from"); const from = message.headers.get("from") || "名前なし";
const subject = message.headers.get("subject"); const subject = message.headers.get("subject") || "件名なし";
const date = message.headers.get("date"); const date = message.headers.get("date");
const text = await getMailText(message); const text = await getMailText(message);
const payload = { const payload = {
username: "contact@trendcreate.net", username: WEBHOOK.SENDER,
avatar_url: WEBHOOK_ICON, avatar_url: WEBHOOK.ICON,
content: `**${from}**からお問い合わせメールが届いております。`, content: `**${from}**からお問い合わせメールが届いております。`,
embeds: [ embeds: [
{ {
author: { name: from || "名前なし", icon_url: AUTHOR_ICON }, author: { name: from, icon_url: ICONS.AUTHOR },
title: `**${subject || "件名なし"}**`, title: `**${subject}**`,
description: text || "本文はありません。", description: text || "本文はありません。",
timestamp: new Date(date).toISOString(), timestamp: date
? new Date(date).toISOString()
: new Date().toISOString(),
footer: { footer: {
text: "Powered by Cloudflare Worker and Email Routing", text: "Powered by Cloudflare Worker and Email Routing",
icon_url: FOOTER_ICON, icon_url: ICONS.FOOTER,
}, },
}, },
], ],
}; };
// console.log(JSON.stringify(payload)) return fetch(WEBHOOK.URL, {
method: "POST",
try { headers: { "Content-Type": "application/json" },
const result = await fetch(WEBHOOK_URL, { body: JSON.stringify(payload),
method: "POST", }).catch((err) => {
headers: { "Content-Type": "application/json" }, console.error("Webhook送信エラー:", err);
body: JSON.stringify(payload), throw err;
}); });
return result;
} catch (err) {
console.log(err);
throw new Error(err);
}
} }
async function getMailText(message) { async function getMailText(message) {
if (!message.raw) return "";
try { try {
const buf = await readStream(message.raw); const buffer = await streamToArrayBuffer(message.raw);
if (buf === "NO_CONTENT") { return parseEmail(buffer);
return "";
}
return parseEmail(buf);
} catch (err) { } catch (err) {
console.error("メール解析エラー:", err);
return `エラー: ${err.message}`; return `エラー: ${err.message}`;
} }
} }
async function readStream(stream) { async function streamToArrayBuffer(stream) {
if (typeof stream === "undefined") {
return "NO_CONTENT";
}
const chunks = [];
const reader = stream.getReader(); const reader = stream.getReader();
const chunks = [];
let totalSize = 0;
while (true) { while (true) {
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) break; if (done) break;
chunks.push(value);
if (value) {
chunks.push(value);
totalSize += value.length;
}
} }
const size = chunks.reduce((sum, chunk) => sum + chunk.length, 0); if (chunks.length === 1) {
const result = new Uint8Array(size); return chunks[0];
}
const result = new Uint8Array(totalSize);
let offset = 0;
let pos = 0;
for (const chunk of chunks) { for (const chunk of chunks) {
result.set(chunk, pos); result.set(chunk, offset);
pos += chunk.length; offset += chunk.length;
} }
return result; return result;
@ -126,43 +129,49 @@ function parseEmail(buffer) {
const body = text.substring(headerEnd + 4); const body = text.substring(headerEnd + 4);
const boundaryMatch = header.match(/boundary="?([^"\r\n]+)"?/i); const boundaryMatch = header.match(/boundary="?([^"\r\n]+)"?/i);
if (!boundaryMatch) return body.trim(); if (!boundaryMatch) return body.trim();
const boundary = `--${boundaryMatch[1]}`; const boundary = `--${boundaryMatch[1]}`;
const parts = body.split(boundary); const parts = body.split(boundary);
for (const part of parts) { for (const part of parts) {
if (part.includes("Content-Type: text/plain")) { if (!part.includes("Content-Type: text/plain")) continue;
const isBase64 = part.includes("Content-Transfer-Encoding: base64");
const partBody = part.split("\r\n\r\n")[1]?.trim();
if (!partBody) continue; const partHeaderEnd = part.indexOf("\r\n\r\n");
if (partHeaderEnd === -1) continue;
if (isBase64) { const partHeader = part.substring(0, partHeaderEnd);
try { const partBody = part.substring(partHeaderEnd + 4).trim();
const cleanBase64 = partBody.replace(/[\r\n\s]/g, "");
return decodeBase64(cleanBase64); if (!partBody) continue;
} catch {
continue; if (partHeader.includes("Content-Transfer-Encoding: base64")) {
} try {
} else { const cleanBase64 = partBody.replace(/[\r\n\s]/g, "");
return partBody; return decodeBase64(cleanBase64);
} catch {
continue;
} }
} }
return partBody;
} }
return ""; return "";
} }
function decodeBase64(base64) { function decodeBase64(base64) {
const binary = atob(base64); try {
const bytes = new Uint8Array(binary.length); const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) { for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i); bytes[i] = binary.charCodeAt(i);
}
return new TextDecoder().decode(bytes);
} catch (e) {
return "";
} }
return new TextDecoder().decode(bytes);
} }