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 CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "hrvclan",
3
- "version": "1.0.9",
4
- "description": "Generate leaderboard card images",
5
- "main": "src/index.js",
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 Clans Public NPM Package (CommonJS)
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. Mandatory Requirements
13
+ # 🚀 1. Requirements
8
14
 
9
15
  - Node.js >= 18
10
- - CommonJS and not use ESModules
11
- - Windows / Linux / VPS have run it
16
+ - CommonJS only
17
+ - Windows / Linux / VPS supported
12
18
 
13
- Check Node Version:
19
+ Check version:
14
20
 
15
21
  ```bash
16
22
  node -v
17
23
  ```
18
- ## 2. How to use
19
- ### Step 1: Install Module
20
- ```
24
+
25
+ ---
26
+
27
+ # 📦 2. Installation
28
+
29
+ ```bash
21
30
  npm install hrvclan
22
31
  ```
23
- ### Step 2: Import Package
24
- ```
32
+
33
+ ---
34
+
35
+ # 📥 3. Import Package
36
+
37
+ ```js
25
38
  const {
26
39
  getLeaderboardCardUrl,
27
40
  fetchLeaderboardCardImage,
28
- saveLeaderboardImage
29
- } = require('hrvclan');
30
- ```
31
- ### Step 3: Test It Out
41
+ saveLeaderboardImage,
42
+ ship
43
+ } = require("hrvclan");
32
44
  ```
33
- const fs = require("fs");
34
45
 
35
- // Import
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
- name: "example_jack",
46
- score: 9999,
47
- avatar: "https://i.pravatar.cc/150?img=1"
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
- console.log("📸 Rendering the image...");
69
+ // 1️⃣ Get preview URL
70
+ const url = getLeaderboardCardUrl(entries);
71
+ console.log("Preview:", url);
74
72
 
75
- // Method 2: Save Directly
76
- await saveLeaderboardImage(entries, "./leaderboard.png");
73
+ // 2️⃣ Save directly
74
+ await saveLeaderboardImage(entries, "./leaderboard.png");
77
75
 
78
- console.log("✅ Saved Files leaderboard.png");
76
+ // 3️⃣ Get Buffer
77
+ const buffer = await fetchLeaderboardCardImage(entries, {
78
+ width: 1080,
79
+ height: 1920,
80
+ fullPage: false
81
+ });
79
82
 
80
- // Method 3: Get Buffer Image (like method 2)
81
- const buffer = await fetchLeaderboardCardImage(entries, {
82
- width: 1080,
83
- height: 1920,
84
- fullPage: false
85
- });
83
+ fs.writeFileSync("leaderboard-buffer.png", buffer);
84
+ }
86
85
 
87
- fs.writeFileSync("leaderboard-buffer.png", buffer);
86
+ run();
87
+ ```
88
+
89
+ ---
88
90
 
89
- console.log("✅ Saved files leaderboard-buffer.png");
91
+ # ❤️ 5. Ship Usage
90
92
 
91
- } catch (err) {
92
- console.error("❌ Error:", err);
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
@@ -0,0 +1,7 @@
1
+ const ship = require("./ship");
2
+ const leaderboard = require("./leaderboard");
3
+
4
+ module.exports = {
5
+ ship,
6
+ leaderboard
7
+ };
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