← back to browse
mirai command

welcome

No description

0
views
0
likes
0
installs
raw source
const {
    createCanvas,
    loadImage,
    registerFont
} = require('canvas');
const fs = require('fs-extra');
const path = require('path');
const { getTime, drive } = global.utils;

const fontDir = process.cwd() + "/scripts/cmds/assets/font";
const canvasFontDir = process.cwd() + "/scripts/cmds/canvas/fonts";

registerFont(path.join(fontDir, "NotoSans-Bold.ttf"), {
    family: 'NotoSans',
    weight: 'bold'
});

registerFont(path.join(fontDir, "NotoSans-SemiBold.ttf"), {
    family: 'NotoSans',
    weight: '600'
});

registerFont(path.join(fontDir, "NotoSans-Regular.ttf"), {
    family: 'NotoSans',
    weight: 'normal'
});

registerFont(path.join(fontDir, "BeVietnamPro-Bold.ttf"), {
    family: 'BeVietnamPro',
    weight: 'bold'
});

registerFont(path.join(fontDir, "BeVietnamPro-SemiBold.ttf"), {
    family: 'BeVietnamPro',
    weight: '600'
});

registerFont(path.join(fontDir, "BeVietnamPro-Regular.ttf"), {
    family: 'BeVietnamPro',
    weight: 'normal'
});

registerFont(path.join(fontDir, "Kanit-SemiBoldItalic.ttf"), {
    family: 'Kanit',
    weight: '600',
    style: 'italic'
});

registerFont(path.join(canvasFontDir, "Rounded.otf"), {
    family: 'Rounded'
});

