🐴 CPU時間を節約しないと動かないんだ!YO! (worker.js)
This commit is contained in:
parent
aaf9a6dd20
commit
728f19c23d
1 changed files with 88 additions and 79 deletions
167
worker.js
167
worker.js
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue