#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); // Configuration const config = { postsDirectory: './src/data/post', assetsDirectory: './src/assets/images', backupDirectory: './blog-posts-backup', logFile: './fix-blog-posts.log' }; // Logging utility class Logger { constructor(logFile) { this.logFile = logFile; this.logs = []; } log(level, message, file = null) { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level, message, file }; this.logs.push(logEntry); const logString = file ? `[${timestamp}] ${level.toUpperCase()}: ${message} (${file})` : `[${timestamp}] ${level.toUpperCase()}: ${message}`; console.log(logString); } async saveLogs() { const logContent = this.logs.map(entry => entry.file ? `[${entry.timestamp}] ${entry.level.toUpperCase()}: ${entry.message} (${entry.file})` : `[${entry.timestamp}] ${entry.level.toUpperCase()}: ${entry.message}` ).join('\n'); await writeFile(this.logFile, logContent); console.log(`\nLogs saved to ${this.logFile}`); } } // HTML entities mapping const htmlEntities = { // Common HTML entities '&': '&', '<': '<', '>': '>', '"': '"', ''': "'", ' ': ' ', // Extended HTML entities '©': '©', '®': '®', '™': '™', '…': '…', '—': '—', '–': '–', '“': '"', '”': '"', '‘': '\u2018', '’': '\u2019', '•': '•', '·': '·', '§': '§', '¶': '¶', '†': '†', '‡': '‡', '‰': '‰', '‹': '‹', '›': '›', '€': '€', '£': '£', '¥': '¥', '¢': '¢', '¤': '¤', // Common Unicode entities '🎯': '🎯', '📡': '📡', '📱': '📱', '📺': '📺', '📻': '📻', '📞': '📞', '📟': '📟', '📢': '📢', '📣': '📣', '📤': '📤', '📥': '📥', '📦': '📦', '📧': '📧', '📨': '📨', '📩': '📩', '📪': '📪', '📫': '📫', '📬': '📬', '📭': '📭', '📮': '📮', '📯': '📯', '📰': '📰', '📲': '📲', '📳': '📳', '📴': '📴', '📵': '📵', '📶': '📶', '📷': '📷', '📸': '📸', '📹': '📹', '📼': '📼', '📽': '📽', '📿': '📿', '🔀': '🔀', '🔁': '🔁', '🔂': '🔂', '🔃': '🔃', '🔄': '🔄', '🔅': '🔅', '🔆': '🔆', '🔇': '🔇', '🔈': '🔈', '🔉': '🔉', '🔊': '🔊', '🔋': '🔋', '🔌': '🔌', '🔍': '🔍', '🔎': '🔎', '🔏': '🔏', '🔐': '🔐', '🔑': '🔑', '🔒': '🔒', '🔓': '🔓', '🔔': '🔔', '🔕': '🔕', '🔖': '🔖', '🔗': '🔗', '🔘': '🔘', '🔙': '🔙', '🔚': '🔚', '🔛': '🔛', '🔜': '🔜', '🔝': '🔝', '🔞': '🔞', '🔟': '🔟', '🔠': '🔠', '🔡': '🔡', '🔢': '🔢', '🔣': '🔣', '🔤': '🔤', '🔥': '🔥', '🔦': '🔦', '🔧': '🔧', '🔨': '🔨', '🔩': '🔩', '🔪': '🔪', '🔫': '🔫', '🔬': '🔬', '🔭': '🔭', '🔮': '🔮', '🔯': '🔯', '🔰': '🔰', '🔱': '🔱', '🔲': '🔲', '🔳': '🔳', '🔴': '🔴', '🔵': '🔵', '🔶': '🔶', '🔷': '🔷', '🔸': '🔸', '🔹': '🔹', '🔺': '🔺', '🔻': '🔻', '🔼': '🔼', '🔽': '🔽', '🚀': '🚀', '🚁': '🚁', '🚂': '🚂', '🚃': '🚃', '🚄': '🚄', '🚅': '🚅', '🚆': '🚆', '🚇': '🚇', '🚈': '🚈', '🚉': '🚉', '🚠': '🚠', '🚡': '🚡', '🚢': '🚢', '🚣': '🚣', '🚤': '🚤', '🚥': '🚥', '🚦': '🚦', '🚧': '🚧', '🚨': '🚨', '🚩': '🚩', '🚪': '🚪', '🚫': '🚫', '🚬': '🚬', '🚭': '🚭', '🚮': '🚮', '🚯': '🚯', '🚰': '🚰', '🚱': '🚱', '🚲': '🚲', '🚳': '🚳', '🚴': '🚴', '🚵': '🚵', '🚶': '🚶', '🚷': '🚷', '🚸': '🚸', '🚹': '🚹', '🚺': '🚺', '🚻': '🚻', '🚼': '🚼', '🚽': '🚽', '🚾': '🚾', '🚿': '🚿', '🛀': '🛀', '🛁': '🛁', '🛂': '🛂', '🛃': '🛃', '🛄': '🛄', '🛅': '🛅', '🌍': '🌍', '🌎': '🌎', '🌏': '🌏', '🌐': '🌐', '🌑': '🌑', '🌒': '🌒', '🌓': '🌓', '🌔': '🌔', '🌕': '🌕', '🌖': '🌖', '🌗': '🌗', '🌘': '🌘', '🌙': '🌙', '🌚': '🌚', '🌛': '🌛', '🌜': '🌜', '🌝': '🌝', '🌞': '🌞', '🌟': '🌟', '🌠': '🌠', '🌡': '🌡', '🌤': '🌤', '🌥': '🌥', '🌦': '🌦', '🌧': '🌧', '🌨': '🌨', '🌩': '🌩', '🌪': '🌪', '🌫': '🌫', '🌬': '🌬', '🌭': '🌭', '🌮': '🌮', '🌯': '🌯', '🌰': '🌰', '🌱': '🌱', '🌲': '🌲', '🌳': '🌳', '🌴': '🌴', '🌵': '🌵', '🌶': '🌶', '🌷': '🌷', '🌸': '🌸', '🌹': '🌹', '🌺': '🌺', '🌻': '🌻', '🌼': '🌼', '🌽': '🌽', '🌾': '🌾', '🌿': '🌿', '🍀': '🍀', '🍁': '🍁', '🍂': '🍂', '🍃': '🍃', '🍄': '🍄', '🍅': '🍅', '🍆': '🍆', '🍇': '🍇', '🍈': '🍈', '🍉': '🍉', '🍊': '🍊', '🍋': '🍋', '🍌': '🍌', '🍍': '🍍', '🍎': '🍎', '🍏': '🍏', '🍐': '🍐', '🍑': '🍑', '🍒': '🍒', '🍓': '🍓', '🍔': '🍔', '🍕': '🍕', '🍖': '🍖', '🍗': '🍗', '🍘': '🍘', '🍙': '🍙', '🍚': '🍚', '🍛': '🍛', '🍜': '🍜', '🍝': '🍝', '🍞': '🍞', '🍟': '🍟', '🍠': '🍠', '🍡': '🍡', '🍢': '🍢', '🍣': '🍣', '🍤': '🍤', '🍥': '🍥', '🍦': '🍦', '🍧': '🍧', '🍨': '🍨', '🍩': '🍩', '🍪': '🍪', '🍫': '🍫', '🍬': '🍬', '🍭': '🍭', '🍮': '🍮', '🍯': '🍯', '🍰': '🍰', '🍱': '🍱', '🍲': '🍲', '🍳': '🍳', '🍴': '🍴', '🍵': '🍵', '🍶': '🍶', '🍷': '🍷', '🍸': '🍸', '🍹': '🍹', '🍺': '🍺', '🍻': '🍻', '🍼': '🍼', '🍽': '🍽', '🍾': '🍾', '🍿': '🍿', '🎀': '🎀', '🎁': '🎁', '🎂': '🎂', '🎃': '🎃', '🎄': '🎄', '🎅': '🎅', '🎆': '🎆', '🎇': '🎇', '🎈': '🎈', '🎉': '🎉', '🎊': '🎊', '🎋': '🎋', '🎌': '🎌', '🎍': '🎍', '🎎': '🎎', '🎏': '🎏', '🎐': '🎐', '🎑': '🎑', '🎒': '🎒', '🎓': '🎓', '🎖': '🎖', '🎗': '🎗', '🎙': '🎙', '🎚': '🎚', '🎛': '🎛', '🎞': '🎞', '🎟': '🎟', '🎠': '🎠', '🎡': '🎡', '🎢': '🎢', '🎣': '🎣', '🎤': '🎤', '🎥': '🎥', '🎦': '🎦', '🎧': '🎧', '🎨': '🎨', '🎩': '🎩', '🎪': '🎪', '🎫': '🎫', '🎬': '🎬', '🎭': '🎭', '🎮': '🎮', '🎰': '🎰', '🎱': '🎱', '🎲': '🎲', '🎳': '🎳', '🎴': '🎴', '🎵': '🎵', '🎶': '🎶', '🎷': '🎷', '🎸': '🎸', '🎹': '🎹', '🎺': '🎺', '🎻': '🎻', '🎼': '🎼', '🎽': '🎽', '🎾': '🎾', '🎿': '🎿', '🏀': '🏀', '🏁': '🏁', '🏂': '🏂', '🏃': '🏃', '🏄': '🏄', '🏅': '🏅', '🏆': '🏆', '🏇': '🏇', '🏈': '🏈', '🏉': '🏉', '🏊': '🏊', '🏋': '🏋', '🏌': '🏌', '🏍': '🏍', '🏎': '🏎', '🏏': '🏏', '🏐': '🏐', '🏑': '🏑', '🏒': '🏒', '🏓': '🏓', '🏔': '🏔', '🏕': '🏕', '🏖': '🏖', '🏗': '🏗', '🏘': '🏘', '🏙': '🏙', '🏚': '🏚', '🏛': '🏛', '🏜': '🏜', '🏝': '🏝', '🏞': '🏞', '🏟': '🏟', '🏠': '🏠', '🏡': '🏡', '🏢': '🏢', '🏣': '🏣', '🏤': '🏤', '🏥': '🏥', '🏦': '🏦', '🏧': '🏧', '🏨': '🏨', '🏩': '🏩', '🏪': '🏪', '🏫': '🏫', '🏬': '🏬', '🏭': '🏭', '🏮': '🏮', '🏯': '🏯', '🏰': '🏰', '🏳': '🏳', '🏴': '🏴', '🏵': '🏵', '🏷': '🏷', '🏸': '🏸', '🏹': '🏹', '🏺': '🏺', '🏻': '🏻', '🏼': '🏼', '🏽': '🏽', '🏾': '🏾', '🏿': '🏿', '🐀': '🐀', '🐁': '🐁', '🐂': '🐂', '🐃': '🐃', '🐄': '🐄', '🐅': '🐅', '🐆': '🐆', '🐇': '🐇', '🐈': '🐈', '🐉': '🐉', '🐊': '🐊', '🐋': '🐋', '🐌': '🐌', '🐍': '🐍', '🐎': '🐎', '🐏': '🐏', '🐐': '🐐', '🐑': '🐑', '🐒': '🐒', '🐓': '🐓', '🐔': '🐔', '🐕': '🐕', '🐖': '🐖', '🐗': '🐗', '🐘': '🐘', '🐙': '🐙', '🐚': '🐚', '🐛': '🐛', '🐜': '🐜', '🐝': '🐝', '🐞': '🐞', '🐟': '🐟', '🐠': '🐠', '🐡': '🐡', '🐢': '🐢', '🐣': '🐣', '🐤': '🐤', '🐥': '🐥', '🐦': '🐦', '🐧': '🐧', '🐨': '🐨', '🐩': '🐩', '🐪': '🐪', '🐫': '🐫', '🐬': '🐬', '🐭': '🐭', '🐮': '🐮', '🐯': '🐯', '🐰': '🐰', '🐱': '🐱', '🐲': '🐲', '🐳': '🐳', '🐴': '🐴', '🐵': '🐵', '🐶': '🐶', '🐷': '🐷', '🐸': '🐸', '🐹': '🐹', '🐺': '🐺', '🐻': '🐻', '🐼': '🐼', '🐽': '🐽', '🐾': '🐾', '🐿': '🐿', '👀': '👀', '👁': '👁', '👂': '👂', '👃': '👃', '👄': '👄', '👅': '👅', '👆': '👆', '👇': '👇', '👈': '👈', '👉': '👉', '👊': '👊', '👋': '👋', '👌': '👌', '👍': '👍', '👎': '👎', '👏': '👏', '👐': '👐', '👑': '👑', '👒': '👒', '👓': '👓', '👔': '👔', '👕': '👕', '👖': '👖', '👗': '👗', '👘': '👘', '👙': '👙', '👚': '👚', '👛': '👛', '👜': '👜', '👝': '👝', '👞': '👞', '👟': '👟', '👠': '👠', '👡': '👡', '👢': '👢', '👣': '👣', '👤': '👤', '👥': '👥', '👦': '👦', '👧': '👧', '👨': '👨', '👩': '👩', '👪': '👪', '👫': '👫', '👬': '👬', '👭': '👭', '👮': '👮', '👯': '👯', '👰': '👰', '👱': '👱', '👲': '👲', '👳': '👳', '👴': '👴', '👵': '👵', '👶': '👶', '👷': '👷', '👸': '👸', '👹': '👹', '👺': '👺', '👻': '👻', '👼': '👼', '👽': '👽', '👾': '👾', '👿': '👿', '💀': '💀', '💁': '💁', '💂': '💂', '💃': '💃', '💄': '💄', '💅': '💅', '💆': '💆', '💇': '💇', '💈': '💈', '💉': '💉', '💊': '💊', '💋': '💋', '💌': '💌', '💍': '💍', '💎': '💎', '💏': '💏', '💐': '💐', '💑': '💑', '💒': '💒', '💓': '💓', '💔': '💔', '💕': '💕', '💖': '💖', '💗': '💗', '💘': '💘', '💙': '💙', '💚': '💚', '💛': '💛', '💜': '💜', '💝': '💝', '💞': '💞', '💟': '💟', '💠': '💠', '💡': '💡', '💢': '💢', '💣': '💣', '💤': '💤', '💥': '💥', '💦': '💦', '💧': '💧', '💨': '💨', '💩': '💩', '💪': '💪', '💫': '💫', '💬': '💬', '💭': '💭', '💮': '💮', '💯': '💯', '💰': '💰', '💱': '💱', '💲': '💲', '💳': '💳', '💴': '💴', '💵': '💵', '💶': '💶', '💷': '💷', '💸': '💸', '💹': '💹', '💺': '💺', '💻': '💻', '💼': '💼', '💽': '💽', '💾': '💾', '💿': '💿', '📀': '📀', '📁': '📁', '📂': '📂', '📃': '📃', '📄': '📄', '📅': '📅', '📆': '📆', '📇': '📇', '📈': '📈', '📉': '📉', '📊': '📊', '📋': '📋', '📌': '📌', '📍': '📍', '📎': '📎', '📏': '📏', '📐': '📐', '📑': '📑', '📒': '📒', '📓': '📓', '📔': '📔', '📕': '📕', '📖': '📖', '📗': '📗', '📘': '📘', '📙': '📙', '📚': '📚', '📛': '📛', '📜': '📜', '📝': '📝', '📞': '📞', '📟': '📟', '📠': '📠', '📡': '📡', '📢': '📢', '📣': '📣', '📤': '📤', '📥': '📥', '📦': '📦', '📧': '📧', '📨': '📨', '📩': '📩', '📪': '📪', '📫': '📫', '📬': '📬', '📭': '📭', '📮': '📮', '📯': '📯', '📰': '📰', '📱': '📱', '📲': '📲', '📳': '📳', '📴': '📴', '📵': '📵', '📶': '📶', '📷': '📷', '📸': '📸', '📹': '📹', '📺': '📺', '📻': '📻', '📼': '📼', '📽': '📽', '📿': '📿', '🔀': '🔀', '🔁': '🔁', '🔂': '🔂', '🔃': '🔃', '🔄': '🔄', '🔅': '🔅', '🔆': '🔆', '🔇': '🔇', '🔈': '🔈', '🔉': '🔉', '🔊': '🔊', '🔋': '🔋', '🔌': '🔌', '🔍': '🔍', '🔎': '🔎', '🔏': '🔏', '🔐': '🔐', '🔑': '🔑', '🔒': '🔒', '🔓': '🔓', '🔔': '🔔', '🔕': '🔕', '🔖': '🔖', '🔗': '🔗', '🔘': '🔘', '🔙': '🔙', '🔚': '🔚', '🔛': '🔛', '🔜': '🔜', '🔝': '🔝', '🔞': '🔞', '🔟': '🔟', '🔠': '🔠', '🔡': '🔡', '🔢': '🔢', '🔣': '🔣', '🔤': '🔤', '🔥': '🔥', '🔦': '🔦', '🔧': '🔧', '🔨': '🔨', '🔩': '🔩', '🔪': '🔪', '🔫': '🔫', '🔬': '🔬', '🔭': '🔭', '🔮': '🔮', '🔯': '🔯', '🔰': '🔰', '🔱': '🔱', '🔲': '🔲', '🔳': '🔳', '🔴': '🔴', '🔵': '🔵', '🔶': '🔶', '🔷': '🔷', '🔸': '🔸', '🔹': '🔹', '🔺': '🔺', '🔻': '🔻', '🔼': '🔼', '🔽': '🔽', '🕉': '🕉', '🕊': '🕊', '🕋': '🕋', '🕌': '🕌', '🕍': '🕍', '🕎': '🕎', '🕐': '🕐', '🕑': '🕑', '🕒': '🕒', '🕓': '🕓', '🕔': '🕔', '🕕': '🕕', '🕖': '🕖', '🕗': '🕗', '🕘': '🕘', '🕙': '🕙', '🕚': '🕚', '🕛': '🕛', '🕜': '🕜', '🕝': '🕝', '🕞': '🕞', '🕟': '🕟', '🕠': '🕠', '🕡': '🕡', '🕢': '🕢', '🕣': '🕣', '🕤': '🕤', '🕥': '🕥', '🕦': '🕦', '🕧': '🕧', '🕯': '🕯', '🕰': '🕰', '🕳': '🕳', '🕴': '🕴', '🕵': '🕵', '🕶': '🕶', '🕷': '🕷', '🕸': '🕸', '🕹': '🕹', '🕺': '🕺', '🖇': '🖇', '🖊': '🖊', '🖋': '🖋', '🖌': '🖌', '🖍': '🖍', '🖐': '🖐', '🖕': '🖕', '🖖': '🖖', '🖤': '🖤', '🖥': '🖥', '🖨': '🖨', '🖱': '🖱', '🖲': '🖲', '🖼': '🖼', '🗂': '🗂', '🗃': '🗃', '🗄': '🗄', '🗑': '🗑', '🗒': '🗒', '🗓': '🗓', '🗜': '🗜', '🗝': '🗝', '🗞': '🗞', '🗡': '🗡', '🗣': '🗣', '🗨': '🗨', '🗯': '🗯', '🗳': '🗳', '🗺': '🗺', '🗻': '🗻', '🗼': '🗼', '🗽': '🗽', '🗾': '🗾', '🗿': '🗿', '😀': '😀', '😁': '😁', '😂': '😂', '😃': '😃', '😄': '😄', '😅': '😅', '😆': '😆', '😇': '😇', '😈': '😈', '😉': '😉', '😊': '😊', '😋': '😋', '😌': '😌', '😍': '😍', '😎': '😎', '😏': '😏', '😐': '😐', '😑': '😑', '😒': '😒', '😓': '😓', '😔': '😔', '😕': '😕', '😖': '😖', '😗': '😗', '😘': '😘', '😙': '😙', '😚': '😚', '😛': '😛', '😜': '😜', '😝': '😝', '😞': '😞', '😟': '😟', '😠': '😠', '😡': '😡', '😢': '😢', '😣': '😣', '😤': '😤', '😥': '😥', '😦': '😦', '😧': '😧', '😨': '😨', '😩': '😩', '😪': '😪', '😫': '😫', '😬': '😬', '😭': '😭', '😮': '😮', '😯': '😯', '😰': '😰', '😱': '😱', '😲': '😲', '😳': '😳', '😴': '😴', '😵': '😵', '😶': '😶', '😷': '😷', '😸': '😸', '😹': '😹', '😺': '😺', '😻': '😻', '😼': '😼', '😽': '😽', '😾': '😾', '😿': '😿', '🙀': '🙀', '🙁': '🙁', '🙂': '🙂', '🙃': '🙃', '🙄': '🙄', '🙅': '🙅', '🙆': '🙆', '🙇': '🙇', '🙈': '🙈', '🙉': '🙉', '🙊': '🙊', '🙋': '🙋', '🙌': '🙌', '🙍': '🙍', '🙎': '🙎', '🙏': '🙏', '🚀': '🚀', '🚁': '🚁', '🚂': '🚂', '🚃': '🚃', '🚄': '🚄', '🚅': '🚅', '🚆': '🚆', '🚇': '🚇', '🚈': '🚈', '🚉': '🚉', '🚊': '🚊', '🚋': '🚋', '🚌': '🚌', '🚍': '🚍', '🚎': '🚎', '🚏': '🚏', '🚐': '🚐', '🚑': '🚑', '🚒': '🚒', '🚓': '🚓', '🚔': '🚔', '🚕': '🚕', '🚖': '🚖', '🚗': '🚗', '🚘': '🚘', '🚙': '🚙', '🚚': '🚚', '🚛': '🚛', '🚜': '🚜', '🚝': '🚝', '🚞': '🚞', '🚟': '🚟', '🚠': '🚠', '🚡': '🚡', '🚢': '🚢', '🚣': '🚣', '🚤': '🚤', '🚥': '🚥', '🚦': '🚦', '🚧': '🚧', '🚨': '🚨', '🚩': '🚩', '🚪': '🚪', '🚫': '🚫', '🚬': '🚬', '🚭': '🚭', '🚮': '🚮', '🚯': '🚯', '🚰': '🚰', '🚱': '🚱', '🚲': '🚲', '🚳': '🚳', '🚴': '🚴', '🚵': '🚵', '🚶': '🚶', '🚷': '🚷', '🚸': '🚸', '🚹': '🚹', '🚺': '🚺', '🚻': '🚻', '🚼': '🚼', '🚽': '🚽', '🚾': '🚾', '🚿': '🚿', '🛀': '🛀', '🛁': '🛁', '🛂': '🛂', '🛃': '🛃', '🛄': '🛄', '🛅': '🛅', '🛋': '🛋', '🛌': '🛌', '🛍': '🛍', '🛎': '🛎', '🛏': '🛏', '🛐': '🛐', '🛑': '🛑', '🛒': '🛒', '🛕': '🛕', '🛠': '🛠', '🛡': '🛡', '🛢': '🛢', '🛣': '🛣', '🛤': '🛤', '🛥': '🛥', '🛩': '🛩', '🛫': '🛫', '🛬': '🛬', '🛰': '🛰', '🛳': '🛳', '🛴': '🛴', '🛵': '🛵', '🛶': '🛶', '🛷': '🛷', '🛸': '🛸', '🛹': '🛹', '🛺': '🛺', '🟠': '🟠', '🟡': '🟡', '🟢': '🟢', '🟣': '🟣', '🟤': '🟤', '🟥': '🟥', '🟦': '🟦', '🟧': '🟧', '🟨': '🟨', '🟩': '🟩', '🟪': '🟪', '🟫': '🟫', '🤍': '🤍', '🤎': '🤎', '🤏': '🤏', '🤐': '🤐', '🤑': '🤑', '🤒': '🤒', '🤓': '🤓', '🤔': '🤔', '🤕': '🤕', '🤖': '🤖', '🤗': '🤗', '🤘': '🤘', '🤙': '🤙', '🤚': '🤚', '🤛': '🤛', '🤜': '🤜', '🤝': '🤝', '🤞': '🤞', '🤟': '🤟', '🤠': '🤠', '🤡': '🤡', '🤢': '🤢', '🤣': '🤣', '🤤': '🤤', '🤥': '🤥', '🤦': '🤦', '🤧': '🤧', '🤨': '🤨', '🤩': '🤩', '🤪': '🤪', '🤫': '🤫', '🤬': '🤬', '🤭': '🤭', '🤮': '🤮', '🤯': '🤯', '🤰': '🤰', '🤱': '🤱', '🤲': '🤲', '🤳': '🤳', '🤴': '🤴', '🤵': '🤵', '🤶': '🤶', '🤷': '🤷', '🤸': '🤸', '🤹': '🤹', '🤺': '🤺', '🤼': '🤼', '🤽': '🤽', '🤾': '🤾', '🥀': '🥀', '🥁': '🥁', '🥂': '🥂', '🥃': '🥃', '🥄': '🥄', '🥅': '🥅', '🥇': '🥇', '🥈': '🥈', '🥉': '🥉', '🥊': '🥊', '🥋': '🥋', '🥌': '🥌', '🥍': '🥍', '🥎': '🥎', '🥏': '🥏', '🥐': '🥐', '🥑': '🥑', '🥒': '🥒', '🥓': '🥓', '🥔': '🥔', '🥕': '🥕', '🥖': '🥖', '🥗': '🥗', '🥘': '🥘', '🥙': '🥙', '🥚': '🥚', '🥛': '🥛', '🥜': '🥜', '🥝': '🥝', '🥞': '🥞', '🥟': '🥟', '🥠': '🥠', '🥡': '🥡', '🥢': '🥢', '🥣': '🥣', '🥤': '🥤', '🥥': '🥥', '🥦': '🥦', '🥧': '🥧', '🥨': '🥨', '🥩': '🥩', '🥪': '🥪', '🥫': '🥫', '🥬': '🥬', '🥭': '🥭', '🥮': '🥮', '🥯': '🥯', '🥰': '🥰', '🥱': '🥱', '🥲': '🥲', '🥳': '🥳', '🥴': '🥴', '🥵': '🥵', '🥶': '🥶', '🥷': '🥷', '🥸': '🥸', '🥹': '🥹', '🥺': '🥺', '🥻': '🥻', '🥼': '🥼', '🥽': '🥽', '🥾': '🥾', '🥿': '🥿', '🦀': '🦀', '🦁': '🦁', '🦂': '🦂', '🦃': '🦃', '🦄': '🦄', '🦅': '🦅', '🦆': '🦆', '🦇': '🦇', '🦈': '🦈', '🦉': '🦉', '🦊': '🦊', '🦋': '🦋', '🦌': '🦌', '🦍': '🦍', '🦎': '🦎', '🦏': '🦏', '🦐': '🦐', '🦑': '🦑', '🦒': '🦒', '🦓': '🦓', '🦔': '🦔', '🦕': '🦕', '🦖': '🦖', '🦗': '🦗', '🦘': '🦘', '🦙': '🦙', '🦚': '🦚', '🦛': '🦛', '🦜': '🦜', '🦝': '🦝', '🦞': '🦞', '🦟': '🦟', '🦠': '🦠', '🦡': '🦡', '🦢': '🦢', '🦣': '🦣', '🦤': '🦤', '🦥': '🦥', '🦦': '🦦', '🦧': '🦧', '🦨': '🦨', '🦩': '🦩', '🦪': '🦪', '🦫': '🦫', '🦬': '🦬', '🦭': '🦭', '🦮': '🦮', '🦯': '🦯', '🦰': '🦰', '🦱': '🦱', '🦲': '🦲', '🦳': '🦳', '🦴': '🦴', '🦵': '🦵', '🦶': '🦶', '🦷': '🦷', '🦸': '🦸', '🦹': '🦹', '🦺': '🦺', '🦻': '🦻', '🦼': '🦼', '🦽': '🦽', '🦾': '🦾', '🦿': '🦿', '🧀': '🧀', '🧁': '🧁', '🧂': '🧂', '🧃': '🧃', '🧄': '🧄', '🧅': '🧅', '🧆': '🧆', '🧇': '🧇', '🧈': '🧈', '🧉': '🧉', '🧊': '🧊', '🧋': '🧋', '🧌': '🧌', '🧍': '🧍', '🧎': '🧎', '🧏': '🧏', '🧐': '🧐', '🧑': '🧑', '🧒': '🧒', '🧓': '🧓', '🧔': '🧔', '🧕': '🧕', '🧖': '🧖', '🧗': '🧗', '🧘': '🧘', '🧙': '🧙', '🧚': '🧚', '🧛': '🧛', '🧜': '🧜', '🧝': '🧝', '🧞': '🧞', '🧟': '🧟', '🧠': '🧠', '🧡': '🧡', '🧢': '🧢', '🧣': '🧣', '🧤': '🧤', '🧥': '🧥', '🧦': '🧦', '🧧': '🧧', '🧨': '🧨', '🧩': '🧩', '🧪': '🧪', '🧫': '🧫', '🧬': '🧬', '🧭': '🧭', '🧮': '🧮', '🧯': '🧯', '🧰': '🧰', '🧱': '🧱', '🧲': '🧲', '🧳': '🧳', '🧴': '🧴', '🧵': '🧵', '🧶': '🧶', '🧷': '🧷', '🧸': '🧸', '🧹': '🧹', '🧺': '🧺', '🧻': '🧻', '🧼': '🧼', '🧽': '🧽', '🧾': '🧾', '🧿': '🧿', '🩰': '🩰', '🩱': '🩱', '🩲': '🩲', '🩳': '🩳', '🩴': '🩴', '🩸': '🩸', '🩹': '🩹', '🩺': '🩺', '🪀': '🪀', '🪁': '🪁', '🪂': '🪂', '🪃': '🪃', '🪄': '🪄', '🪅': '🪅', '🪆': '🪆', '🪐': '🪐', '🪑': '🪑', '🪒': '🪒', '🪓': '🪓', '🪔': '🪔', '🪕': '🪕', '🪖': '🪖', '🪗': '🪗', '🪘': '🪘', '🪙': '🪙', '🪚': '🪚', '🪛': '🪛', '🪜': '🪜', '🪝': '🪝', '🪞': '🪞', '🪟': '🪟', '🪠': '🪠', '🪡': '🪡', '🪢': '🪢', '🪣': '🪣', '🪤': '🪤', '🪥': '🪥', '🪦': '🪦', '🪧': '🪧', '🪨': '🪨', '🪰': '🪰', '🪱': '🪱', '🪲': '🪲', '🪳': '🪳', '🪴': '🪴', '🪵': '🪵', '🪶': '🪶', '🫀': '🫀', '🫁': '🫁', '🫂': '🫂', '🫐': '🫐', '🫑': '🫑', '🫒': '🫒', '🫓': '🫓', '🫔': '🫔', '🫕': '🫕', '🫖': '🫖', '🫠': '🫠', '🫡': '🫡', '🫢': '🫢', '🫣': '🫣', '🫤': '🫤', '🫥': '🫥', '🫦': '🫦', '🫧': '🫧', '🫰': '🫰', '🫱': '🫱', '🫲': '🫲', '🫳': '🫳', '🫴': '🫴', '🫵': '🫵', '🫶': '🫶', '‼': '‼', '⁉': '⁉', '™': '™', 'ℹ': 'ℹ', '↔': '↔', '↕': '↕', '↖': '↖', '↗': '↗', '↘': '↘', '↙': '↙', '↩': '↩', '↪': '↪', '⌚': '⌚', '⌛': '⌛', '⌨': '⌨', '⏏': '⏏', '⏩': '⏩', '⏪': '⏪', '⏫': '⏫', '⏬': '⏬', '⏭': '⏭', '⏮': '⏮', '⏯': '⏯', '⏰': '⏰', '⏱': '⏱', '⏲': '⏲', '⏳': '⏳', '⏸': '⏸', '⏹': '⏹', '⏺': '⏺', 'Ⓜ': 'Ⓜ', '▪': '▪', '▫': '▫', '▶': '▶', '◀': '◀', '◻': '◻', '◼': '◼', '◽': '◽', '◾': '◾', '☀': '☀', '☁': '☁', '☂': '☂', '☃': '☃', '☄': '☄', '☎': '☎', '☑': '☑', '☔': '☔', '☕': '☕', '☘': '☘', '☝': '☝', '☠': '☠', '☢': '☢', '☣': '☣', '☦': '☦', '☪': '☪', '☮': '☮', '☯': '☯', '☸': '☸', '☹': '☹', '☺': '☺', '♀': '♀', '♂': '♂', '♈': '♈', '♉': '♉', '♊': '♊', '♋': '♋', '♌': '♌', '♍': '♍', '♎': '♎', '♏': '♏', '♐': '♐', '♑': '♑', '♒': '♒', '♓': '♓', '♟': '♟', '♠': '♠', '♣': '♣', '♥': '♥', '♦': '♦', '♨': '♨', '♻': '♻', '♾': '♾', '♿': '♿', '⚒': '⚒', '⚓': '⚓', '⚔': '⚔', '⚕': '⚕', '⚖': '⚖', '⚗': '⚗', '⚙': '⚙', '⚛': '⚛', '⚜': '⚜', '⚠': '⚠', '⚡': '⚡', '⚪': '⚪', '⚫': '⚫', '⚰': '⚰', '⚱': '⚱', '⚽': '⚽', '⚾': '⚾', '⛄': '⛄', '⛅': '⛅', '⛈': '⛈', '⛎': '⛎', '⛏': '⛏', '⛑': '⛑', '⛓': '⛓', '⛔': '⛔', '⛩': '⛩', '⛪': '⛪', '⛰': '⛰', '⛱': '⛱', '⛲': '⛲', '⛳': '⛳', '⛴': '⛴', '⛵': '⛵', '⛷': '⛷', '⛸': '⛸', '⛹': '⛹', '⛺': '⛺', '⛽': '⛽', '✂': '✂', '✅': '✅', '✈': '✈', '✉': '✉', '✊': '✊', '✋': '✋', '✌': '✌', '✍': '✍', '✏': '✏', '✒': '✒', '✔': '✔', '✖': '✖', '✝': '✝', '✡': '✡', '✨': '✨', '✳': '✳', '✴': '✴', '❄': '❄', '❇': '❇', '❌': '❌', '❎': '❎', '❓': '❓', '❔': '❔', '❕': '❕', '❗': '❗', '❣': '❣', '❤': '❤', '➕': '➕', '➖': '➖', '➗': '➗', '➡': '➡', '➰': '➰', '➿': '➿', '⤴': '⤴', '⤵': '⤵', '⬅': '⬅', '⬆': '⬆', '⬇': '⬇', '⬛': '⬛', '⬜': '⬜', '⭐': '⭐', '⭕': '⭕', '〰': '〰', '〽': '〽', '㊗': '㊗', '㊙': '㊙', '': '󠁪', '️': '️' }; // Function to convert HTML entities to Unicode function decodeHtmlEntities(text) { let result = text; // First handle numeric entities (both decimal and hex) result = result.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => { const codePoint = parseInt(hex, 16); return String.fromCodePoint(codePoint); }); result = result.replace(/&#(\d+);/g, (match, decimal) => { const codePoint = parseInt(decimal, 10); return String.fromCodePoint(codePoint); }); // Then handle named entities for (const [entity, unicode] of Object.entries(htmlEntities)) { result = result.replace(new RegExp(entity.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), unicode); } return result; } // Function to convert WordPress caption shortcodes to Markdown function convertWordPressCaption(content) { // Pattern to match WordPress caption shortcode with markdown image inside const captionPattern = /\[caption[^\]]*\](!\[[^\]]*\]\([^)]+\))([^[\]]*)\[\/caption\]/gi; return content.replace(captionPattern, (match, imgMarkdown, caption) => { const cleanCaption = caption.trim(); if (cleanCaption) { return `${imgMarkdown}\n*${cleanCaption}*`; } else { return imgMarkdown; } }); } // Function to convert WordPress image URLs to Astro asset paths function convertWordPressUrlToAssetPath(url) { // Pattern to match WordPress upload URLs const wpUploadPattern = /https?:\/\/[^\/]+\/wp-content\/uploads\/(\d{4})\/(\d{2})\/(.+)/; const match = url.match(wpUploadPattern); if (match) { const [, year, month, filename] = match; return `~/assets/images/${year}/${month}/${filename}`; } // If it's already an asset path, return as is if (url.startsWith('~/assets/images/')) { return url; } // If it's a relative path, convert to asset path if (url.startsWith('/assets/images/')) { return `~${url}`; } // For other cases, return as is return url; } // Function to find existing image files async function findExistingImages(assetsDir) { const imageMap = new Map(); async function scanDirectory(dir) { try { const entries = await readdir(dir); for (const entry of entries) { const fullPath = path.join(dir, entry); const stats = await stat(fullPath); if (stats.isDirectory()) { await scanDirectory(fullPath); } else if (stats.isFile() && /\.(jpg|jpeg|png|gif|svg|webp)$/i.test(entry)) { const relativePath = path.relative(assetsDir, fullPath); const assetPath = `~/assets/images/${relativePath.replace(/\\/g, '/')}`; imageMap.set(entry.toLowerCase(), assetPath); // Also map without extension for flexible matching const nameWithoutExt = entry.replace(/\.[^.]+$/, '').toLowerCase(); if (!imageMap.has(nameWithoutExt)) { imageMap.set(nameWithoutExt, assetPath); } } } } catch (error) { // Directory might not exist, continue } } await scanDirectory(assetsDir); return imageMap; } // Function to process a single markdown file async function processMarkdownFile(filePath, logger, imageMap) { try { const content = await readFile(filePath, 'utf-8'); const originalContent = content; let processedContent = content; let changes = []; // 1. Convert HTML entities to Unicode const entitiesConverted = decodeHtmlEntities(processedContent); if (entitiesConverted !== processedContent) { changes.push('HTML entities converted to Unicode'); processedContent = entitiesConverted; } // 2. Convert WordPress caption shortcodes const captionsConverted = convertWordPressCaption(processedContent); if (captionsConverted !== processedContent) { changes.push('WordPress caption shortcodes converted to Markdown'); processedContent = captionsConverted; } // 3. Convert WordPress image URLs to Astro asset paths const imageUrlPattern = /https?:\/\/[^\/]+\/wp-content\/uploads\/\d{4}\/\d{2}\/[^\s)]+\.(jpg|jpeg|png|gif|svg|webp)/gi; const imageUrlsConverted = processedContent.replace(imageUrlPattern, (match) => { changes.push(`WordPress image URL converted: ${match}`); return convertWordPressUrlToAssetPath(match); }); if (imageUrlsConverted !== processedContent) { processedContent = imageUrlsConverted; } // 4. Fix any broken image references by matching with existing files const imageRefPattern = /!\[([^\]]*)\]\(([^)]+)\)/g; processedContent = processedContent.replace(imageRefPattern, (match, alt, src) => { // If it's already a proper asset path, leave it alone if (src.startsWith('~/assets/images/')) { return match; } // Try to find the image in our map const filename = path.basename(src).toLowerCase(); const nameWithoutExt = filename.replace(/\.[^.]+$/, ''); if (imageMap.has(filename)) { changes.push(`Image reference fixed: ${src} -> ${imageMap.get(filename)}`); return `![${alt}](${imageMap.get(filename)})`; } else if (imageMap.has(nameWithoutExt)) { changes.push(`Image reference fixed: ${src} -> ${imageMap.get(nameWithoutExt)}`); return `![${alt}](${imageMap.get(nameWithoutExt)})`; } return match; }); // Only write file if there were changes if (processedContent !== originalContent) { await writeFile(filePath, processedContent, 'utf-8'); logger.log('info', `Processed file with changes: ${changes.join(', ')}`, filePath); return { processed: true, changes }; } else { logger.log('info', 'No changes needed', filePath); return { processed: false, changes: [] }; } } catch (error) { logger.log('error', `Error processing file: ${error.message}`, filePath); return { processed: false, changes: [], error: error.message }; } } // Function to create backup of posts async function createBackup(postsDir, backupDir) { try { // Create backup directory if it doesn't exist if (!fs.existsSync(backupDir)) { fs.mkdirSync(backupDir, { recursive: true }); } const files = await readdir(postsDir); let backupCount = 0; for (const file of files) { if (file.endsWith('.md') || file.endsWith('.mdx')) { const sourcePath = path.join(postsDir, file); const backupPath = path.join(backupDir, file); const content = await readFile(sourcePath, 'utf-8'); await writeFile(backupPath, content, 'utf-8'); backupCount++; } } return backupCount; } catch (error) { throw new Error(`Failed to create backup: ${error.message}`); } } // Main function async function main() { const logger = new Logger(config.logFile); logger.log('info', 'Starting blog post processing...'); try { // Create backup logger.log('info', 'Creating backup of blog posts...'); const backupCount = await createBackup(config.postsDirectory, config.backupDirectory); logger.log('info', `Backup created: ${backupCount} files backed up to ${config.backupDirectory}`); // Scan for existing images logger.log('info', 'Scanning for existing images...'); const imageMap = await findExistingImages(config.assetsDirectory); logger.log('info', `Found ${imageMap.size} images in assets directory`); // Process all markdown files logger.log('info', 'Processing markdown files...'); const files = await readdir(config.postsDirectory); const markdownFiles = files.filter(file => file.endsWith('.md') || file.endsWith('.mdx')); let processedCount = 0; let changedCount = 0; let errorCount = 0; const allChanges = []; for (const file of markdownFiles) { const filePath = path.join(config.postsDirectory, file); const result = await processMarkdownFile(filePath, logger, imageMap); processedCount++; if (result.error) { errorCount++; } else if (result.processed) { changedCount++; allChanges.push({ file, changes: result.changes }); } } // Summary logger.log('info', `Processing complete:`); logger.log('info', ` - Total files processed: ${processedCount}`); logger.log('info', ` - Files with changes: ${changedCount}`); logger.log('info', ` - Files with errors: ${errorCount}`); logger.log('info', ` - Files unchanged: ${processedCount - changedCount - errorCount}`); if (allChanges.length > 0) { logger.log('info', '\nSummary of changes:'); allChanges.forEach(({ file, changes }) => { logger.log('info', ` ${file}:`); changes.forEach(change => { logger.log('info', ` - ${change}`); }); }); } // Save logs await logger.saveLogs(); console.log('\n✅ Blog post processing completed successfully!'); console.log(`📄 Check ${config.logFile} for detailed logs`); console.log(`💾 Backup created in ${config.backupDirectory}`); } catch (error) { logger.log('error', `Fatal error: ${error.message}`); await logger.saveLogs(); console.error('❌ Processing failed:', error.message); process.exit(1); } } // Run the script if (require.main === module) { main().catch(console.error); } module.exports = { decodeHtmlEntities, convertWordPressCaption, convertWordPressUrlToAssetPath, findExistingImages, processMarkdownFile, createBackup };