diff --git a/README.md b/README.md index 6e78a54..20cf7fd 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,42 @@ npm install songcard # or yarn add songcard ``` +
+ +## Usage + +| Option | Type | Description | +|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| imageBg | String | Image that will be display to the songcard.

Example: https://images-ext-1.discordapp.net/external/uw_-bWFyeXnWb11wGThe2CAbTYdrxzFqMJ2trxDIYVE/https/i.scdn.co/image/ab67616d0000b2738ad8f5243d6534e03b656c8b?width=468&height=468
File format: PNG/JPEG | +| imageText | String | Text that will be display to the songcard. | +| trackStream | Boolean | Whether to set the trackDuration and trackTotalDuration to `LIVE`

Example: if trackStream is `true` the trackDuration and totalTrackDuration will show as `LIVE` else it will show number. | +| trackDuration | Integer | Show current duration of the songs. If no value provide it will show `0:00`. | +| trackTotalDuration | Integer | Show the songs duration. | + +
+ +# Themes + +## 1. Classic + + +![](https://cdn.discordapp.com/attachments/959777491818528788/1215117638498258994/card.png?ex=65fb957c&is=65e9207c&hm=c9ac7d6611677d0279395a0da55a34efd6b29addd245577b4a0d47cbbabbc6cd&) + +### Example -## Example ```js -const createCard = require("songcard"); // Import +const { classicCard } = require("songcard"); const { AttachmentBuilder } = require("discord.js"); client.on("interactionCreate", async (message) => { - const cardImage = await createCard( - imageBg = "https://images-ext-1.discordapp.net/external/uw_-bWFyeXnWb11wGThe2CAbTYdrxzFqMJ2trxDIYVE/https/i.scdn.co/image/ab67616d0000b2738ad8f5243d6534e03b656c8b?width=468&height=468", - imageText = "Die For You (with Ariana Grande) - Remix", - trackStream = false, - trackDuration = 220000, - trackTotalDuration = 233000, - ); + const cardImage = await classicCard({ + imageBg: "https://images-ext-1.discordapp.net/external/uw_-bWFyeXnWb11wGThe2CAbTYdrxzFqMJ2trxDIYVE/https/i.scdn.co/image/ab67616d0000b2738ad8f5243d6534e03b656c8b?width=468&height=468", + imageText: "Die For You (with Ariana Grande) - Remix", + trackStream: false, + trackDuration: 220000, + trackTotalDuration: 233000, + }); const attachment = new AttachmentBuilder(cardImage, { name: "card.png", @@ -45,25 +66,36 @@ client.on("interactionCreate", async (message) => { client.login("token"); ``` +
-## Usage +## 2. Simple -| Option | Type | Description | -|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| imageBg | String | Image that will be display to the songcard.

Example: https://images-ext-1.discordapp.net/external/uw_-bWFyeXnWb11wGThe2CAbTYdrxzFqMJ2trxDIYVE/https/i.scdn.co/image/ab67616d0000b2738ad8f5243d6534e03b656c8b?width=468&height=468
File format: PNG/JPEG | -| imageText | String | Text that will be display to the songcard. | -| trackStream | Boolean | Whether to set the trackDuration and trackTotalDuration to `LIVE`

