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