UNPKG

astro-mail-obfuscation

Version:

Protect email addresses, phone numbers and other sensitive data from bots scraping the source code of your Astro app.

1 lines 5.33 kB
import{fileURLToPath}from"url";import{globby}from"globby";import pLimit from"p-limit";import{randomBytes}from"node:crypto";import{promises as fs}from"node:fs";import*as cheerio from"cheerio";function generateUserKey(){return randomBytes(16).toString("hex")}function generateUserSalt(length){const chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let result="";const bytes=randomBytes(length);for(let i=0;i<length;i++){result+=chars[bytes[i]%chars.length]}return result}const defaultAllowedTags=["h1","h2","h3","h4","h5","h6","p","a","label","ul","ol","li","strong","b","em","i","span"];export default function astroMailObfuscation(userOptions={}){const userKey=userOptions.userKey||generateUserKey();const userSalt=userOptions.userSalt||generateUserSalt(8);const options={fallbackText:userOptions.fallbackText||"[PROTECTED!]",userKey:userKey,userSalt:userSalt,concurrencyLimit:userOptions.concurrencyLimit||5,allowedTags:mergeAllowedTags(userOptions.allowedTags)};return{name:"astro-mail-obfuscation",hooks:{"astro:config:setup":({injectScript:injectScript})=>{try{const scriptUrl=new URL("./scripts/client.js",import.meta.url).href;injectScript("page",`import "${scriptUrl}";`)}catch(err){console.error("Astro-Mail-Obfuscation: Failed to inject script.",err)}},"astro:build:done":async({dir:dir})=>{try{const outDir=fileURLToPath(dir);const htmlFiles=await globby("**/*.html",{cwd:outDir,absolute:true});const limit=pLimit(options.concurrencyLimit);const tasks=htmlFiles.map((file=>limit((()=>processHtmlFile(file,options)))));await Promise.all(tasks);console.log("Astro-Mail-Obfuscation: Completed.")}catch(err){console.error("Astro-Mail-Obfuscation: Error during build.",err)}}}}}function mergeAllowedTags(userTags){const combinedTags=new Set;defaultAllowedTags.forEach((tag=>combinedTags.add(tag.toLowerCase())));if(userTags){userTags.forEach((tag=>combinedTags.add(tag.toLowerCase())))}return Array.from(combinedTags)}async function processHtmlFile(filePath,options){try{const html=await fs.readFile(filePath,"utf-8");const modifiedHtml=await modifyHtml(html,options);await fs.writeFile(filePath,modifiedHtml,"utf-8")}catch(err){console.error(`Astro-Mail-Obfuscation: Error processing file ${filePath}.`,err)}}async function modifyHtml(html,options){const $=cheerio.load(html);const selector="[data-obfuscation]";const allowedTags=options.allowedTags;$(selector).each(((_,element)=>{const $el=$(element);const tagName=$el[0].tagName.toLowerCase();if(!allowedTags.includes(tagName)){console.warn(`Astro-Mail-Obfuscation: <${tagName}> is not allowed. Element is skipped.`);return}const href=$el.attr("href")||"";const type=$el.attr("data-obfuscation")||"";const validTypes=["1","2","3"];const method=validTypes.includes(type)?type:(Math.floor(Math.random()*3)+1).toString();const content=$el.html()||"";const{userKey:key,userSalt:salt}=options;const{encrypt:encrypt}=encryptionMethods[method];const encryptedHref=href?encrypt(href,key,salt):null;const encryptedContent=encrypt(content,key,salt);if(encryptedContent){$el.empty();$el.append(`<noscript>${options.fallbackText}</noscript>`);$el.attr("data-7391",encryptedContent);$el.attr("data-obfuscation",method);if(encryptedHref){$el.attr("data-5647",encryptedHref).removeAttr("href")}}else{console.error(`Astro-Mail-Obfuscation: Encryption failed for element: ${$.html($el)}`)}}));if($("[data-5647]").length||$("[data-7391]").length){$("head").append(`<meta name="4758" content="${options.userKey}${options.userSalt}">`)}return $.html()}const encryptionMethods={1:{encrypt:(content,key)=>{if(!content||!key)return;try{const contentBuffer=Buffer.from(content,"utf-8");const keyBuffer=Buffer.from(key,"utf-8");const encrypted=Buffer.alloc(contentBuffer.length);for(let i=0;i<contentBuffer.length;i++){encrypted[i]=contentBuffer[i]^keyBuffer[i%keyBuffer.length]}return encrypted.toString("hex")}catch(err){console.error("Astro-Mail-Obfuscation: Encryption error in method 1.",err)}}},2:{encrypt:(content,key)=>{if(!content||!key)return;try{const contentBuffer=Buffer.from(content,"utf-8");const keyBuffer=Buffer.from(key,"utf-8");const xorResult=Buffer.alloc(contentBuffer.length);for(let i=0;i<contentBuffer.length;i++){xorResult[i]=contentBuffer[i]^keyBuffer[i%keyBuffer.length]}const base64=xorResult.toString("base64");const shouldReverse=key.charCodeAt(key.length-1)>key.charCodeAt(key.length-2);return shouldReverse?base64.split("").reverse().join(""):base64}catch(err){console.error("Astro-Mail-Obfuscation: Encryption error in method 2.",err)}}},3:{encrypt:(content,key,salt)=>{if(!content||!key||!salt)return;try{const contentBuffer=Buffer.from(content,"utf-8");const keyBuffer=Buffer.from(key,"utf-8");const saltBuffer=Buffer.from(salt,"utf-8");const encrypted=Buffer.alloc(contentBuffer.length);for(let i=0;i<contentBuffer.length;i++){encrypted[i]=contentBuffer[i]^keyBuffer[i%keyBuffer.length]^saltBuffer[i%saltBuffer.length]}return base62Encode(encrypted)}catch(err){console.error("Astro-Mail-Obfuscation: Encryption error in method 3.",err)}}}};function base62Encode(buffer){const chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";let value=BigInt("0x"+buffer.toString("hex"));let result="";const base=BigInt(62);if(value===BigInt(0))return"0";while(value>0){result=chars[Number(value%base)]+result;value/=base}return result}