UNPKG

canvafy

Version:

Make configurable canvas easily with Canvafy

333 lines (283 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const { createCanvas, loadImage, GlobalFonts } = require('@napi-rs/canvas'); const path = require('path'); const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); /** * @typedef {object} Instagram * @see {Instagram} * @example const InstagramCard = await new canvafy.Instagram() * @type {Class} */ module.exports = class Instagram { constructor(options) { this.font = { name: options?.font?.name ?? "Chirp", path: options?.font?.path }; this.avatar = "https://cdn.discordapp.com/avatars/928259219038302258/299ebac2bc13f5a8f44d2dd1f0c9f856.png?size=1024"; this.verified = true; this.theme = "dark"; this.user = { username: "fivesobes" }; this.like = { likeCount: 5, likeText: "likes" }; this.image = "https://cdn.discordapp.com/avatars/928259219038302258/299ebac2bc13f5a8f44d2dd1f0c9f856.png?size=1024"; this.postDate = (Date.now() - 5000); this.isSaved = true; this.isLiked = true; this.story = true; } /** * .setStory * @param {boolean} story Set Story * @returns {Instagram} * @example setStory(true) * @example setStory(false) * @throws {Error} Story must be a boolean * @returns {Instagram} */ setStory(story) { if (typeof story !== "boolean") throw new Error("Story must be a boolean type"); this.story = story; return this; } /** * .setAvatar * @param {string} image Set User Avatar URL * @returns {Instagram} * @example setAvatar("https://someone-image.png") */ setAvatar(image) { this.avatar = image; return this; }; /** * .setUser * @param {object} user {username: "string"} * @returns {Instagram} * @example setUser({username: "fivesobes"}) */ setUser({ username }) { var newUsername = username.replace(/[^a-zA-Z0-9_]/g, '').trim(); this.user = { username: newUsername }; return this; }; /** * .setPostDate * @param {number} date Set Post Date * @returns {Instagram} * @example setPostDate(Date.now() - 15000) * @throws {Error} Date must be a number * @returns {Instagram} * @example setPostDate(Date.now() - 100000) */ setPostDate(date){ if(typeof date !== "number") throw new Error("Date must be a number type"); this.postDate = date; return this; }; /** * .setSaved * @param {boolean} saved Set Saved * @returns {Instagram} * @example setSaved(true) * @example setSaved(false) * @throws {Error} Saved must be a boolean * @returns {Instagram} */ setSaved(saved){ if(typeof saved !== "boolean") throw new Error("Saved must be a boolean type"); this.isSaved = saved; return this; } /** * .setLiked * @param {boolean} liked Set Liked * @returns {Instagram} * @example setLiked(true) * @example setLiked(false) * @throws {Error} Liked must be a boolean * @returns {Instagram} */ setLiked(liked){ if(typeof liked !== "boolean") throw new Error("Liked must be a boolean type"); this.isLiked = liked; return this; } /** * .setLikeCount * @param {number} count Set Like Count * @returns {Instagram} * @example setLikeCount(5) * @throws {Error} Count must be a number type */ setLike({ count, likeText }){ if(typeof count !== "number") throw new Error("Count must be a number type"); if(typeof likeText !== "string") throw new Error("Like text must be a string type"); this.like = { likeCount:count, likeText:likeText } return this; }; /** * .setPostImage * @param {string} image Set Post Image * @returns {Instagram} * @example setPostImage("https://someone-image.png") */ setPostImage(image){ this.image = image; return this; }; /** * .setTheme * @param {string} theme Set Theme * @returns {Instagram} * @example setTheme("dark") * @example setTheme("light") * @throws {Error} Invalid theme */ setTheme(theme) { if (!["dark", "light"].some(e => e == theme)) throw new Error("Invalid theme"); this.theme = theme; return this; }; /** * .setVerified * @param {boolean} verified Set Verified * @returns {Instagram} * @example setVerified(true) * @example setVerified(false) * @throws {Error} Verified must be a boolean */ setVerified(verified) { if (typeof verified !== "boolean") throw new Error("Verified must be a boolean"); this.verified = verified; return this; }; async build() { if (this.font.path) GlobalFonts.registerFromPath(this.font.path, this.font.name); var canvas = createCanvas(1080, 1450); var ctx = canvas.getContext("2d"); if(this.user.username.trim().length < 1) throw new Error("Username must be at least 1 character long."); ctx.beginPath(); ctx.fillStyle = this.theme == "light" ? "#fff" : "#000"; ctx.fillRect(10, 10, canvas.width - 20, canvas.height - 20) ctx.fillStyle = this.theme == "light" ? "#000" : "#fff"; ctx.textAlign = "left"; ctx.font = "50px Chirp Bold"; ctx.fillText(`${this.user.username.length > 14 ? this.user.username.slice(0,14)+".." : this.user.username}`, 145, 105); ctx.fillStyle = this.theme == "light" ? "#000" : "#fff"; ctx.textAlign = "left"; ctx.font = "45px Chirp Bold"; ctx.fillText(`${formatLikes(this.like.likeCount)} ${this.like.likeText.length > 10 ? this.like.likeText.slice(0,10)+"..": this.like.likeText}`,50, canvas.height - 120); ctx.fillStyle = "#afafaf"; ctx.textAlign = "left"; ctx.font = "30px Chirp Bold"; ctx.fillText(`${timeAgo(this.postDate)}`,50, canvas.height - 65); if (this.verified === true) { var textLengthBlank = { 8: 220, 9: 225, 10: 230, 11: 235, 12: 240, 13: 245, 14: 250, 15: 255, 16: 260, }; ctx.font = "35px Chirp Bold"; var textLength = ctx.measureText(this.user.username.length > 14 ? this.user.username.slice(0,14)+".." : this.user.username).width; ctx.drawImage(await loadImage(`${__dirname}/../assets/images/twitter-verified.png`), (this.user.username.length < 8 ? (textLength + 200) : textLengthBlank[this.user.username.length > 14 ? 16 : this.user.username.length] + textLength), 60, 60, 60); } ctx.fillStyle = "#fff"; ctx.fillRect(10, 170, canvas.width - 20, canvas.height - 480); ctx.drawImage(await loadImage(this.image), 10, 170, canvas.width - 20, canvas.height - 470); try { if(this.story) { ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-story-frame.png`), 7.5, 14, 140, 140); } if(this.isLiked) { ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-liked.png`), 40, canvas.height - 270, 80, 80); } else { ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-like-${this.theme == "light" ? "dark" : "light"}.png`), 40, canvas.height - 270, 80, 80); } ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-comment-${this.theme == "light" ? "dark" : "light"}.png`), 160, canvas.height - 270, 75, 75); ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-share-${this.theme == "light" ? "dark" : "light"}.png`), 280, canvas.height - 270, 80, 80); ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-share-${this.theme == "light" ? "dark" : "light"}.png`), 280, canvas.height - 270, 80, 80); ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-other-${this.theme == "light" ? "dark" : "light"}.png`), canvas.width - 110, 60, 80, 80); if(this.isSaved) { ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-save-${this.theme == "light" ? "dark" : "light"}-filled.png`), canvas.width - 150, canvas.height - 270, 80, 80); } else { ctx.drawImage(await loadImage(`${__dirname}/../assets/images/instagram-save-${this.theme == "light" ? "dark" : "light"}.png`), canvas.width - 150, canvas.height - 270, 80, 80); } } catch (err) { console.log(err); }; ctx.beginPath(); ctx.arc(80, 87, 50, 0, Math.PI * 2); ctx.closePath(); ctx.clip(); try { ctx.drawImage(await loadImage(this.avatar), 93 - 67, 37, 105, 105); } catch { throw new Error("The image given in the argument of the setAvatar method is not valid or you are not connected to the internet."); } return canvas.toBuffer('image/png'); } }; function writeComment(ctx,comment,theme){ comment = comment.length > 2490 ? comment.slice(0, 2490) + "..." : comment; if(!comment.includes(" ")) { comment.length > 57 ? comment = comment.slice(0, 57) + "..." : comment; ctx.fillStyle = theme == "light" ? "#000" : "#fff"; ctx.font = "25px Chirp"; ctx.fillText(comment, 85, 170); return; } var words = comment.split(' '); var line = ''; var lineHeight = 40; var x = 85; var y = 170; for (var i = 0; i < words.length; i++) { var testLine = line + words[i] + ' '; var metrics = ctx.measureText(testLine).width; if (metrics > 800) { ctx.fillStyle = theme == "light" ? "#000" : "#fff"; ctx.font = "25px Chirp"; ctx.fillText(line, x, y); line = words[i] + ' '; y += lineHeight; } else { line = testLine; } } ctx.fillStyle = theme == "light" ? "#000" : "#fff"; ctx.fillText(line, x, y); }; function formatLikes(number) { if (number >= 1000000) { return (number / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; } else if (number >= 1000) { return (number / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; } else { return number.toString(); } } function timeAgo(timestamp) { const now = new Date(); const past = new Date(timestamp); const seconds = Math.floor((now - past) / 1000); const intervals = { year: 31536000, month: 2592000, week: 604800, day: 86400, hour: 3600, minute: 60, second: 1 }; for (const unit in intervals) { const interval = Math.floor(seconds / intervals[unit]); if (interval >= 1) { return `${interval} ${unit}${interval > 1 ? 's' : ''} ago`; } } return 'just now'; }