Example: if trackStream is `true` the trackDuration and totalTrackDuration will show as `LIVE` else it will show number. | -| trackDuration | Integer | Show current duration of the songs. If no value provide it will show `0:00`. | -| trackTotalDuration | Integer | Show the songs duration. | +![](https://media.discordapp.net/attachments/959777491818528788/1215115258000183396/card.png?ex=65fb9345&is=65e91e45&hm=6b67d2ccbe049a6d6d859607f3d948635ea7f10bb6e5e35316c4e02697ee52be&=&format=webp&quality=lossless&width=300&height=300) -
-
+### Example -![](https://cdn.discordapp.com/attachments/897715616155328542/1146301148706390036/image.png) +```js +const { simpleCard } = require("songcard"); +const { AttachmentBuilder } = require("discord.js"); -
+client.on("interactionCreate", async (message) => { + + const cardImage = await simpleCard({ + imageBg: + "https://i.scdn.co/image/ab67616d0000b27328862817fc34472677afb214", + imageText: "Yesterday", + }); -![](https://cdn.discordapp.com/attachments/959777491818528788/1146131591131832330/card.png) + const attachment = new AttachmentBuilder(cardImage, { + name: "card.png", + }); + interaction.channel.send({ + files: [attachment], + }); +}); + +client.login("token"); +``` diff --git a/src/index.js b/src/index.js index 9f4e108..ce56164 100644 --- a/src/index.js +++ b/src/index.js @@ -1,197 +1,4 @@ -const { createCanvas, loadImage } = require("canvas"); -const Jimp = require("jimp"); +const { classicCard } = require("./themes/classicCard"); +const { simpleCard } = require("./themes/simpleCard"); -async function createCard( - imageBg, - imageText, - trackStream, - trackDuration, - trackTotalDuration -) { - const prettyMilliseconds = (await import("pretty-ms")).default; - const canvasWidth = 1200; - const canvasHeight = 400; - const canvas = createCanvas(canvasWidth, canvasHeight); - const ctx = canvas.getContext("2d"); - - const imageToAdd = await loadImage(imageBg); - const imageToAdds = await Jimp.read(imageBg); - - const sampleColor = imageToAdds.getPixelColor(0, 0); - const { r, g, b } = Jimp.intToRGBA(sampleColor); - - const brightnessFactor = 0.7; - - const adjustedR = Math.round(r * brightnessFactor); - const adjustedG = Math.round(g * brightnessFactor); - const adjustedB = Math.round(b * brightnessFactor); - - const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); - gradient.addColorStop(0, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); - gradient.addColorStop(1, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); - - ctx.fillStyle = gradient; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - const totalTrackDuration = trackTotalDuration; - const currentTrackDuration = trackDuration; - - const progressWidth = 700; - const progressHeight = 8; - const progressX = 420; - const progressY = 280; - const borderRadius1 = 10; - - const progressPercentage = (currentTrackDuration / totalTrackDuration) * 100; - - const gradients = ctx.createLinearGradient( - progressX, - 0, - progressX + progressWidth, - 0 - ); - - gradients.addColorStop(0, "white"); - - gradients.addColorStop(progressPercentage / 100, "white"); - gradients.addColorStop(progressPercentage / 100, "gray"); - gradients.addColorStop(1, "gray"); - - ctx.fillStyle = gradients; - - ctx.roundRect( - progressX, - progressY, - progressWidth, - progressHeight, - borderRadius1 - ); - ctx.fill(); - - const dotX = progressX + (progressWidth * progressPercentage) / 100; - const dotY = progressY + progressHeight / 2; - const dotRadius = 8; - - ctx.fillStyle = "white"; - ctx.beginPath(); - ctx.arc(dotX, dotY, dotRadius, 0, Math.PI * 2); - ctx.closePath(); - ctx.fill(); - - let duration; - - if (!trackDuration) { - duration = "0:00"; - } - if (trackDuration) { - duration = prettyMilliseconds(trackDuration, { - colonNotation: true, - secondsDecimalDigits: 0, - }); - } - - let totalDuration; - - if (!trackTotalDuration) { - totalDuration = "0:00"; - } - if (trackTotalDuration) { - totalDuration = prettyMilliseconds(trackTotalDuration, { - colonNotation: true, - secondsDecimalDigits: 0, - }); - } - - ctx.fillStyle = "#fff"; - ctx.font = "30px Arial"; - const text1X = 420; - const text1Y = 330; - ctx.fillText(trackStream ? `LIVE` : duration, text1X, text1Y); - - ctx.fillStyle = "#fff"; - ctx.font = "30px Arial"; - const text2X = 1060; - const text2Y = 330; - ctx.fillText(trackStream ? `LIVE` : totalDuration, text2X, text2Y); - - const imageSize = Math.min(canvasHeight - 80, canvasWidth - 80); - const imageX = 40; - const imageY = 40; - const borderRadius = 25; - - ctx.filter = "blur(5px)"; - ctx.drawImage( - canvas, - 0, - 0, - canvasWidth, - canvasHeight, - 0, - 0, - canvasWidth, - canvasHeight - ); - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(imageX + borderRadius, imageY); - ctx.lineTo(imageX + imageSize - borderRadius, imageY); - ctx.quadraticCurveTo( - imageX + imageSize, - imageY, - imageX + imageSize, - imageY + borderRadius - ); - ctx.lineTo(imageX + imageSize, imageY + imageSize - borderRadius); - ctx.quadraticCurveTo( - imageX + imageSize, - imageY + imageSize, - imageX + imageSize - borderRadius, - imageY + imageSize - ); - ctx.lineTo(imageX + borderRadius, imageY + imageSize); - ctx.quadraticCurveTo( - imageX, - imageY + imageSize, - imageX, - imageY + imageSize - borderRadius - ); - ctx.lineTo(imageX, imageY + borderRadius); - ctx.quadraticCurveTo(imageX, imageY, imageX + borderRadius, imageY); - ctx.closePath(); - ctx.clip(); - ctx.drawImage(imageToAdd, imageX, imageY, imageSize, imageSize); - ctx.restore(); - - const textX = imageX + imageSize + 60; - const textY = imageY - -60; - - const maxWidth = 540; - const text = imageText; - - const textWidth = ctx.measureText(text).width; - - if (textWidth > maxWidth) { - const ellipsisWidth = ctx.measureText("...").width; - - const availableWidth = maxWidth - ellipsisWidth; - - let truncatedText = text; - while (ctx.measureText(truncatedText).width > availableWidth) { - truncatedText = truncatedText.slice(0, -1); - } - - truncatedText += "..."; - - ctx.fillStyle = "#fff"; - ctx.font = "40px Arial"; - ctx.fillText(truncatedText, textX, textY); - } else { - ctx.fillStyle = "#fff"; - ctx.font = "40px Arial"; - ctx.fillText(text, textX, textY); - } - return canvas.toBuffer(); -} - -module.exports = createCard; +module.exports = { classicCard, simpleCard } \ No newline at end of file diff --git a/src/themes/classicCard.js b/src/themes/classicCard.js new file mode 100644 index 0000000..6726c6e --- /dev/null +++ b/src/themes/classicCard.js @@ -0,0 +1,197 @@ +const { createCanvas, loadImage } = require("canvas"); +const Jimp = require("jimp"); + +async function classicCard({ + imageBg, + imageText, + trackStream, + trackDuration, + trackTotalDuration, +}) { + const prettyMilliseconds = (await import("pretty-ms")).default; + const canvasWidth = 1200; + const canvasHeight = 400; + const canvas = createCanvas(canvasWidth, canvasHeight); + const ctx = canvas.getContext("2d"); + + const imageToAdd = await loadImage(imageBg); + const imageToAdds = await Jimp.read(imageBg); + + const sampleColor = imageToAdds.getPixelColor(0, 0); + const { r, g, b } = Jimp.intToRGBA(sampleColor); + + const brightnessFactor = 0.7; + + const adjustedR = Math.round(r * brightnessFactor); + const adjustedG = Math.round(g * brightnessFactor); + const adjustedB = Math.round(b * brightnessFactor); + + const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); + gradient.addColorStop(0, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); + gradient.addColorStop(1, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const totalTrackDuration = trackTotalDuration; + const currentTrackDuration = trackDuration; + + const progressWidth = 700; + const progressHeight = 8; + const progressX = 420; + const progressY = 280; + const borderRadius1 = 10; + + const progressPercentage = (currentTrackDuration / totalTrackDuration) * 100; + + const gradients = ctx.createLinearGradient( + progressX, + 0, + progressX + progressWidth, + 0 + ); + + gradients.addColorStop(0, "white"); + + gradients.addColorStop(progressPercentage / 100, "white"); + gradients.addColorStop(progressPercentage / 100, "gray"); + gradients.addColorStop(1, "gray"); + + ctx.fillStyle = gradients; + + ctx.roundRect( + progressX, + progressY, + progressWidth, + progressHeight, + borderRadius1 + ); + ctx.fill(); + + const dotX = progressX + (progressWidth * progressPercentage) / 100; + const dotY = progressY + progressHeight / 2; + const dotRadius = 8; + + ctx.fillStyle = "white"; + ctx.beginPath(); + ctx.arc(dotX, dotY, dotRadius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + + let duration; + + if (!trackDuration) { + duration = "0:00"; + } + if (trackDuration) { + duration = prettyMilliseconds(trackDuration, { + colonNotation: true, + secondsDecimalDigits: 0, + }); + } + + let totalDuration; + + if (!trackTotalDuration) { + totalDuration = "0:00"; + } + if (trackTotalDuration) { + totalDuration = prettyMilliseconds(trackTotalDuration, { + colonNotation: true, + secondsDecimalDigits: 0, + }); + } + + ctx.fillStyle = "#fff"; + ctx.font = "30px Arial"; + const text1X = 420; + const text1Y = 330; + ctx.fillText(trackStream ? `LIVE` : duration, text1X, text1Y); + + ctx.fillStyle = "#fff"; + ctx.font = "30px Arial"; + const text2X = 1060; + const text2Y = 330; + ctx.fillText(trackStream ? `LIVE` : totalDuration, text2X, text2Y); + + const imageSize = Math.min(canvasHeight - 80, canvasWidth - 80); + const imageX = 40; + const imageY = 40; + const borderRadius = 25; + + ctx.filter = "blur(5px)"; + ctx.drawImage( + canvas, + 0, + 0, + canvasWidth, + canvasHeight, + 0, + 0, + canvasWidth, + canvasHeight + ); + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(imageX + borderRadius, imageY); + ctx.lineTo(imageX + imageSize - borderRadius, imageY); + ctx.quadraticCurveTo( + imageX + imageSize, + imageY, + imageX + imageSize, + imageY + borderRadius + ); + ctx.lineTo(imageX + imageSize, imageY + imageSize - borderRadius); + ctx.quadraticCurveTo( + imageX + imageSize, + imageY + imageSize, + imageX + imageSize - borderRadius, + imageY + imageSize + ); + ctx.lineTo(imageX + borderRadius, imageY + imageSize); + ctx.quadraticCurveTo( + imageX, + imageY + imageSize, + imageX, + imageY + imageSize - borderRadius + ); + ctx.lineTo(imageX, imageY + borderRadius); + ctx.quadraticCurveTo(imageX, imageY, imageX + borderRadius, imageY); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(imageToAdd, imageX, imageY, imageSize, imageSize); + ctx.restore(); + + const textX = imageX + imageSize + 60; + const textY = imageY - -60; + + const maxWidth = 540; + const text = imageText; + + const textWidth = ctx.measureText(text).width; + + if (textWidth > maxWidth) { + const ellipsisWidth = ctx.measureText("...").width; + + const availableWidth = maxWidth - ellipsisWidth; + + let truncatedText = text; + while (ctx.measureText(truncatedText).width > availableWidth) { + truncatedText = truncatedText.slice(0, -1); + } + + truncatedText += "..."; + + ctx.fillStyle = "#fff"; + ctx.font = "40px Arial"; + ctx.fillText(truncatedText, textX, textY); + } else { + ctx.fillStyle = "#fff"; + ctx.font = "40px Arial"; + ctx.fillText(text, textX, textY); + } + return canvas.toBuffer(); +} + +module.exports = { classicCard }; diff --git a/src/themes/simpleCard.js b/src/themes/simpleCard.js new file mode 100644 index 0000000..8466d4f --- /dev/null +++ b/src/themes/simpleCard.js @@ -0,0 +1,111 @@ +const { createCanvas, loadImage } = require("canvas"); +const Jimp = require("jimp"); + +async function simpleCard({ imageBg, imageText }) { + const canvasWidth = 600; + const canvasHeight = 600; + const canvas = createCanvas(canvasWidth, canvasHeight); + const ctx = canvas.getContext("2d"); + + const imageToAdd = await loadImage(imageBg); + const imageToAdds = await Jimp.read(imageBg); + + const sampleColor = imageToAdds.getPixelColor(0, 0); + const { r, g, b } = Jimp.intToRGBA(sampleColor); + + const brightnessFactor = 0.7; + + const adjustedR = Math.round(r * brightnessFactor); + const adjustedG = Math.round(g * brightnessFactor); + const adjustedB = Math.round(b * brightnessFactor); + + const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); + gradient.addColorStop(0, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); + gradient.addColorStop(1, `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const imageSize = Math.min(canvasHeight - 200, canvasWidth - 200); + const imageX = (canvasWidth - imageSize) / 2; + const imageY = (canvasHeight - imageSize - 40) / 2; + const borderRadius = 25; + + ctx.filter = "blur(5px)"; + ctx.drawImage( + canvas, + 0, + 0, + canvasWidth, + canvasHeight, + 0, + 0, + canvasWidth, + canvasHeight + ); + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(imageX + borderRadius, imageY); + ctx.lineTo(imageX + imageSize - borderRadius, imageY); + ctx.quadraticCurveTo( + imageX + imageSize, + imageY, + imageX + imageSize, + imageY + borderRadius + ); + ctx.lineTo(imageX + imageSize, imageY + imageSize - borderRadius); + ctx.quadraticCurveTo( + imageX + imageSize, + imageY + imageSize, + imageX + imageSize - borderRadius, + imageY + imageSize + ); + ctx.lineTo(imageX + borderRadius, imageY + imageSize); + ctx.quadraticCurveTo( + imageX, + imageY + imageSize, + imageX, + imageY + imageSize - borderRadius + ); + ctx.lineTo(imageX, imageY + borderRadius); + ctx.quadraticCurveTo(imageX, imageY, imageX + borderRadius, imageY); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(imageToAdd, imageX, imageY, imageSize, imageSize); + ctx.restore(); + + const textX = canvasWidth / 2; + const textY = imageY + imageSize + 60; + + const maxWidth = 100; + const text = imageText; + + const textWidth = ctx.measureText(text).width; + + if (textWidth > maxWidth) { + const ellipsisWidth = ctx.measureText("...").width; + + const availableWidth = maxWidth - ellipsisWidth; + + let truncatedText = text; + while (ctx.measureText(truncatedText).width > availableWidth) { + truncatedText = truncatedText.slice(0, -1); + } + + truncatedText += "..."; + + ctx.fillStyle = "#fff"; + ctx.font = "35px Arial"; + ctx.textAlign = "center"; + ctx.fillText(truncatedText, textX, textY); + } else { + ctx.fillStyle = "#fff"; + ctx.font = "35px Arial"; + ctx.textAlign = "center"; + ctx.fillText(text, textX, textY); + } + return canvas.toBuffer(); +} + +module.exports = { simpleCard };