hrvclan 1.0.9 → 1.0.10
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/package.json +4 -5
- package/readme.md +202 -60
- package/src/main.js +7 -0
- package/src/ship.js +103 -0
- /package/src/{index.js → leaderboard.js} +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hrvclan",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "src/
|
|
3
|
+
"version": "1.0.10",
|
|
4
|
+
"description": "Hrv Clans Public NPM Package",
|
|
5
|
+
"main": "src/main.js",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"leaderboard",
|
|
8
|
+
"ship",
|
|
8
9
|
"card",
|
|
9
10
|
"image-generator",
|
|
10
11
|
"hrv-web",
|
|
@@ -13,8 +14,6 @@
|
|
|
13
14
|
"author": "Nguyễn Thành Tài",
|
|
14
15
|
"license": "MIT",
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"axios": "^1.13.5",
|
|
17
|
-
"node-fetch": "^3.3.2",
|
|
18
17
|
"puppeteer": "^24.37.2"
|
|
19
18
|
},
|
|
20
19
|
"files": [
|
package/readme.md
CHANGED
|
@@ -1,98 +1,240 @@
|
|
|
1
|
-
# 📊 Hrv
|
|
1
|
+
# 📊 Hrv Clan – Public NPM Package (CommonJS)
|
|
2
|
+
|
|
3
|
+
Node.js package render:
|
|
4
|
+
|
|
5
|
+
- 📈 Leaderboard → PNG
|
|
6
|
+
- ❤️ Ship Love Image → PNG
|
|
7
|
+
|
|
8
|
+
> Works only with CommonJS (`require`)
|
|
9
|
+
> Do NOT use ESModules
|
|
2
10
|
|
|
3
|
-
- Action With **CommonJS (`require`)**, not use ESModule.
|
|
4
|
-
- **The Hrv Clan only published the image of leaderboard creation version (Some Features Are Comming Soon)**
|
|
5
11
|
---
|
|
6
12
|
|
|
7
|
-
# 🚀 1.
|
|
13
|
+
# 🚀 1. Requirements
|
|
8
14
|
|
|
9
15
|
- Node.js >= 18
|
|
10
|
-
- CommonJS
|
|
11
|
-
- Windows / Linux / VPS
|
|
16
|
+
- CommonJS only
|
|
17
|
+
- Windows / Linux / VPS supported
|
|
12
18
|
|
|
13
|
-
Check
|
|
19
|
+
Check version:
|
|
14
20
|
|
|
15
21
|
```bash
|
|
16
22
|
node -v
|
|
17
23
|
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# 📦 2. Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
21
30
|
npm install hrvclan
|
|
22
31
|
```
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# 📥 3. Import Package
|
|
36
|
+
|
|
37
|
+
```js
|
|
25
38
|
const {
|
|
26
39
|
getLeaderboardCardUrl,
|
|
27
40
|
fetchLeaderboardCardImage,
|
|
28
|
-
saveLeaderboardImage
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Step 3: Test It Out
|
|
41
|
+
saveLeaderboardImage,
|
|
42
|
+
ship
|
|
43
|
+
} = require("hrvclan");
|
|
32
44
|
```
|
|
33
|
-
const fs = require("fs");
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# 📈 4. Leaderboard Usage
|
|
49
|
+
|
|
50
|
+
## Example
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const fs = require("fs");
|
|
36
54
|
const {
|
|
37
55
|
getLeaderboardCardUrl,
|
|
38
56
|
fetchLeaderboardCardImage,
|
|
39
57
|
saveLeaderboardImage
|
|
40
58
|
} = require("hrvclan");
|
|
41
59
|
|
|
42
|
-
// Example Data
|
|
43
60
|
const entries = [
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "example_nam",
|
|
51
|
-
score: 7200,
|
|
52
|
-
avatar: "https://i.pravatar.cc/150?img=2"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "example_linh",
|
|
56
|
-
score: 5500,
|
|
57
|
-
avatar: "https://i.pravatar.cc/150?img=3"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "example_hoa",
|
|
61
|
-
score: 3000,
|
|
62
|
-
avatar: "https://i.pravatar.cc/150?img=4"
|
|
63
|
-
}
|
|
61
|
+
{ name: "jack", score: 9999, avatar: "https://i.pravatar.cc/150?img=1" },
|
|
62
|
+
{ name: "nam", score: 7200, avatar: "https://i.pravatar.cc/150?img=2" },
|
|
63
|
+
{ name: "linh", score: 5500, avatar: "https://i.pravatar.cc/150?img=3" },
|
|
64
|
+
{ name: "hoa", score: 3000, avatar: "https://i.pravatar.cc/150?img=4" }
|
|
64
65
|
];
|
|
65
66
|
|
|
66
67
|
async function run() {
|
|
67
|
-
try {
|
|
68
|
-
// Method 1: Get Link
|
|
69
|
-
console.log("🔗 URL");
|
|
70
|
-
const url = getLeaderboardCardUrl(entries);
|
|
71
|
-
console.log(url);
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
// 1️⃣ Get preview URL
|
|
70
|
+
const url = getLeaderboardCardUrl(entries);
|
|
71
|
+
console.log("Preview:", url);
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
// 2️⃣ Save directly
|
|
74
|
+
await saveLeaderboardImage(entries, "./leaderboard.png");
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
// 3️⃣ Get Buffer
|
|
77
|
+
const buffer = await fetchLeaderboardCardImage(entries, {
|
|
78
|
+
width: 1080,
|
|
79
|
+
height: 1920,
|
|
80
|
+
fullPage: false
|
|
81
|
+
});
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
width: 1080,
|
|
83
|
-
height: 1920,
|
|
84
|
-
fullPage: false
|
|
85
|
-
});
|
|
83
|
+
fs.writeFileSync("leaderboard-buffer.png", buffer);
|
|
84
|
+
}
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
run();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
# ❤️ 5. Ship Usage
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
Generate love compatibility image.
|
|
94
|
+
|
|
95
|
+
## Example
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const fs = require("fs");
|
|
99
|
+
const { ship } = require("hrvclan");
|
|
100
|
+
|
|
101
|
+
async function run() {
|
|
102
|
+
|
|
103
|
+
const buffer = await ship({
|
|
104
|
+
avt1: "https://i.pravatar.cc/150?img=5",
|
|
105
|
+
avt2: "https://i.pravatar.cc/150?img=6",
|
|
106
|
+
percent: 88 // optional (random if not provided)
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync("ship.png", buffer);
|
|
110
|
+
|
|
111
|
+
console.log("Ship image saved!");
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
run();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# 📚 API Reference
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 📈 Leaderboard
|
|
124
|
+
|
|
125
|
+
### getLeaderboardCardUrl(entries)
|
|
126
|
+
|
|
127
|
+
Returns HTML preview URL.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
string
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### fetchLeaderboardCardImage(entries, options)
|
|
136
|
+
|
|
137
|
+
Returns PNG Buffer.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Promise<Buffer>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Options:
|
|
144
|
+
|
|
145
|
+
| Name | Type |
|
|
146
|
+
|------|------|
|
|
147
|
+
| width | number |
|
|
148
|
+
| height | number |
|
|
149
|
+
| fullPage | boolean |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### saveLeaderboardImage(entries, filePath)
|
|
154
|
+
|
|
155
|
+
Save PNG file directly.
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
Promise<void>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## ❤️ Ship
|
|
164
|
+
|
|
165
|
+
### ship(options)
|
|
166
|
+
|
|
167
|
+
Returns PNG Buffer.
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Promise<Buffer>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Options:
|
|
174
|
+
|
|
175
|
+
| Name | Type | Required |
|
|
176
|
+
|------|------|-------------------|
|
|
177
|
+
| avt1 | string (image URL) | ✅ |
|
|
178
|
+
| avt2 | string (image URL) | ✅ |
|
|
179
|
+
| percent | number (0-100) | ❌ |
|
|
180
|
+
|
|
181
|
+
If percent is not provided → random 0–100.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# 📦 Data Format
|
|
186
|
+
|
|
187
|
+
Leaderboard entry:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
{
|
|
191
|
+
name: string,
|
|
192
|
+
score: number,
|
|
193
|
+
avatar: string
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
# 🛠 Troubleshooting
|
|
200
|
+
|
|
201
|
+
### Cannot use import statement outside module
|
|
202
|
+
|
|
203
|
+
Use:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
require("hrvclan");
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
NOT:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
import ...
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Puppeteer error on VPS
|
|
218
|
+
|
|
219
|
+
Install:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npm install puppeteer --unsafe-perm=true
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
# 🔄 Versioning
|
|
228
|
+
|
|
229
|
+
Increase version before publishing:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm version patch
|
|
233
|
+
npm publish
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
# 👑 Author
|
|
97
239
|
|
|
98
|
-
|
|
240
|
+
Hrv Clan
|
package/src/main.js
ADDED
package/src/ship.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
async function safeLoadImage(url, fallbackPath = null) {
|
|
6
|
+
if (!url || !url.startsWith('http'))
|
|
7
|
+
return fallbackPath ? loadLocal(fallbackPath) : null;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const response = await axios.get(url, {
|
|
11
|
+
responseType: 'arraybuffer',
|
|
12
|
+
timeout: 10000,
|
|
13
|
+
headers: { 'User-Agent': 'Mozilla/5.0' }
|
|
14
|
+
});
|
|
15
|
+
return await loadImage(Buffer.from(response.data));
|
|
16
|
+
} catch {
|
|
17
|
+
return fallbackPath ? loadLocal(fallbackPath) : null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadLocal(p) {
|
|
22
|
+
try {
|
|
23
|
+
return loadImage(path.join(__dirname, p));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getEmojiUrl(emoji) {
|
|
30
|
+
const hex = [...emoji]
|
|
31
|
+
.map(char => char.codePointAt(0).toString(16))
|
|
32
|
+
.join('-');
|
|
33
|
+
return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/${hex}.png`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function generateShipImage(query) {
|
|
37
|
+
const { bg, avt1, avt2, percent: pQuery } = query;
|
|
38
|
+
const percent = parseInt(pQuery) || Math.floor(Math.random() * 101);
|
|
39
|
+
|
|
40
|
+
const canvas = createCanvas(340, 150);
|
|
41
|
+
const ctx = canvas.getContext('2d');
|
|
42
|
+
|
|
43
|
+
let heartEmoji = '❤️';
|
|
44
|
+
let glowColor = '#ff0000';
|
|
45
|
+
if (percent < 20) { heartEmoji = '💔'; glowColor = '#808080'; }
|
|
46
|
+
else if (percent < 50) { heartEmoji = '🧡'; glowColor = '#ffa500'; }
|
|
47
|
+
else if (percent < 80) { heartEmoji = '💖'; glowColor = '#ff69b4'; }
|
|
48
|
+
else { heartEmoji = '❤️🔥'; glowColor = '#ffffff'; }
|
|
49
|
+
|
|
50
|
+
const [imgBg, imgAvt1, imgAvt2, imgHeart] = await Promise.all([
|
|
51
|
+
safeLoadImage(bg),
|
|
52
|
+
safeLoadImage(avt1, 'assets/avatar.png'),
|
|
53
|
+
safeLoadImage(avt2, 'assets/avatar.png'),
|
|
54
|
+
safeLoadImage(getEmojiUrl(heartEmoji))
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
if (imgBg) ctx.drawImage(imgBg, 0, 0, 340, 150);
|
|
58
|
+
else {
|
|
59
|
+
ctx.fillStyle = '#1a1a1a';
|
|
60
|
+
ctx.fillRect(0, 0, 340, 150);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';
|
|
64
|
+
ctx.fillRect(10, 10, 320, 130);
|
|
65
|
+
|
|
66
|
+
const drawAvatar = (img, x) => {
|
|
67
|
+
if (!img) return;
|
|
68
|
+
ctx.save();
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.arc(x, 75, 45, 0, Math.PI * 2);
|
|
71
|
+
ctx.clip();
|
|
72
|
+
ctx.drawImage(img, x - 45, 30, 90, 90);
|
|
73
|
+
ctx.restore();
|
|
74
|
+
ctx.strokeStyle = '#ffffff';
|
|
75
|
+
ctx.lineWidth = 3;
|
|
76
|
+
ctx.stroke();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
drawAvatar(imgAvt1, 70);
|
|
80
|
+
drawAvatar(imgAvt2, 270);
|
|
81
|
+
|
|
82
|
+
if (imgHeart) {
|
|
83
|
+
const size = 95;
|
|
84
|
+
ctx.save();
|
|
85
|
+
ctx.shadowBlur = 30;
|
|
86
|
+
ctx.shadowColor = glowColor;
|
|
87
|
+
ctx.drawImage(imgHeart, 170 - size / 2, 75 - size / 2, size, size);
|
|
88
|
+
ctx.restore();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ctx.font = 'bold 24px sans-serif';
|
|
92
|
+
ctx.textAlign = 'center';
|
|
93
|
+
ctx.textBaseline = 'middle';
|
|
94
|
+
ctx.strokeStyle = 'black';
|
|
95
|
+
ctx.lineWidth = 5;
|
|
96
|
+
ctx.strokeText(`${percent}%`, 170, 80);
|
|
97
|
+
ctx.fillStyle = 'white';
|
|
98
|
+
ctx.fillText(`${percent}%`, 170, 75);
|
|
99
|
+
|
|
100
|
+
return canvas.toBuffer('image/png');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { generateShipImage };
|
|
File without changes
|