diff --git a/worker.js b/worker.js new file mode 100644 index 0000000..98cecd3 --- /dev/null +++ b/worker.js @@ -0,0 +1,158 @@ +// いじりやすいようにここに変数を集約させたい +const BLOCKLIST = ["hacker@example.com", "spammer@example.com"]; +const FORWARD_TO = "trendcreate2021@gmail.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"; + +export default { + async email(message, env, ctx) { + if (BLOCKLIST.includes(message.from)) { + message.setReject("Address is blocked"); + return; + } + + try { + const [text] = await Promise.all([ + getMailText(message), + message.forward(FORWARD_TO) + ]); + + const result = await notify(message.headers.get("from"), message.headers.get('subject'), text, message.headers.get("date")); + + if (!result.ok) { + console.log(await result.text()) + console.log(await result.json()) + return; + } + } catch (err) { + console.error("処理エラー:", err); + } + } +}; + +async function notify(from, subject, text, date) { + const payload = { + username: "contact@trendcreate.net", + avatar_url: WEBHOOK_ICON, + content: `**${from}**からお問い合わせメールが届いております。`, + embeds: [{ + author: {name: from || "名前なし", icon_url: AUTHOR_ICON}, + title: `**${subject || "件名なし"}**`, + description: text || "本文はありません。", + timestamp: new Date(date).toISOString(), + footer: { + text: 'Powered by Cloudflare Worker and Email Routing', + icon_url: FOOTER_ICON + } + }], + + }; + + // 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; +} + +async function getMailText(message) { + try { + const buf = await readStream(message.raw); + if (buf === "NO_CONTENT") { + return "" + } + return parseEmail(buf); + } catch (err) { + return `エラー: ${err.message}`; + } +} + +async function readStream(stream) { + if (typeof stream === "undefined") { + return "NO_CONTENT" + } + + const chunks = []; + const reader = stream.getReader(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + } + + const size = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(size); + + let pos = 0; + for (const chunk of chunks) { + result.set(chunk, pos); + pos += chunk.length; + } + + return result; +} + +function parseEmail(buffer) { + const text = new TextDecoder().decode(buffer); + const headerEnd = text.indexOf('\r\n\r\n'); + + if (headerEnd === -1) return ''; + + const header = text.substring(0, headerEnd); + 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 (!partBody) continue; + + if (isBase64) { + try { + const cleanBase64 = partBody.replace(/[\r\n\s]/g, ''); + return decodeBase64(cleanBase64); + } catch { + continue; + } + } else { + return partBody; + } + } + } + + return ''; +} + +function decodeBase64(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return new TextDecoder().decode(bytes); +} \ No newline at end of file