meo-forkcy-logger
Version:
A flexible logger and banner printer for CLI bots using colored console output.
392 lines (370 loc) • 11 kB
JavaScript
const colors = require("meo-forkcy-colors");
const socialRows = [
["💬 Telegram (Me)", "https://t.me/MeoMunDep"],
[" 𝕏 Twitter - X", "https://x.com/MeoMeo0009"],
["📺 Discord", "https://discord.gg/5aHuaGdu"],
["📢 Telegram (Channel)", "https://t.me/keoairdropfreene"],
["🎵 TikTok", "https://www.tiktok.com/@meomundep"],
["📸 Instagram", "https://www.instagram.com/meom.undep/"],
["🎥 YouTube", "https://www.youtube.com/@keoairdropfreene"],
];
// Combining characters table (zero-width characters)
const combining = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
];
// Binary search to check if a character is in the combining table
function bisearch(ucs) {
let min = 0;
let max = combining.length - 1;
if (ucs < combining[0][0] || ucs > combining[max][1]) return false;
while (max >= min) {
const mid = Math.floor((min + max) / 2);
if (ucs > combining[mid][1]) min = mid + 1;
else if (ucs < combining[mid][0]) max = mid - 1;
else return true;
}
return false;
}
// Determine the display width of a single Unicode character
function wcwidth(ucs) {
// Handle null character
if (ucs === 0) return 0;
// Handle C0/C1 control characters and DEL
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return -1;
// Handle combining characters
if (bisearch(ucs)) return 0;
// Handle SOFT HYPHEN
if (ucs === 0x00ad) return 1;
// Handle ZERO WIDTH SPACE
if (ucs === 0x200b) return 0;
// Check for East Asian Wide (W) or Full-width (F) characters
if (
(ucs >= 0x1100 && ucs <= 0x115f) || // Hangul Jamo init. consonants
(ucs >= 0x2329 && ucs <= 0x232a) ||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK ... Yi
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) || // CJK Unified Ideographs Extension
(ucs >= 0x30000 && ucs <= 0x3fffd)
) {
return 2;
}
// Emojis and symbols (approximation based on common wide characters)
if (ucs >= 0x1f300 && ucs <= 0x1faff) {
return 2;
}
// All other characters have width 1
return 1;
}
function displayWidth(text) {
let width = 0;
for (const char of [...text]) {
const code = char.codePointAt(0);
const charWidth = wcwidth(code);
if (charWidth < 0) return -1; // Control characters invalidate width
width += charWidth;
}
return width;
}
function padEndDisplayWidth(text, targetWidth) {
const current = displayWidth(text);
if (current < 0) return text; // Handle invalid width
return text + " ".repeat(Math.max(0, targetWidth - current));
}
function drawLine(left, mid1, mid2, mid3, right, w1, w2, w3, w4) {
return (
left +
"─".repeat(w1 + 2) +
mid1 +
"─".repeat(w2 + 2) +
mid2 +
"─".repeat(w3 + 2) +
mid3 +
"─".repeat(w4 + 2) +
right
);
}
// New function to draw the bottom line with special characters
function drawBottomLine(w1, w2, w3, w4) {
return (
"╰" +
"─".repeat(w1 + 2) +
"┴" +
"─".repeat(w2 + 2) +
"┴" +
"─".repeat(w3 + 2) +
"┴" +
"─".repeat(w4 + 2) +
"╯"
);
}
/**
* Print a merged table combining social links and bot configs in a styled banner.
*
* @param {string} botName - The name of the bot.
* @param {Object} [configs={}] - Key-value pairs for configuration display.
* @param {Function} [colorL1=colors.lime] - Color function for left column 1.
* @param {Function} [colorL2=colors.cyan] - Color function for left column 2.
* @param {Function} [colorR1=colors.orange] - Color function for right column 1.
* @param {Function} [colorR2=colors.magenta] - Color function for right column 2.
*/
function printMergedTableWithBanner(
botName,
configs = {},
colorL1 = colors.lime,
colorL2 = colors.cyan,
colorR1 = colors.orange,
colorR2 = colors.magenta
) {
const configRows = Object.entries(configs).map(([k, v]) => {
let val;
if (Array.isArray(v)) val = `[${v.join(", ")}]`;
else if (typeof v === "boolean") val = v ? "true" : "false";
else val = String(v);
return [k, val];
});
// Column widths
const w1 = Math.max(
...socialRows.map(([k]) => displayWidth(k)),
displayWidth("Platform")
);
const w2 = Math.max(
...socialRows.map(([, v]) => displayWidth(v)),
displayWidth("Link")
);
const w3 = Math.max(
...configRows.map(([k]) => displayWidth(k)),
displayWidth("Setting")
);
const w4 = Math.max(
...configRows.map(([, v]) => displayWidth(v)),
displayWidth("Value")
);
const totalWidth = w1 + w2 + w3 + w4 + 3 * 3 + 2;
// Print banner
const title = `MeoMunDep ${botName} Automation Script`;
const pad = Math.floor((totalWidth - displayWidth(title)) / 2);
console.log(colors.green("╭" + "─".repeat(totalWidth) + "╮"));
console.log(
colors.green(
"│" +
" ".repeat(pad) +
colors.pink(title) +
" ".repeat(totalWidth - pad - displayWidth(title)) +
"│"
)
);
// Print table header
console.log(colors.green(drawLine("├", "┬", "┬", "┬", "┤", w1, w2, w3, w4)));
console.log(
colors.green(
`│ ${colors.red.bold(
padEndDisplayWidth("My Social Network", w1)
)} │ ${colors.red.bold(
padEndDisplayWidth("Link", w2)
)} │ ${colors.red.bold(
padEndDisplayWidth("Setting", w3)
)} │ ${colors.red.bold(padEndDisplayWidth("Value", w4))} │`
)
);
console.log(colors.green(drawLine("├", "┼", "┼", "┼", "┤", w1, w2, w3, w4)));
// Part 1: Print rows where both socialRows and configRows may have data
const pairedRows = Math.min(socialRows.length, configRows.length);
for (let i = 0; i < pairedRows; i++) {
const [sKey, sVal] = socialRows[i];
const [cKey, cVal] = configRows[i];
console.log(
colors.green(
`│ ${colorL1(padEndDisplayWidth(sKey, w1))} │ ${colorL2(
padEndDisplayWidth(sVal, w2)
)} │ ${colorR1(padEndDisplayWidth(cKey, w3))} │ ${colorR2(
padEndDisplayWidth(cVal, w4)
)} │`
)
);
}
// Part 2: If there are more socialRows, print the remaining socialRows
if (socialRows.length > pairedRows) {
for (let i = pairedRows; i < socialRows.length; i++) {
const [sKey, sVal] = socialRows[i];
console.log(
colors.green(
`│ ${colorL1(padEndDisplayWidth(sKey, w1))} │ ${colorL2(
padEndDisplayWidth(sVal, w2)
)} │ ${colorR1(padEndDisplayWidth("", w3))} │ ${colorR2(
padEndDisplayWidth("", w4)
)} │`
)
);
}
}
// Part 3: If there are more configRows, print a separator and the remaining configRows
if (configRows.length > pairedRows) {
if (socialRows.length > pairedRows) {
// Add a separator if we printed extra socialRows
console.log(
colors.green(drawLine("├", "┼", "┼", "┼", "┤", w1, w2, w3, w4))
);
}
for (let i = pairedRows; i < configRows.length; i++) {
const [cKey, cVal] = configRows[i];
console.log(
colors.green(
`│ ${colorL1(padEndDisplayWidth("", w1))} │ ${colorL2(
padEndDisplayWidth("", w2)
)} │ ${colorR1(padEndDisplayWidth(cKey, w3))} │ ${colorR2(
padEndDisplayWidth(cVal, w4)
)} │`
)
);
}
}
// Use the new bottom line function with ┴ characters
console.log(colors.green(drawBottomLine(w1, w2, w3, w4)));
}
module.exports = { printMergedTableWithBanner };