async function createWelcomeCanvas(gcImg, img1, img2, userName, userNumber, threadName, potato) {
    const width = 1200;
    const height = 600;
    const canvas = createCanvas(width, height);
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#0a0a0a';
    ctx.fillRect(0, 0, width, height);

    function fitAndSetFont(family, weight, maxSize, minSize, text, maxWidth, style = '') {
        let size = maxSize;
        for (; size >= minSize; size -= 1) {
            ctx.font = `${style ? style + ' ' : ''}${weight} ${size}px ${family}`;
            if (ctx.measureText(text).width <= maxWidth) break;
        }
        ctx.font = `${style ? style + ' ' : ''}${weight} ${Math.max(size, minSize)}px ${family}`;
        return Math.max(size, minSize);
    }
    function drawTextWithStroke(text, x, y, align = 'left') {
        ctx.textAlign = align;
        ctx.strokeStyle = 'rgba(0,0,0,0.65)';
        ctx.lineWidth = 4;
        ctx.strokeText(text, x, y);
        ctx.fillText(text, x, y);
    }

    ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
    ctx.lineWidth = 2;
    for (let i = -height; i < width; i += 60) {
        ctx.beginPath();
        ctx.moveTo(i, 0);
        ctx.lineTo(i + height, height);
        ctx.stroke();
    }
    const lightGradient = ctx.createLinearGradient(0, 0, width, height);
    lightGradient.addColorStop(0, 'rgba(255, 255, 255, 0.02)');
    lightGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.05)');
    lightGradient.addColorStop(1, 'rgba(255, 255, 255, 0.02)');
    ctx.fillStyle = lightGradient;
    ctx.fillRect(0, 0, width, height);
    const squares = [{
        x: 50,
        y: 50,
        size: 80,
        rotation: 15
    },
        {
            x: 1100,
            y: 80,
            size: 60,
            rotation: -20
        },
        {
            x: 150,
            y: 500,
            size: 50,
            rotation: 30
        },
        {
            x: 1050,
            y: 480,
            size: 70,
            rotation: -15
        },
        {
            x: 900,
            y: 30,
            size: 40,
            rotation: 45
        },
        {
            x: 200,
            y: 150,
            size: 35,
            rotation: -30
        },
        {
            x: 400,
            y: 80,
            size: 45,
            rotation: 60
        },
        {
            x: 700,
            y: 520,
            size: 55,
            rotation: -40
        },
        {
            x: 950,
            y: 250,
            size: 38,
            rotation: 25
        },
        {
            x: 300,
            y: 350,
            size: 42,
            rotation: -50
        }];

    squares.forEach(sq => {
        ctx.save();
        ctx.translate(sq.x + sq.size / 2, sq.y + sq.size / 2);
        ctx.rotate((sq.rotation * Math.PI) / 180);

        const sqGradient = ctx.createLinearGradient(-sq.size / 2, -sq.size / 2, sq.size / 2, sq.size / 2);
        sqGradient.addColorStop(0, 'rgba(34, 197, 94, 0.3)');
        sqGradient.addColorStop(1, 'rgba(22, 163, 74, 0.1)');

        ctx.fillStyle = sqGradient;
        ctx.fillRect(-sq.size / 2, -sq.size / 2, sq.size, sq.size);

        ctx.strokeStyle = 'rgba(34, 197, 94, 0.5)';
        ctx.lineWidth = 2;
        ctx.strokeRect(-sq.size / 2, -sq.size / 2, sq.size, sq.size);

        ctx.restore();
    });
    const circles = [{
        x: 250,
        y: 250,
        radius: 30,
        alpha: 0.15
    },
        {
            x: 850,
            y: 150,
            radius: 25,
            alpha: 0.12
        },
        {
            x: 600,
            y: 50,
            radius: 20,
            alpha: 0.1
        },
        {
            x: 100,
            y: 350,
            radius: 35,
            alpha: 0.18
        },
        {
            x: 1000,
            y: 380,
            radius: 28,
            alpha: 0.14
        },
        {
            x: 450,
            y: 480,
            radius: 22,
            alpha: 0.11
        }];

    circles.forEach(circ => {
        ctx.beginPath();
        ctx.arc(circ.x, circ.y, circ.radius, 0, Math.PI * 2);
        const circGradient = ctx.createRadialGradient(circ.x, circ.y, 0, circ.x, circ.y, circ.radius);
        circGradient.addColorStop(0, `rgba(34, 197, 94, ${circ.alpha})`);
        circGradient.addColorStop(1, 'rgba(22, 163, 74, 0)');
        ctx.fillStyle = circGradient;
        ctx.fill();

        ctx.strokeStyle = `rgba(34, 197, 94, ${circ.alpha * 2})`;
        ctx.lineWidth = 1.5;
        ctx.stroke();
    });
    const triangles = [{
        x: 550,
        y: 150,
        size: 40,
        rotation: 0
    },
        {
            x: 180,
            y: 420,
            size: 35,
            rotation: 180
        },
        {
            x: 1080,
            y: 320,
            size: 38,
            rotation: 90
        },
        {
            x: 380,
            y: 200,
            size: 32,
            rotation: -45
        }];

    triangles.forEach(tri => {
        ctx.save();
        ctx.translate(tri.x, tri.y);
        ctx.rotate((tri.rotation * Math.PI) / 180);

        ctx.beginPath();
        ctx.moveTo(0, -tri.size / 2);
        ctx.lineTo(-tri.size / 2, tri.size / 2);
        ctx.lineTo(tri.size / 2, tri.size / 2);
        ctx.closePath();

        const triGradient = ctx.createLinearGradient(-tri.size / 2, 0, tri.size / 2, 0);
        triGradient.addColorStop(0, 'rgba(34, 197, 94, 0.2)');
        triGradient.addColorStop(1, 'rgba(22, 163, 74, 0.1)');
        ctx.fillStyle = triGradient;
        ctx.fill();

        ctx.strokeStyle = 'rgba(34, 197, 94, 0.4)';
        ctx.lineWidth = 2;
        ctx.stroke();

        ctx.restore();
    });

    async function drawCircularImage(imageSrc, x, y, radius, borderColor, borderWidth = 5) {
        try {
            const image = await loadImage(imageSrc);
            ctx.shadowColor = borderColor;
            ctx.shadowBlur = 15;
            ctx.beginPath();
            ctx.arc(x, y, radius + borderWidth, 0, Math.PI * 2);
            ctx.fillStyle = borderColor;
            ctx.fill();
            ctx.shadowBlur = 0;
            ctx.beginPath();
            ctx.arc(x, y, radius + borderWidth, 0, Math.PI * 2);
            ctx.fillStyle = borderColor;
            ctx.fill();
            ctx.save();
            ctx.beginPath();
            ctx.arc(x, y, radius, 0, Math.PI * 2);
            ctx.closePath();
            ctx.clip();
            ctx.drawImage(image, x - radius, y - radius, radius * 2, radius * 2);
            ctx.restore();
        } catch (err) {
            ctx.beginPath();
            ctx.arc(x, y, radius, 0, Math.PI * 2);
            ctx.fillStyle = '#1f1f1f';
            ctx.fill();
        }
    }
    
    await drawCircularImage(img2, width - 120, 100, 55, '#22c55e');
    fitAndSetFont('"NotoSans", "BeVietnamPro", sans-serif', 'bold', 22, 14, 'Added by ' + potato, 320);
    ctx.fillStyle = '#22c55e';
    ctx.textAlign = 'right';
    drawTextWithStroke('Added by ' + potato, width - 190, 105, 'right');
    await drawCircularImage(img1, 120, height - 100, 55, '#16a34a');
    fitAndSetFont('"NotoSans", "BeVietnamPro", sans-serif', 'bold', 28, 16, userName, width - 250);
    ctx.fillStyle = '#ffffff';
    ctx.textAlign = 'left';
    drawTextWithStroke(userName, 190, height - 95, 'left');
    await drawCircularImage(gcImg, width / 2, 200, 90, '#22c55e', 6);
    fitAndSetFont('"NotoSans", "BeVietnamPro", sans-serif', '600', 42, 20, threadName, width * 0.8);
    ctx.fillStyle = '#ffffff';
    ctx.textAlign = 'center';
    drawTextWithStroke(threadName, width / 2, 335, 'center');
    fitAndSetFont('"Kanit", "NotoSans", sans-serif', '600', 56, 28, 'WELCOME', width * 0.9, 'italic');
    const nameGradient = ctx.createLinearGradient(width / 2 - 200, 0, width / 2 + 200, 0);
    nameGradient.addColorStop(0, '#4ade80');
    nameGradient.addColorStop(1, '#22c55e');
    ctx.fillStyle = nameGradient;
    drawTextWithStroke('WELCOME', width / 2, 410, 'center');
    ctx.strokeStyle = 'rgba(34, 197, 94, 0.4)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(width / 2 - 180, 430);
    ctx.lineTo(width / 2 + 180, 430);
    ctx.stroke();
    fitAndSetFont('"NotoSans", "BeVietnamPro", sans-serif', '600', 26, 16, `You are the ${userNumber}th member`, width * 0.9);
    ctx.fillStyle = '#a0a0a0';
    ctx.textAlign = 'center';
    drawTextWithStroke(`You are the ${userNumber}th member`, width / 2, 480, 'center');

    return canvas.createPNGStream();
}

module.exports = {
    config: {
        name: "welcome",
        version: "1.3",
        author: "Neoaz ゐ",//Adapted from @procoder Allou Mohammed
        category: "events"
    },
    
    langs: {
        vi: {
            session1: "sáng",
            session2: "trưa",
            session3: "chiều",
            session4: "tối",
            defaultWelcomeMessage: "Chào mừng {userName} đến với {boxName}"
        },
        en: {
            session1: "morning",
            session2: "noon",
            session3: "afternoon",
            session4: "evening",
            defaultWelcomeMessage: "Welcome {userName} to {boxName}"
        }
    },

    onStart: async ({
        threadsData, event, message, usersData, getLang
    }) => {
        const type = "log:subscribe";
        if (event.logMessageType != type) return;
        
        try {
            await threadsData.refreshInfo(event.threadID);
            const threadsInfo = await threadsData.get(event.threadID);
            if (!threadsInfo.settings.sendWelcomeMessage)
                return;
            const gcImg = threadsInfo.imageSrc;
            const threadName = threadsInfo.threadName;
            const addedList = event.logMessageData.addedParticipants || [];
            const joined = addedList[0]?.userFbId;
            const by = event.author;
            if (!joined) return;
            const img1 = await usersData.getAvatarUrl(joined).catch(() => null);
            const img2 = await usersData.getAvatarUrl(by).catch(() => null);
            const usernumber = threadsInfo.members?.length || 1;
            const userName = event.logMessageData.addedParticipants[0].fullName || (await usersData.getName(joined));
            const authorN = await usersData.getName(by);
            
            const welcomeImage = await createWelcomeCanvas(gcImg, img1, img2, userName, usernumber, threadName, authorN);
            
            const imagePath = path.join(__dirname, '../cmds/', global.utils.randomString(4) + ".png");
            const writeStream = fs.createWriteStream(imagePath);
            welcomeImage.pipe(writeStream);
            
            await new Promise((resolve) => {
                writeStream.on('finish', resolve);
            });

            const hours = getTime("HH");
            const data = threadsInfo.data || {};
            let { welcomeMessage = getLang("defaultWelcomeMessage") } = data;

            const names = await Promise.all(addedList.map(p => usersData.getName(p.userFbId)));
            const nameJoined = names.join(", ");

            const multipleText = addedList.length > 1 ? "you guys" : "you";

            const form = {};
            if (welcomeMessage.includes("{userNameTag}")) {
                form.mentions = [{
                    id: joined,
                    tag: userName
                }];
            }

            welcomeMessage = (welcomeMessage || "")
                .replace(/\{userName\}/g, nameJoined)
                .replace(/\{userNameTag\}/g, userName)
                .replace(/\{threadName\}|\{boxName\}/g, threadName)
                .replace(/\{time\}/g, hours)
                .replace(/\{multiple\}/g, multipleText)
                .replace(/\{session\}/g, hours <= 10
                    ? getLang("session1")
                    : hours <= 12
                        ? getLang("session2")
                        : hours <= 18
                            ? getLang("session3")
                            : getLang("session4")
                );

            form.body = welcomeMessage;

            const attachments = [];
            if (threadsInfo.data?.welcomeAttachment && Array.isArray(threadsInfo.data.welcomeAttachment)) {
                const files = threadsInfo.data.welcomeAttachment;
                const fromDrive = await Promise.allSettled(files.map(file => drive.getFile(file, "stream")));
                for (const r of fromDrive)
                    if (r.status == "fulfilled")
                        attachments.push(r.value);
            }
            attachments.push(fs.createReadStream(imagePath));
            form.attachment = attachments;

            await message.send(form);
            
            fs.unlink(imagePath).catch(() => {});
        } catch (error) {
            console.error("[WELCOME] Error:", error.message);
            console.error(error.stack);
        }
    }
};

View raw file