Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

Commit

Permalink
feat: dyanmic card
Browse files Browse the repository at this point in the history
  • Loading branch information
LewdHuTao committed Jun 7, 2024
1 parent 92b1c97 commit 92c1746
Show file tree
Hide file tree
Showing 22 changed files with 245 additions and 19 deletions.
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@ npm install songcard
# or
yarn add songcard
```
<br>

## Usage

| Option | Type | Description |
|------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| imageBg | String | Image that will be display to the songcard. <br><br> Example: https://images-ext-1.discordapp.net/external/uw_-bWFyeXnWb11wGThe2CAbTYdrxzFqMJ2trxDIYVE/https/i.scdn.co/image/ab67616d0000b2738ad8f5243d6534e03b656c8b?width=468&height=468 <br>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`<br><br>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. |

<br>

Expand All @@ -37,21 +26,29 @@ yarn add songcard
## 1. Classic


![](./src/examples/assets/card1.png)
![](./src/assets/card1.png)

### Example

![](./src/examples/assets/code1.png)
![](./src/assets/code1.png)

<br>

## 2. Simple

![](./src/examples/assets/card2.png)
![](./src/assets/card2.png)

### Example

![](./src/assets/code2.png)

## 3. Dynamic

![](./src/assets/card3.png)

### Example

![](./src/examples/assets/code2.png)
![](./src/assets/code3.png)



4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "songcard",
"version": "1.2.0",
"version": "1.2.1",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
Expand Down
File renamed without changes
File renamed without changes
Binary file added src/assets/card3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
Binary file added src/assets/code3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/soundcloud.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/spotify.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/youtube.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
23 changes: 23 additions & 0 deletions src/example/dynamicCard.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { dynamicCard } = require("songcard");
const { AttachmentBuilder } = require("discord.js");

client.on("interactionCreate", async (interaction) => {
const cardImage = await dynamicCard({
thumbnailURL:
"https://i.scdn.co/image/ab67616d00001e0240d7efd2594a2b6bda60ea18",
songTitle: "What is Love",
songArtist: "TWICE",
streamProvider: "spotify",
trackRequester: "@lewdhutao",
});

const attachment = new AttachmentBuilder(cardImage, {
name: "card.png",
});

interaction.channel.send({
files: [attachment],
});
});

client.login("token");
File renamed without changes.
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { classicCard } = require("./themes/classicCard");
const { simpleCard } = require("./themes/simpleCard");
const { dynamicCard } = require("./themes/dynamicCard")

module.exports = { classicCard, simpleCard };
module.exports = { classicCard, simpleCard, dynamicCard };
File renamed without changes.
27 changes: 27 additions & 0 deletions src/test/dynamicCard.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const fs = require("fs");
const { dynamicCard } = require("../themes/dynamicCard");

async function testdynamicCard() {
const thumbnailURL =
"https://i.scdn.co/image/ab67616d00001e0240d7efd2594a2b6bda60ea18";
const songTitle = "What is Love";
const songArtist = "TWICE";
const streamProvider = "spotify";
const trackRequester = "@lewdhutao";

try {
const buffer = await dynamicCard({
thumbnailURL,
songTitle,
songArtist,
streamProvider,
trackRequester,
});
fs.writeFileSync("dynamicCard.png", buffer);
console.log("Canvas generated successfully.");
} catch (error) {
console.error("Error generating canvas:", error);
}
}

testdynamicCard();
File renamed without changes.
Binary file removed src/tests/classicCard.png
Binary file not shown.
Binary file removed src/tests/simpleCard.png
Binary file not shown.
178 changes: 178 additions & 0 deletions src/themes/dynamicCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const { createCanvas, loadImage, GlobalFonts } = require("@napi-rs/canvas");
const path = require("path");

