hrvclan 1.0.0

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.
Files changed (3) hide show
  1. package/package.json +33 -0
  2. package/readme.md +46 -0
  3. package/src/index.js +104 -0
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "hrvclan",
3
+ "version": "1.0.0",
4
+ "description": "Generate leaderboard card images using hrv-web.netlify.app API",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "keywords": [
8
+ "leaderboard",
9
+ "card",
10
+ "image-generator",
11
+ "hrv-web",
12
+ "api"
13
+ ],
14
+ "author": "Nguyễn Thành Tài",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "axios": "^1.13.5",
18
+ "node-fetch": "^3.3.2",
19
+ "puppeteer": "^24.37.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^25.2.2",
23
+ "typescript": "^5.5.4"
24
+ },
25
+ "files": [
26
+ "src/**/*",
27
+ "README.md"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/nthanhtai08/hrvclan.git"
32
+ }
33
+ }
package/readme.md ADDED
@@ -0,0 +1,46 @@
1
+ # 📊 Leaderboard HTML → PNG Renderer (CommonJS)
2
+
3
+ Module Node.js sử dụng **Puppeteer** để:
4
+
5
+ - Encode dữ liệu leaderboard
6
+ - Gửi lên API HTML
7
+ - Render trang HTML
8
+ - Chụp màn hình thành ảnh PNG
9
+ - Lưu file về máy
10
+
11
+ Hoạt động với **CommonJS (`require`)**, không dùng ESModule.
12
+
13
+ ---
14
+
15
+ # 🚀 1. Yêu cầu
16
+
17
+ - Node.js >= 18
18
+ - Không dùng `"type": "module"` trong `package.json`
19
+ - Windows / Linux / VPS đều chạy được
20
+
21
+ Kiểm tra Node:
22
+
23
+ ```bash
24
+ node -v
25
+ ```
26
+ # 2. How to use
27
+ ```
28
+ ## Bước 1: Cài Module
29
+ ```npm install @nthanhtai08/hrvclan```
30
+ ## Bước 2: Import Package
31
+ ```
32
+ const {
33
+ getLeaderboardCardUrl,
34
+ fetchLeaderboardCardImage,
35
+ saveLeaderboardImage
36
+ } = require('hrvclan');
37
+ ```
38
+ ## Bước 3: Done
39
+ ```
40
+ async function run() {
41
+ await saveLeaderboardImage(entries, "./leaderboard.png");
42
+ console.log("✅ Done");
43
+ }
44
+
45
+ run();
46
+ ```
package/src/index.js ADDED
@@ -0,0 +1,104 @@
1
+ const puppeteer = require('puppeteer');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * Encode base64url
6
+ */
7
+ function toBase64Url(input) {
8
+ return Buffer
9
+ .from(input, 'utf8')
10
+ .toString('base64')
11
+ .replace(/\+/g, '-')
12
+ .replace(/\//g, '_')
13
+ .replace(/=+$/, '');
14
+ }
15
+
16
+ /**
17
+ * Validate entries
18
+ */
19
+ function validateEntries(entries) {
20
+ if (!Array.isArray(entries) || entries.length === 0) {
21
+ throw new Error('Entries phải là mảng và có ít nhất 1 phần tử');
22
+ }
23
+
24
+ entries.forEach((entry, index) => {
25
+ if (!entry.name || typeof entry.name !== 'string') {
26
+ throw new Error(`Entry ${index + 1}: name không hợp lệ`);
27
+ }
28
+
29
+ if (typeof entry.score !== 'number' || !Number.isFinite(entry.score)) {
30
+ throw new Error(`Entry ${index + 1}: score không hợp lệ`);
31
+ }
32
+
33
+ if (typeof entry.avatar !== 'string') {
34
+ throw new Error(`Entry ${index + 1}: avatar phải là string`);
35
+ }
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Tạo URL
41
+ */
42
+ function getLeaderboardCardUrl(entries) {
43
+ validateEntries(entries);
44
+
45
+ const encoded = toBase64Url(JSON.stringify(entries));
46
+
47
+ return `https://hrv-web.netlify.app/apis/leaderboard-card?data=${encoded}`;
48
+ }
49
+
50
+ async function fetchLeaderboardCardImage(entries) {
51
+ const url = getLeaderboardCardUrl(entries);
52
+
53
+ const browser = await puppeteer.launch({
54
+ headless: true,
55
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
56
+ });
57
+
58
+ try {
59
+ const page = await browser.newPage();
60
+
61
+ // Viewport dọc 9:16
62
+ await page.setViewport({
63
+ width: 1080,
64
+ height: 1920,
65
+ deviceScaleFactor: 2
66
+ });
67
+
68
+ await page.goto(url, {
69
+ waitUntil: 'networkidle0',
70
+ timeout: 30000
71
+ });
72
+
73
+ // Đợi container render xong
74
+ await page.waitForSelector('.container');
75
+
76
+ const element = await page.$('.container');
77
+
78
+ if (!element) throw new Error('Không tìm thấy .container');
79
+
80
+ const screenshot = await element.screenshot({
81
+ type: 'png'
82
+ });
83
+
84
+ return screenshot;
85
+
86
+ } finally {
87
+ await browser.close();
88
+ }
89
+ }
90
+
91
+
92
+ async function saveLeaderboardImage(entries, filePath) {
93
+ const buffer = await fetchLeaderboardCardImage(entries);
94
+ fs.writeFileSync(filePath, buffer);
95
+ }
96
+
97
+ /**
98
+ * Export CommonJS
99
+ */
100
+ module.exports = {
101
+ getLeaderboardCardUrl,
102
+ fetchLeaderboardCardImage,
103
+ saveLeaderboardImage
104
+ };