kira-arts 1.3.0 โ 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -10
- package/dist/index.cjs +117 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -2
- package/dist/index.d.ts +167 -2
- package/dist/index.js +117 -30
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
- package/README.npm.md +0 -58
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ A TypeScript library for generating Discord-style visual cards โ profiles, wel
|
|
|
4
4
|
|
|
5
5
|
**๐ Full documentation, live examples, and a Playground: [documentation](https://kira-arts.chocofactory.dev/)**
|
|
6
6
|
|
|
7
|
+
> ๐ง **Heads up:** the documentation is currently hosted at `kira-arts.chocofactory.dev`. This will move to a dedicated, more formal custom domain/branding in an upcoming release โ the link above will be kept up to date when that happens.
|
|
8
|
+
|
|
7
9
|
[](https://www.npmjs.com/package/kira-arts)
|
|
8
10
|
[](https://www.npmjs.com/package/kira-arts)
|
|
9
11
|
[](https://packagephobia.com/result?p=kira-arts)
|
|
@@ -15,7 +17,7 @@ A TypeScript library for generating Discord-style visual cards โ profiles, wel
|
|
|
15
17
|
[](https://github.com/worddevs/kira-arts/actions/workflows/release.yml)
|
|
16
18
|
[](https://github.com/worddevs/kira-arts/stargazers)
|
|
17
19
|
[](https://github.com/worddevs/kira-arts/network/members)
|
|
18
|
-
[](https://github.com/worddevs/kira-arts/graphs/contributors)
|
|
19
21
|
[](https://github.com/worddevs/kira-arts/commits/main)
|
|
20
22
|
[](https://github.com/worddevs/kira-arts/issues)
|
|
21
23
|
[](./CONTRIBUTING.md)
|
|
@@ -25,7 +27,7 @@ A TypeScript library for generating Discord-style visual cards โ profiles, wel
|
|
|
25
27
|
|
|
26
28
|
- ๐ผ๏ธ Profile, Welcome/Leave, Level Up, Achievement, Leaderboard, Ship (compatibility), and Now Playing cards
|
|
27
29
|
- ๐ต Now Playing card ships with adapters for moonlink.js, Lavalink-based clients, discord-player, and distube
|
|
28
|
-
- ๐จ 8 built-in themes, Nitro/role-color aware borders
|
|
30
|
+
- ๐จ 8 built-in themes, Nitro/role-color aware borders, and up to 4-color custom gradients
|
|
29
31
|
- ๐งพ Output as `png`, `jpeg`, or `webp`, ready to use as a discord.js `AttachmentBuilder`
|
|
30
32
|
|
|
31
33
|
## ๐ฆ Installation
|
|
@@ -47,24 +49,71 @@ import { setClient, profileImage, toAttachment } from "kira-arts";
|
|
|
47
49
|
|
|
48
50
|
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
|
49
51
|
|
|
50
|
-
client.once("
|
|
52
|
+
client.once("clientReady", () => {
|
|
51
53
|
setClient(client); // ๐ required before generating any card
|
|
52
54
|
});
|
|
53
55
|
|
|
54
|
-
client.
|
|
56
|
+
client.on("interactionCreate", async (interaction) => {
|
|
57
|
+
if (interaction.isChatInputCommand()) return;
|
|
58
|
+
|
|
59
|
+
if (interaction.commandName === "card") {
|
|
60
|
+
const buffer = await profileImage(interaction.user.id, {
|
|
61
|
+
guildId: interaction.guild?.id,
|
|
62
|
+
useRoleColor: true,
|
|
63
|
+
presenceStatus: interaction.member?.presence?.status,
|
|
64
|
+
customBadges: extraBadges.length ? extraBadges : undefined,
|
|
65
|
+
badgesFrame: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await interaction.reply({ files: [toAttachment(buffer, "profile", "png")] });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
await interaction.reply({ files: [toAttachment(buffer, "profile", "png")] });
|
|
72
|
+
client.login(process.env.TOKEN);
|
|
58
73
|
```
|
|
59
74
|
|
|
60
75
|
Every other card, the music adapters, theming, caching, error handling, and output options are documented with live examples at **[documentation](https://kira-arts.chocofactory.dev/)**.
|
|
61
76
|
|
|
77
|
+
## ๐ Cards at a glance
|
|
78
|
+
|
|
79
|
+
| Card | Function | What it's for |
|
|
80
|
+
| --------------- | --------------------------------- | ------------------------------------------- |
|
|
81
|
+
| Profile | `profileImage()` | Avatar, badges, nameplate, server tag, rank |
|
|
82
|
+
| Welcome / Leave | `welcomeImage()` / `leaveImage()` | Member join/leave events |
|
|
83
|
+
| Level Up | `levelUpImage()` | XP progress bar on level-up |
|
|
84
|
+
| Achievement | `achievementImage()` | Unlockable achievements with rarity tiers |
|
|
85
|
+
| Leaderboard | `leaderboardImage()` | Server ranking table |
|
|
86
|
+
| Ship | `shipImage()` | Compatibility between two users |
|
|
87
|
+
| Now Playing | `nowPlayingImage()` | Music player card with source detection |
|
|
88
|
+
|
|
89
|
+
## ๐ค Contributors
|
|
90
|
+
|
|
91
|
+
Kira-Arts is developed and maintained by the WordDevs community.
|
|
92
|
+
|
|
93
|
+
<a href="https://github.com/worddevs/kira-arts/graphs/contributors">
|
|
94
|
+
<img src="https://contrib.rocks/image?repo=worddevs/kira-arts" alt="Contributors" />
|
|
95
|
+
</a>
|
|
96
|
+
|
|
62
97
|
## ๐ License
|
|
63
98
|
|
|
64
|
-
Apache-2.0
|
|
99
|
+
Kira-Arts is released under the **Apache-2.0 License**.
|
|
100
|
+
|
|
101
|
+
Copyright ยฉ [worddevs](https://github.com/worddevs)
|
|
65
102
|
|
|
66
103
|
## ๐ Links
|
|
67
104
|
|
|
68
|
-
- Documentation
|
|
69
|
-
-
|
|
70
|
-
-
|
|
105
|
+
- ๐ **Documentation:** https://kira-arts.chocofactory.dev/
|
|
106
|
+
- ๐ฆ **NPM:** https://www.npmjs.com/package/kira-arts
|
|
107
|
+
- ๐ป **Repository:** https://github.com/worddevs/kira-arts
|
|
108
|
+
- ๐ **Issues:** https://github.com/worddevs/kira-arts/issues
|
|
109
|
+
- ๐ **Contributing:** https://github.com/worddevs/kira-arts/blob/main/CONTRIBUTING.md
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
<p align="center">
|
|
114
|
+
Made with ๐ by <a href="https://github.com/worddevs">WordDevs</a>
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<p align="center">
|
|
118
|
+
<sub>Built with TypeScript and powered by <code>@napi-rs/canvas</code>.</sub>
|
|
119
|
+
</p>
|
package/dist/index.cjs
CHANGED
|
@@ -441,7 +441,6 @@ function normalizeDisplayText(value) {
|
|
|
441
441
|
if (typeof value !== "string") return value;
|
|
442
442
|
try {
|
|
443
443
|
const normalized = value.normalize("NFKC");
|
|
444
|
-
if (normalized !== value) return normalized;
|
|
445
444
|
return applyMathScriptFallback(normalized);
|
|
446
445
|
} catch {
|
|
447
446
|
return value;
|
|
@@ -647,7 +646,7 @@ function getDateOrString(dateInput, createdTimestamp, localDateType = "en") {
|
|
|
647
646
|
return (/* @__PURE__ */ new Date(+createdTimestamp)).toLocaleDateString(localDateType, dateOptions);
|
|
648
647
|
}
|
|
649
648
|
}
|
|
650
|
-
function formatGiveawayEndText(endsAt, from = Date.now(), localDateType = "
|
|
649
|
+
function formatGiveawayEndText(endsAt, from = Date.now(), localDateType = "en") {
|
|
651
650
|
const target = endsAt instanceof Date ? endsAt : new Date(endsAt);
|
|
652
651
|
const diffMs = target.getTime() - from;
|
|
653
652
|
const isEs = localDateType.startsWith("es");
|
|
@@ -718,6 +717,14 @@ function formatDuration(ms) {
|
|
|
718
717
|
}
|
|
719
718
|
return `${minutes}:${seconds}`;
|
|
720
719
|
}
|
|
720
|
+
function normalizeDisplayText2(text) {
|
|
721
|
+
if (!text) return text;
|
|
722
|
+
try {
|
|
723
|
+
return text.normalize("NFKC");
|
|
724
|
+
} catch {
|
|
725
|
+
return text;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
721
728
|
|
|
722
729
|
// Public/ProfileImage.files.json
|
|
723
730
|
var ProfileImage_files_default = {
|
|
@@ -905,7 +912,17 @@ async function genAvatarFrame(data, options) {
|
|
|
905
912
|
const ctx = canvas.getContext("2d");
|
|
906
913
|
const frameUrl = data?.decoration?.avatarFrame;
|
|
907
914
|
const avatarFrame = await (0, import_canvas6.loadImage)(frameUrl);
|
|
908
|
-
|
|
915
|
+
if (options?.squareAvatar) {
|
|
916
|
+
const roundValue = 30 * (269 / 225);
|
|
917
|
+
ctx.save();
|
|
918
|
+
ctx.beginPath();
|
|
919
|
+
ctx.roundRect(25, 18, 269, 269, [roundValue]);
|
|
920
|
+
ctx.clip();
|
|
921
|
+
ctx.drawImage(avatarFrame, 25, 18, 269, 269);
|
|
922
|
+
ctx.restore();
|
|
923
|
+
} else {
|
|
924
|
+
ctx.drawImage(avatarFrame, 25, 18, 269, 269);
|
|
925
|
+
}
|
|
909
926
|
if (options?.presenceStatus) {
|
|
910
927
|
canvas = await cutAvatarStatus(canvas, options);
|
|
911
928
|
}
|
|
@@ -1879,6 +1896,36 @@ function applyTextEffect(ctx, effect, glowColor) {
|
|
|
1879
1896
|
|
|
1880
1897
|
// Src/Utils/MemberEeventCard/pills.ts
|
|
1881
1898
|
var import_canvas14 = require("@napi-rs/canvas");
|
|
1899
|
+
var JOIN_DOT_COLOR = "#3ba55d";
|
|
1900
|
+
var LEAVE_DOT_COLOR = "#ed4245";
|
|
1901
|
+
function drawEventKindPill(ctx, width, kind, label) {
|
|
1902
|
+
const dotColor = kind === "welcome" ? JOIN_DOT_COLOR : LEAVE_DOT_COLOR;
|
|
1903
|
+
const dotSize = 7;
|
|
1904
|
+
const paddingX = 12;
|
|
1905
|
+
const pillHeight = 26;
|
|
1906
|
+
const gap = 8;
|
|
1907
|
+
ctx.font = withFallback("12px Helvetica Bold");
|
|
1908
|
+
const textWidth = ctx.measureText(label).width;
|
|
1909
|
+
const pillWidth = paddingX * 2 + dotSize + gap + textWidth;
|
|
1910
|
+
const pillX = width / 2 - pillWidth / 2;
|
|
1911
|
+
const pillY = 16;
|
|
1912
|
+
ctx.save();
|
|
1913
|
+
ctx.beginPath();
|
|
1914
|
+
ctx.roundRect(pillX, pillY, pillWidth, pillHeight, [pillHeight / 2]);
|
|
1915
|
+
ctx.fillStyle = "rgba(10, 10, 14, 0.55)";
|
|
1916
|
+
ctx.fill();
|
|
1917
|
+
ctx.restore();
|
|
1918
|
+
const dotCx = pillX + paddingX + dotSize / 2;
|
|
1919
|
+
const dotCy = pillY + pillHeight / 2;
|
|
1920
|
+
ctx.beginPath();
|
|
1921
|
+
ctx.arc(dotCx, dotCy, dotSize / 2, 0, Math.PI * 2);
|
|
1922
|
+
ctx.fillStyle = dotColor;
|
|
1923
|
+
ctx.fill();
|
|
1924
|
+
ctx.textAlign = "left";
|
|
1925
|
+
ctx.fillStyle = "#ffffff";
|
|
1926
|
+
ctx.font = withFallback("12px Helvetica Bold");
|
|
1927
|
+
ctx.fillText(label, pillX + paddingX + dotSize + gap, pillY + pillHeight / 2 + 4);
|
|
1928
|
+
}
|
|
1882
1929
|
async function drawServerTagPill(ctx, serverTag) {
|
|
1883
1930
|
const text = truncateText(serverTag.text, 12);
|
|
1884
1931
|
const iconSize = serverTag.badgeURL ? 18 : 0;
|
|
@@ -1952,19 +1999,35 @@ async function drawMemberEventCard(ctx, width, height, avatarUrl, username, layo
|
|
|
1952
1999
|
if (bannerImage) {
|
|
1953
2000
|
drawCoverImage(ctx, bannerImage, 0, 0, width, height);
|
|
1954
2001
|
const overlay = ctx.createLinearGradient(0, 0, 0, height);
|
|
1955
|
-
overlay.addColorStop(0, "rgba(8, 9, 12, 0.
|
|
1956
|
-
overlay.addColorStop(0.3, "rgba(8, 9, 12, 0.
|
|
1957
|
-
overlay.addColorStop(0.55, "rgba(13, 15, 20, 0.
|
|
1958
|
-
overlay.addColorStop(1, "rgba(13, 15, 20, 0.
|
|
2002
|
+
overlay.addColorStop(0, "rgba(8, 9, 12, 0.15)");
|
|
2003
|
+
overlay.addColorStop(0.3, "rgba(8, 9, 12, 0.35)");
|
|
2004
|
+
overlay.addColorStop(0.55, "rgba(13, 15, 20, 0.6)");
|
|
2005
|
+
overlay.addColorStop(1, "rgba(13, 15, 20, 0.92)");
|
|
1959
2006
|
ctx.fillStyle = overlay;
|
|
1960
2007
|
ctx.fillRect(0, 0, width, height);
|
|
1961
2008
|
} else {
|
|
1962
|
-
const grd = ctx.createLinearGradient(0, 0, width, BANNER_HEIGHT);
|
|
1963
|
-
grd.addColorStop(0, hexToRgba(layout.accentColor, 0.
|
|
1964
|
-
grd.addColorStop(
|
|
2009
|
+
const grd = ctx.createLinearGradient(0, 0, width, BANNER_HEIGHT * 1.4);
|
|
2010
|
+
grd.addColorStop(0, hexToRgba(layout.accentColor, 0.95));
|
|
2011
|
+
grd.addColorStop(0.55, hexToRgba(layout.accentColor, 0.55));
|
|
2012
|
+
grd.addColorStop(1, hexToRgba(layout.accentColor, 0.18));
|
|
1965
2013
|
ctx.fillStyle = grd;
|
|
1966
2014
|
ctx.fillRect(0, 0, width, BANNER_HEIGHT);
|
|
1967
2015
|
}
|
|
2016
|
+
const spotlight = ctx.createRadialGradient(
|
|
2017
|
+
width / 2,
|
|
2018
|
+
BANNER_HEIGHT,
|
|
2019
|
+
0,
|
|
2020
|
+
width / 2,
|
|
2021
|
+
BANNER_HEIGHT,
|
|
2022
|
+
BANNER_HEIGHT * 1.15
|
|
2023
|
+
);
|
|
2024
|
+
spotlight.addColorStop(0, hexToRgba(layout.accentColor, 0.35));
|
|
2025
|
+
spotlight.addColorStop(1, hexToRgba(layout.accentColor, 0));
|
|
2026
|
+
ctx.fillStyle = spotlight;
|
|
2027
|
+
ctx.fillRect(0, 0, width, height);
|
|
2028
|
+
if (layout.kindLabel) {
|
|
2029
|
+
drawEventKindPill(ctx, width, layout.kind, layout.kindLabel);
|
|
2030
|
+
}
|
|
1968
2031
|
if (layout.serverTag) {
|
|
1969
2032
|
await drawServerTagPill(ctx, layout.serverTag);
|
|
1970
2033
|
}
|
|
@@ -2020,6 +2083,13 @@ async function drawMemberEventCard(ctx, width, height, avatarUrl, username, layo
|
|
|
2020
2083
|
ctx.font = withFallback(`${Math.round(30 * fontScale)}px Helvetica Bold`);
|
|
2021
2084
|
ctx.fillStyle = layout.usernameColor ? parseHex(layout.usernameColor) : "#FFFFFF";
|
|
2022
2085
|
ctx.fillText(truncateText(username, 24), width / 2, cursorY);
|
|
2086
|
+
applyTextEffect(ctx, "none", glowColor);
|
|
2087
|
+
const dividerY = cursorY + 14 * fontScale;
|
|
2088
|
+
const dividerWidth = 36 * fontScale;
|
|
2089
|
+
ctx.beginPath();
|
|
2090
|
+
ctx.roundRect(width / 2 - dividerWidth / 2, dividerY, dividerWidth, 3, [2]);
|
|
2091
|
+
ctx.fillStyle = hexToRgba(layout.accentColor, 0.9);
|
|
2092
|
+
ctx.fill();
|
|
2023
2093
|
if (layout.message) {
|
|
2024
2094
|
cursorY += 32 * fontScale;
|
|
2025
2095
|
applyTextEffect(ctx, layout.messageEffect, glowColor);
|
|
@@ -2028,11 +2098,11 @@ async function drawMemberEventCard(ctx, width, height, avatarUrl, username, layo
|
|
|
2028
2098
|
ctx.fillText(truncateText(layout.message, 64), width / 2, cursorY);
|
|
2029
2099
|
}
|
|
2030
2100
|
applyTextEffect(ctx, "none", glowColor);
|
|
2031
|
-
if (layout.memberCount != null) {
|
|
2101
|
+
if (layout.memberCount != null && layout.memberCountText) {
|
|
2032
2102
|
cursorY += 30 * fontScale;
|
|
2033
2103
|
ctx.font = withFallback(`bold ${Math.round(16 * fontScale)}px Helvetica Bold`);
|
|
2034
2104
|
ctx.fillStyle = hexToRgba(layout.accentColor, 1);
|
|
2035
|
-
ctx.fillText(
|
|
2105
|
+
ctx.fillText(layout.memberCountText, width / 2, cursorY);
|
|
2036
2106
|
}
|
|
2037
2107
|
if (layout.dateText) {
|
|
2038
2108
|
cursorY += 26 * fontScale;
|
|
@@ -2059,7 +2129,16 @@ var DEFAULT_ACCENT2 = "#2DD4BF";
|
|
|
2059
2129
|
function formatMessage(template, username, guildName, memberCount) {
|
|
2060
2130
|
return template.replace(/\{username\}/g, username).replace(/\{user\}/g, username).replace(/\{server\}/g, guildName).replace(/\{memberCount\}/g, memberCount != null ? String(memberCount) : "");
|
|
2061
2131
|
}
|
|
2062
|
-
|
|
2132
|
+
function formatMemberCount(memberCount, localDateType) {
|
|
2133
|
+
const isEs = localDateType.startsWith("es");
|
|
2134
|
+
return isEs ? `Member #${memberCount}` : `Member #${memberCount}`;
|
|
2135
|
+
}
|
|
2136
|
+
function formatKindLabel(kind, localDateType) {
|
|
2137
|
+
const isEs = localDateType.startsWith("es");
|
|
2138
|
+
if (kind === "welcome") return isEs ? "NEW MEMBER" : "NEW MEMBER";
|
|
2139
|
+
return isEs ? "MEMBER LEFT" : "MEMBER LEFT";
|
|
2140
|
+
}
|
|
2141
|
+
async function renderMemberEventCard(userId, guildName, defaultMessage, options = {}, kind = "welcome") {
|
|
2063
2142
|
if (!userId || typeof userId !== "string") {
|
|
2064
2143
|
throw new KiraError("A valid userId is required", "VALIDATION" /* Validation */);
|
|
2065
2144
|
}
|
|
@@ -2067,7 +2146,7 @@ async function renderMemberEventCard(userId, guildName, defaultMessage, options
|
|
|
2067
2146
|
throw new KiraError("A valid guildName is required", "VALIDATION" /* Validation */);
|
|
2068
2147
|
}
|
|
2069
2148
|
const data = await fetchUserData(userId, options.guildId, options.bypassCache);
|
|
2070
|
-
const username = data.basicInfo.globalName || data.basicInfo.username;
|
|
2149
|
+
const username = normalizeDisplayText2(data.basicInfo.globalName || data.basicInfo.username);
|
|
2071
2150
|
const avatarUrl = data.assets.avatarURL ?? data.assets.defaultAvatarURL;
|
|
2072
2151
|
const palette = getThemePalette(options.theme);
|
|
2073
2152
|
const accentColor = options.accentColor ? parseHex(options.accentColor) : palette?.accentColor ? parseHex(palette.accentColor) : DEFAULT_ACCENT2;
|
|
@@ -2080,16 +2159,23 @@ async function renderMemberEventCard(userId, guildName, defaultMessage, options
|
|
|
2080
2159
|
roleColor: data.decoration.roleColor,
|
|
2081
2160
|
fallback: palette?.borderColor ?? [accentColor]
|
|
2082
2161
|
});
|
|
2162
|
+
const localDateType = options.localDateType ?? "es";
|
|
2083
2163
|
const rawMessage = options.message ?? defaultMessage;
|
|
2084
|
-
const message =
|
|
2085
|
-
|
|
2164
|
+
const message = normalizeDisplayText2(
|
|
2165
|
+
formatMessage(rawMessage, username, normalizeDisplayText2(guildName), options.memberCount)
|
|
2166
|
+
);
|
|
2167
|
+
const dateText = options.showDate ? getDateOrString(options.date, Date.now(), localDateType) : void 0;
|
|
2168
|
+
const memberCountText = options.memberCount != null ? formatMemberCount(options.memberCount, localDateType) : void 0;
|
|
2086
2169
|
const fontScale = options.fontScale && options.fontScale > 0 ? options.fontScale : 1;
|
|
2087
2170
|
const avatarFrameUrl = options.showAvatarDecoration ? data.decoration.avatarFrame : null;
|
|
2088
2171
|
const serverTag = options.showServerTag ? data.decoration.serverTag : null;
|
|
2089
2172
|
const badges = options.showBadges ? data.assets.badges : void 0;
|
|
2090
2173
|
const layout = {
|
|
2174
|
+
kind,
|
|
2175
|
+
kindLabel: formatKindLabel(kind, localDateType),
|
|
2091
2176
|
message,
|
|
2092
2177
|
memberCount: options.memberCount,
|
|
2178
|
+
memberCountText,
|
|
2093
2179
|
dateText,
|
|
2094
2180
|
accentColor,
|
|
2095
2181
|
borderColors,
|
|
@@ -2116,15 +2202,15 @@ async function renderMemberEventCard(userId, guildName, defaultMessage, options
|
|
|
2116
2202
|
}
|
|
2117
2203
|
|
|
2118
2204
|
// Src/Cards/welcomeImage.ts
|
|
2119
|
-
var DEFAULT_WELCOME_MESSAGE = "
|
|
2205
|
+
var DEFAULT_WELCOME_MESSAGE = "Welcome to {server}!";
|
|
2120
2206
|
async function welcomeImage(userId, guildName, options = {}) {
|
|
2121
|
-
return renderMemberEventCard(userId, guildName, DEFAULT_WELCOME_MESSAGE, options);
|
|
2207
|
+
return renderMemberEventCard(userId, guildName, DEFAULT_WELCOME_MESSAGE, options, "welcome");
|
|
2122
2208
|
}
|
|
2123
2209
|
|
|
2124
2210
|
// Src/Cards/leaveImage.ts
|
|
2125
|
-
var DEFAULT_LEAVE_MESSAGE = "{username} has left {server}
|
|
2211
|
+
var DEFAULT_LEAVE_MESSAGE = "{username} has left {server}";
|
|
2126
2212
|
async function leaveImage(userId, guildName, options = {}) {
|
|
2127
|
-
return renderMemberEventCard(userId, guildName, DEFAULT_LEAVE_MESSAGE, options);
|
|
2213
|
+
return renderMemberEventCard(userId, guildName, DEFAULT_LEAVE_MESSAGE, options, "leave");
|
|
2128
2214
|
}
|
|
2129
2215
|
|
|
2130
2216
|
// Src/Utils/LevelUpCard/constants.ts
|
|
@@ -2541,7 +2627,7 @@ var RARITY_COLORS = {
|
|
|
2541
2627
|
};
|
|
2542
2628
|
var RARITY_LABELS = {
|
|
2543
2629
|
common: "Common",
|
|
2544
|
-
rare: "
|
|
2630
|
+
rare: "Rare",
|
|
2545
2631
|
epic: "Epic",
|
|
2546
2632
|
legendary: "Legendary"
|
|
2547
2633
|
};
|
|
@@ -3730,7 +3816,7 @@ function drawConfetti(ctx, width, height, colors) {
|
|
|
3730
3816
|
ctx.restore();
|
|
3731
3817
|
}
|
|
3732
3818
|
function drawStatusPill(ctx, x, y, active, color) {
|
|
3733
|
-
const text = active ? "
|
|
3819
|
+
const text = active ? "ACTIVE" : "ENDED";
|
|
3734
3820
|
ctx.save();
|
|
3735
3821
|
ctx.font = withFallback("bold 12px Helvetica Bold");
|
|
3736
3822
|
const textWidth = ctx.measureText(text).width;
|
|
@@ -3825,7 +3911,7 @@ async function drawGiveawayCard(ctx, width, height, layout) {
|
|
|
3825
3911
|
ctx.textAlign = "left";
|
|
3826
3912
|
ctx.textBaseline = "middle";
|
|
3827
3913
|
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
3828
|
-
ctx.fillText("
|
|
3914
|
+
ctx.fillText("GIVEAWAY", PADDING2 + 32, cursorY + 12);
|
|
3829
3915
|
drawStatusPill(ctx, width - PADDING2, cursorY, layout.status !== "ended", statusColor);
|
|
3830
3916
|
cursorY += 56;
|
|
3831
3917
|
ctx.textAlign = "left";
|
|
@@ -3861,13 +3947,13 @@ async function drawGiveawayCard(ctx, width, height, layout) {
|
|
|
3861
3947
|
const midGap = 26;
|
|
3862
3948
|
const trophyGap = 12;
|
|
3863
3949
|
const maxRowWidth = width - PADDING2 * 2;
|
|
3864
|
-
const winnersLabel = `${layout.winnersCount} ${layout.winnersCount === 1 ? "
|
|
3950
|
+
const winnersLabel = `${layout.winnersCount} ${layout.winnersCount === 1 ? "winner" : "winners"}`;
|
|
3865
3951
|
ctx.font = withFallback("15px Helvetica");
|
|
3866
3952
|
const winnersLabelWidth = ctx.measureText(winnersLabel).width;
|
|
3867
3953
|
const fixedWidth = HOST_AVATAR_SIZE + avatarGap + midGap + trophySize + trophyGap + winnersLabelWidth;
|
|
3868
3954
|
const hostMaxWidth = layout.hostName ? Math.max(60, maxRowWidth - fixedWidth) : 0;
|
|
3869
3955
|
ctx.font = withFallback("15px Helvetica");
|
|
3870
|
-
const hostText = layout.hostName ? truncateTextToWidth(ctx, `
|
|
3956
|
+
const hostText = layout.hostName ? truncateTextToWidth(ctx, `Hosted by ${layout.hostName}`, hostMaxWidth) : "";
|
|
3871
3957
|
const hostTextWidth = layout.hostName ? ctx.measureText(hostText).width : 0;
|
|
3872
3958
|
const rowWidth = layout.hostName ? HOST_AVATAR_SIZE + avatarGap + hostTextWidth + midGap + trophySize + trophyGap + winnersLabelWidth : trophySize + trophyGap + winnersLabelWidth;
|
|
3873
3959
|
let rowX = width / 2 - rowWidth / 2;
|
|
@@ -3901,7 +3987,7 @@ async function drawGiveawayCard(ctx, width, height, layout) {
|
|
|
3901
3987
|
const infoColor = "rgba(255, 255, 255, 0.5)";
|
|
3902
3988
|
ctx.font = withFallback("13px Helvetica");
|
|
3903
3989
|
const dateTextWidth = ctx.measureText(layout.dateText).width;
|
|
3904
|
-
const participantsText = layout.participantsCount !== void 0 ? `${layout.participantsCount} ${layout.participantsCount === 1 ? "
|
|
3990
|
+
const participantsText = layout.participantsCount !== void 0 ? `${layout.participantsCount} ${layout.participantsCount === 1 ? "participant" : "participants"}` : void 0;
|
|
3905
3991
|
const separator = " \u2022 ";
|
|
3906
3992
|
const separatorWidth = participantsText ? ctx.measureText(separator).width : 0;
|
|
3907
3993
|
const participantsTextWidth = participantsText ? ctx.measureText(participantsText).width : 0;
|
|
@@ -3922,7 +4008,7 @@ async function drawGiveawayCard(ctx, width, height, layout) {
|
|
|
3922
4008
|
}
|
|
3923
4009
|
if (hasWinners) {
|
|
3924
4010
|
const shown = winners.slice(0, MAX_WINNERS_SHOWN);
|
|
3925
|
-
const remaining =
|
|
4011
|
+
const remaining = layout.winnersCount - shown.length;
|
|
3926
4012
|
const slotWidth = 92;
|
|
3927
4013
|
const rowWidth2 = shown.length * slotWidth;
|
|
3928
4014
|
const startX = width / 2 - rowWidth2 / 2 + slotWidth / 2;
|
|
@@ -3938,7 +4024,7 @@ async function drawGiveawayCard(ctx, width, height, layout) {
|
|
|
3938
4024
|
if (remaining > 0) {
|
|
3939
4025
|
ctx.font = withFallback("13px Helvetica");
|
|
3940
4026
|
ctx.fillStyle = "rgba(255, 255, 255, 0.55)";
|
|
3941
|
-
ctx.fillText(`+${remaining}
|
|
4027
|
+
ctx.fillText(`+${remaining} more`, width / 2, height - PADDING2 + 4);
|
|
3942
4028
|
}
|
|
3943
4029
|
}
|
|
3944
4030
|
ctx.restore();
|
|
@@ -3965,6 +4051,7 @@ async function giveawayImage(prize, options = {}) {
|
|
|
3965
4051
|
const hostData = options.hostId ? await fetchUserData(options.hostId, options.guildId, bypassCache) : void 0;
|
|
3966
4052
|
const hostName = options.hostName ?? hostData?.basicInfo.globalName ?? hostData?.basicInfo.username;
|
|
3967
4053
|
const hostAvatarUrl = options.hostAvatar ?? hostData?.assets.avatarURL ?? hostData?.assets.defaultAvatarURL;
|
|
4054
|
+
const totalWinnerIds = options.winnerIds?.length ?? 0;
|
|
3968
4055
|
const winnerIds = options.winnerIds?.slice(0, MAX_WINNERS_SHOWN) ?? [];
|
|
3969
4056
|
const winnerDataList = await Promise.all(
|
|
3970
4057
|
winnerIds.map((winnerId) => fetchUserData(winnerId, options.guildId, bypassCache))
|
|
@@ -3973,7 +4060,7 @@ async function giveawayImage(prize, options = {}) {
|
|
|
3973
4060
|
username: data.basicInfo.globalName || data.basicInfo.username,
|
|
3974
4061
|
avatarUrl: data.assets.avatarURL ?? data.assets.defaultAvatarURL
|
|
3975
4062
|
}));
|
|
3976
|
-
const winnersCount = options.winnersCount ?? (
|
|
4063
|
+
const winnersCount = options.winnersCount ?? (totalWinnerIds > 0 ? totalWinnerIds : 1);
|
|
3977
4064
|
const palette = getThemePalette(options.theme);
|
|
3978
4065
|
const accentColor = options.accentColor ? parseHex(options.accentColor) : palette?.accentColor ?? DEFAULT_ACCENT6;
|
|
3979
4066
|
const borderColors = resolveCardColors({
|
|
@@ -3985,7 +4072,7 @@ async function giveawayImage(prize, options = {}) {
|
|
|
3985
4072
|
roleColor: hostData?.decoration.roleColor,
|
|
3986
4073
|
fallback: palette?.borderColor ?? [accentColor]
|
|
3987
4074
|
});
|
|
3988
|
-
const dateText = status === "ended" ? options.endedText ?? "
|
|
4075
|
+
const dateText = status === "ended" ? options.endedText ?? "Giveaway ended" : options.endsAt ? formatGiveawayEndText(options.endsAt, Date.now(), options.localDateType ?? "en") : void 0;
|
|
3989
4076
|
const layout = {
|
|
3990
4077
|
prize,
|
|
3991
4078
|
description: options.description,
|