async function dynamicCard({
thumbnailURL, // required
songTitle, // required
songArtist, // optional
streamProvider, // optional
trackRequester, // optional
}) {
const cardWidth = 800;
const cardHeight = 250;
const backgroundColor = "#2B2D31";

const canvas = createCanvas(cardWidth, cardHeight);
const ctx = canvas.getContext("2d");

const fontPath = path.join(__dirname, "..", "fonts", "ArialUnicodeMS.ttf");
GlobalFonts.registerFromPath(fontPath, "ArialUnicodeMS");

function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === "undefined") {
stroke = true;
}
if (typeof radius === "undefined") {
radius = 5;
}
if (typeof radius === "number") {
radius = { tl: radius, tr: radius, br: radius, bl: radius };
} else {
var defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 };
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(
x + width,
y + height,
x + width - radius.br,
y + height
);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}

ctx.fillStyle = backgroundColor;
roundRect(ctx, 0, 0, cardWidth, cardHeight, 20, true, false);

const thumbnailImage = await loadImage(thumbnailURL);

const padding = 20;
const thumbnailSize = cardHeight - 2 * padding;

ctx.save();
ctx.beginPath();
ctx.moveTo(cardWidth - thumbnailSize - padding + 20, padding);
ctx.arcTo(
cardWidth - padding,
padding,
cardWidth - padding,
padding + 20,
20
);
ctx.arcTo(
cardWidth - padding,
cardHeight - padding,
cardWidth - padding - 20,
cardHeight - padding,
20
);
ctx.arcTo(
cardWidth - thumbnailSize - padding,
cardHeight - padding,
cardWidth - thumbnailSize - padding,
cardHeight - padding - 20,
20
);
ctx.arcTo(
cardWidth - thumbnailSize - padding,
padding,
cardWidth - thumbnailSize - padding + 20,
padding,
20
);
ctx.closePath();
ctx.clip();
ctx.drawImage(
thumbnailImage,
cardWidth - thumbnailSize - padding,
padding,
thumbnailSize,
thumbnailSize
);
ctx.restore();

const streamProviderIcons = {
spotify: path.join(__dirname, "..", "assets", "spotify.png"),
youtube: path.join(__dirname, "..", "assets", "youtube.png"),
soundcloud: path.join(__dirname, "..", "assets", "soundcloud.png"),
};

if (streamProvider && streamProviderIcons[streamProvider.toLowerCase()]) {
const providerIcon = await loadImage(
streamProviderIcons[streamProvider.toLowerCase()]
);
const iconSize = 30;
const iconPadding = 10;
ctx.save();
ctx.beginPath();
ctx.arc(
cardWidth - padding - iconPadding - iconSize / 2,
cardHeight - padding - iconPadding - iconSize / 2,
iconSize / 2,
0,
Math.PI * 2
);
ctx.closePath();
ctx.clip();
ctx.drawImage(
providerIcon,
cardWidth - padding - iconPadding - iconSize,
cardHeight - padding - iconPadding - iconSize,
iconSize,
iconSize
);
ctx.restore();
}

ctx.fillStyle = "white";
ctx.font = "bold 35px 'ArialUnicodeMS'";
ctx.textAlign = "left";
ctx.textBaseline = "top";

const maxWidth = cardWidth - thumbnailSize - padding * 2;
let truncatedTitle = songTitle;

while (ctx.measureText(truncatedTitle).width > maxWidth) {
truncatedTitle = truncatedTitle.slice(0, -1);
}

if (truncatedTitle.length < songTitle.length) {
truncatedTitle = truncatedTitle.slice(0, -3) + "...";
}

ctx.fillText(truncatedTitle, padding + 10, padding + 20);

ctx.fillStyle = "#A79D9D";
ctx.font = "25px 'ArialUnicodeMS'";

ctx.fillText(songArtist, padding + 10, padding + 70);

ctx.font = "20px 'ArialUnicodeMS'";
ctx.fillText(
`Requested by: ${trackRequester}`,
padding + 10,
cardHeight - padding - 20
);

const buffer = canvas.toBuffer("image/png");

return buffer;
}

module.exports = { dynamicCard };

0 comments on commit 92c1746

Please sign in to comment.