Update api.js
Browse files
api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
const express = require('express');
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const axios = require('axios');
|
| 4 |
-
const cors = require('cors');
|
| 5 |
const app = express();
|
| 6 |
|
| 7 |
app.use((req, res, next) => {
|
|
@@ -14,13 +14,13 @@ app.use(express.static('public'));
|
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
-
app.get('/
|
| 18 |
const { name } = req.query;
|
| 19 |
|
| 20 |
const input = `16566580507931f7c79f67099ddad3a4f0e4ed29a6name${name}page1`;
|
| 21 |
|
| 22 |
const sign = crypto.createHash('md5').update(input).digest('hex').toUpperCase();
|
| 23 |
-
|
| 24 |
const url = 'https://app.blued.cn/home/web-recharge/getUserInfoByNickname';
|
| 25 |
|
| 26 |
const params = {
|
|
@@ -31,13 +31,356 @@ app.get('/blued', async (req, res) => {
|
|
| 31 |
|
| 32 |
try {
|
| 33 |
const response = await axios.get(url, { params });
|
| 34 |
-
res.send(response.data);
|
| 35 |
} catch (error) {
|
| 36 |
console.error(error);
|
| 37 |
-
res.status(500).send('Error getting data from Blued');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
});
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
app.listen(3000, '0.0.0.0', () => {
|
| 42 |
-
console.log('Server listening on port 80');
|
| 43 |
});
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const axios = require('axios');
|
| 4 |
+
const cors = require('cors');
|
| 5 |
const app = express();
|
| 6 |
|
| 7 |
app.use((req, res, next) => {
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
+
app.get('/search', async (req, res) => {
|
| 18 |
const { name } = req.query;
|
| 19 |
|
| 20 |
const input = `16566580507931f7c79f67099ddad3a4f0e4ed29a6name${name}page1`;
|
| 21 |
|
| 22 |
const sign = crypto.createHash('md5').update(input).digest('hex').toUpperCase();
|
| 23 |
+
|
| 24 |
const url = 'https://app.blued.cn/home/web-recharge/getUserInfoByNickname';
|
| 25 |
|
| 26 |
const params = {
|
|
|
|
| 31 |
|
| 32 |
try {
|
| 33 |
const response = await axios.get(url, { params });
|
| 34 |
+
res.send(processResponse(response.data));
|
| 35 |
} catch (error) {
|
| 36 |
console.error(error);
|
| 37 |
+
res.status(500).send('Error getting data from Blued');
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
app.get('/viewPhone', async (req, res) => {
|
| 42 |
+
const { id } = req.query;
|
| 43 |
+
|
| 44 |
+
const url = 'https://app.blued.cn/msg/phone/list';
|
| 45 |
+
|
| 46 |
+
const headers = {
|
| 47 |
+
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
|
| 48 |
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
| 49 |
+
'Accept-Encoding': 'gzip, deflate, br',
|
| 50 |
+
'Connection': 'keep-alive',
|
| 51 |
+
'Cookie': 'uid=' + id
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
try {
|
| 55 |
+
const response = await axios({
|
| 56 |
+
url,
|
| 57 |
+
headers
|
| 58 |
+
});
|
| 59 |
+
res.send((response.data));
|
| 60 |
+
} catch (error) {
|
| 61 |
+
console.error(error);
|
| 62 |
+
res.status(500).send('Error getting data from Blued');
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
// 新增路由:代理用户主页
|
| 68 |
+
app.get('/getDetail', async (req, res) => {
|
| 69 |
+
const { id } = req.query;
|
| 70 |
+
|
| 71 |
+
if (!id) {
|
| 72 |
+
return res.status(400).send('Missing id parameter');
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
const targetUrl = `https://app.blued.cn/user`;
|
| 76 |
+
const params = { id };
|
| 77 |
+
|
| 78 |
+
// 模拟真实浏览器请求头(关键!)
|
| 79 |
+
const headers = {
|
| 80 |
+
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
|
| 81 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
| 82 |
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
| 83 |
+
'Accept-Encoding': 'gzip, deflate, br',
|
| 84 |
+
'Connection': 'keep-alive',
|
| 85 |
+
'Upgrade-Insecure-Requests': '1',
|
| 86 |
+
'Sec-Fetch-Dest': 'document',
|
| 87 |
+
'Sec-Fetch-Mode': 'navigate',
|
| 88 |
+
'Sec-Fetch-Site': 'none',
|
| 89 |
+
'Sec-Fetch-User': '?1',
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
+
try {
|
| 93 |
+
const response = await axios({
|
| 94 |
+
method: 'GET',
|
| 95 |
+
url: targetUrl,
|
| 96 |
+
params: params,
|
| 97 |
+
headers: headers,
|
| 98 |
+
responseType: 'text', // 确保返回文本
|
| 99 |
+
timeout: 10000,
|
| 100 |
+
// 如果 Blued 使用 gzip,axios 会自动解压
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
+
// 设置正确的内容类型
|
| 104 |
+
res.set('Content-Type', 'text/html; charset=utf-8');
|
| 105 |
+
// 可选:移除或修改 CSP/X-Frame-Options(但通常在 HTML meta 或响应头中)
|
| 106 |
+
// 注意:响应头由 Blued 控制,我们只能改 HTML 内容
|
| 107 |
+
|
| 108 |
+
// (可选)尝试移除 HTML 中的 X-Frame-Options meta(如果存在)
|
| 109 |
+
let html = response.data;
|
| 110 |
+
// 移除可能的 <meta http-equiv="X-Frame-Options"> 标签
|
| 111 |
+
html = html.replace(/<meta[^>]*http-equiv=["']?X-Frame-Options["']?[^>]*>/gi, '');
|
| 112 |
+
console.log(extractConfigFromHtml(html))
|
| 113 |
+
// 返回修改后的 HTML
|
| 114 |
+
res.json(extractConfigFromHtml(html));
|
| 115 |
+
} catch (error) {
|
| 116 |
+
console.error('Proxy error:', error.message);
|
| 117 |
+
if (error.response) {
|
| 118 |
+
console.error('Blued responded with status:', error.response.status);
|
| 119 |
+
}
|
| 120 |
+
res.status(500).send('Failed to fetch user page');
|
| 121 |
}
|
| 122 |
});
|
| 123 |
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
function extractConfigFromHtml(htmlString) {
|
| 129 |
+
// 匹配 window.CONFIG = { ... };
|
| 130 |
+
const match = htmlString.match(/window\.CONFIG\s*=\s*(\{[\s\S]*?\});\s*</);
|
| 131 |
+
if (match) {
|
| 132 |
+
try {
|
| 133 |
+
// 安全地解析 JSON(注意:这里其实是 JS 对象字面量,不是标准 JSON)
|
| 134 |
+
// 但由于 Blued 的 CONFIG 是合法 JS 对象,且无函数,可用 eval(谨慎!)或更安全方式
|
| 135 |
+
// 推荐:先替换掉末尾分号,再用 Function 构造器(比 eval 稍安全)
|
| 136 |
+
const configStr = match[1];
|
| 137 |
+
// 使用 Function 构造器解析(避免污染作用域)
|
| 138 |
+
const config = new Function(`return (${configStr})`)();
|
| 139 |
+
return config;
|
| 140 |
+
} catch (e) {
|
| 141 |
+
console.error('解析 CONFIG 失败:', e);
|
| 142 |
+
return null;
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
return null;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// 示例用法(假设你已通过 fetch 或 axios 获取到 html 字符串)
|
| 149 |
+
// const html = await response.text();
|
| 150 |
+
// const config = extractConfigFromHtml(html);
|
| 151 |
+
|
| 152 |
+
function processResponse(response) {
|
| 153 |
+
// 检查结构是否合法
|
| 154 |
+
if (!response || !response.data || !Array.isArray(response.data.list)) {
|
| 155 |
+
return response; // 结构不符合,直接返回原数据
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// 深拷贝并处理 list 中的每个对象
|
| 159 |
+
const processedList = response.data.list.map(item => {
|
| 160 |
+
if (item && typeof item === 'object' && item.hasOwnProperty('uid')) {
|
| 161 |
+
return {
|
| 162 |
+
...item,
|
| 163 |
+
uuid: decrypt(item.uid)
|
| 164 |
+
};
|
| 165 |
+
}
|
| 166 |
+
// 如果没有 uid,保留原对象
|
| 167 |
+
return { ...item };
|
| 168 |
+
});
|
| 169 |
+
|
| 170 |
+
// 返回新结构
|
| 171 |
+
return {
|
| 172 |
+
...response,
|
| 173 |
+
data: {
|
| 174 |
+
...response.data,
|
| 175 |
+
list: processedList
|
| 176 |
+
}
|
| 177 |
+
};
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class Hashids {
|
| 182 |
+
constructor(salt = '', minLength = 0, alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123567890') {
|
| 183 |
+
this.salt = salt + '';
|
| 184 |
+
this.minLength = Math.max(0, parseInt(minLength, 10) || 0);
|
| 185 |
+
this.alphabet = '';
|
| 186 |
+
// deduplicate alphabet chars (preserve order)
|
| 187 |
+
for (let ch of alphabet) if (!this.alphabet.includes(ch)) this.alphabet += ch;
|
| 188 |
+
if (this.alphabet.length < 16) throw new Error('alphabet must contain at least 16 unique characters');
|
| 189 |
+
if (this.alphabet.includes(' ')) throw new Error('alphabet cannot contains spaces');
|
| 190 |
+
|
| 191 |
+
this.seps = 'cfhistuCFHISTU';
|
| 192 |
+
// remove seps from alphabet (mirror Java logic)
|
| 193 |
+
let a = this.alphabet;
|
| 194 |
+
for (let i = 0; i < this.seps.length; i++) {
|
| 195 |
+
const idx = a.indexOf(this.seps.charAt(i));
|
| 196 |
+
if (idx === -1) {
|
| 197 |
+
// if not found, replace that position in seps with space (same effect as Java)
|
| 198 |
+
this.seps = this.seps.substring(0, i) + ' ' + this.seps.substring(i + 1);
|
| 199 |
+
} else {
|
| 200 |
+
a = a.substring(0, idx) + ' ' + a.substring(idx + 1);
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
a = a.replace(/\s+/g, '');
|
| 204 |
+
this.seps = this.seps.replace(/\s+/g, '');
|
| 205 |
+
|
| 206 |
+
// m21293b(strReplaceAll, this.f22838a);
|
| 207 |
+
let shuffledSeps = this._consistentShuffle(this.seps, this.salt);
|
| 208 |
+
this.seps = shuffledSeps;
|
| 209 |
+
|
| 210 |
+
// adjust seps/alphabet according to Java logic
|
| 211 |
+
if (!this.seps || (a.length / this.seps.length) > 3.5) {
|
| 212 |
+
let ceil = Math.ceil(a.length / 3.5);
|
| 213 |
+
if (ceil === 1) ceil = ceil + 1;
|
| 214 |
+
if (ceil > this.seps.length) {
|
| 215 |
+
const diff = ceil - this.seps.length;
|
| 216 |
+
this.seps += a.substring(0, diff);
|
| 217 |
+
a = a.substring(diff);
|
| 218 |
+
} else {
|
| 219 |
+
this.seps = this.seps.substring(0, ceil);
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// now shuffle alphabet with salt
|
| 224 |
+
a = this._consistentShuffle(a, this.salt);
|
| 225 |
+
|
| 226 |
+
const guardCount = Math.ceil(a.length / 12);
|
| 227 |
+
if (a.length < 3) {
|
| 228 |
+
this.guards = this.seps.substring(0, guardCount);
|
| 229 |
+
this.seps = this.seps.substring(guardCount);
|
| 230 |
+
} else {
|
| 231 |
+
this.guards = a.substring(0, guardCount);
|
| 232 |
+
a = a.substring(guardCount);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
this.alphabet = a;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
// consistent shuffle m21293b
|
| 239 |
+
_consistentShuffle(alphabet, salt) {
|
| 240 |
+
if (!salt || salt.length === 0) return alphabet;
|
| 241 |
+
const arr = alphabet.split('');
|
| 242 |
+
const saltChars = salt.split('').map(c => c.charCodeAt(0));
|
| 243 |
+
let i = arr.length - 1;
|
| 244 |
+
let v = 0;
|
| 245 |
+
let p = 0;
|
| 246 |
+
while (i > 0) {
|
| 247 |
+
const idx = v % saltChars.length;
|
| 248 |
+
const c = saltChars[idx];
|
| 249 |
+
p += c;
|
| 250 |
+
const j = (c + idx + p) % i;
|
| 251 |
+
// swap arr[j] and arr[i]
|
| 252 |
+
const tmp = arr[j]; arr[j] = arr[i]; arr[i] = tmp;
|
| 253 |
+
i--;
|
| 254 |
+
v = idx + 1;
|
| 255 |
+
}
|
| 256 |
+
return arr.join('');
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// encode numbers -> hash (m21296a -> m21294b)
|
| 260 |
+
encode(...numbers) {
|
| 261 |
+
if (!numbers || numbers.length === 0) return '';
|
| 262 |
+
// check max like Java: > 9007199254740992L
|
| 263 |
+
const MAX_ALLOWED = BigInt('9007199254740992');
|
| 264 |
+
for (let n of numbers) {
|
| 265 |
+
// accept BigInt or Number or numeric string
|
| 266 |
+
let bn = (typeof n === 'bigint') ? n : BigInt(n);
|
| 267 |
+
if (bn > MAX_ALLOWED) throw new Error('number can not be greater than 9007199254740992');
|
| 268 |
+
}
|
| 269 |
+
return this._encode(numbers);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
_encode(nums) {
|
| 273 |
+
// compute i sum
|
| 274 |
+
let iSum = 0;
|
| 275 |
+
for (let idx = 0; idx < nums.length; idx++) {
|
| 276 |
+
const n = BigInt(nums[idx]);
|
| 277 |
+
iSum = Number(iSum + Number(n % BigInt(idx + 100)));
|
| 278 |
+
}
|
| 279 |
+
let alphabet = this.alphabet;
|
| 280 |
+
const lotteryChar = alphabet.charAt(iSum % alphabet.length);
|
| 281 |
+
let ret = lotteryChar;
|
| 282 |
+
for (let i = 0; i < nums.length; i++) {
|
| 283 |
+
const n = BigInt(nums[i]);
|
| 284 |
+
alphabet = this._consistentShuffle(alphabet, (lotteryChar + this.salt + alphabet).substring(0, alphabet.length));
|
| 285 |
+
ret += this._hashNumber(n, alphabet);
|
| 286 |
+
if (i + 1 < nums.length) {
|
| 287 |
+
const guardIndex = Number((n % BigInt(alphabet.charCodeAt(0) + i)) % BigInt(this.seps.length));
|
| 288 |
+
ret += this.seps.charAt(guardIndex);
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
// pad with guards/guards logic like Java
|
| 293 |
+
if (ret.length < this.minLength) {
|
| 294 |
+
ret = this.guards.charAt((ret.charCodeAt(0) + iSum) % this.guards.length) + ret;
|
| 295 |
+
if (ret.length < this.minLength) {
|
| 296 |
+
ret = ret + this.guards.charAt((iSum + ret.charCodeAt(2 % ret.length)) % this.guards.length);
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
// while still less than minLength, expand by shuffling alphabet etc
|
| 301 |
+
let half = Math.floor(alphabet.length / 2);
|
| 302 |
+
while (ret.length < this.minLength) {
|
| 303 |
+
alphabet = this._consistentShuffle(alphabet, alphabet);
|
| 304 |
+
let str = alphabet.substring(half) + ret + alphabet.substring(0, half);
|
| 305 |
+
const excess = str.length - this.minLength;
|
| 306 |
+
if (excess > 0) {
|
| 307 |
+
const start = Math.floor(excess / 2);
|
| 308 |
+
str = str.substring(start, start + this.minLength);
|
| 309 |
+
}
|
| 310 |
+
ret = str;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
return ret;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// convert number to string using alphabet (m21291a)
|
| 317 |
+
_hashNumber(number, alphabet) {
|
| 318 |
+
let num = BigInt(number);
|
| 319 |
+
const len = alphabet.length;
|
| 320 |
+
let out = '';
|
| 321 |
+
if (num === 0n) return alphabet.charAt(0);
|
| 322 |
+
while (num > 0n) {
|
| 323 |
+
const idx = Number(num % BigInt(len));
|
| 324 |
+
out = alphabet.charAt(idx) + out;
|
| 325 |
+
num = num / BigInt(len);
|
| 326 |
+
}
|
| 327 |
+
return out;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
// decode hash -> array of numbers (m21297a -> m21292a)
|
| 331 |
+
decode(hash) {
|
| 332 |
+
if (hash === '') return [];
|
| 333 |
+
// remove guards
|
| 334 |
+
const hashSplit = hash.replace(new RegExp('[' + this.guards.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + ']', 'g'), ' ').split(' ').filter(Boolean);
|
| 335 |
+
const hashPart = (hashSplit.length === 3 || hashSplit.length === 2) ? hashSplit[1] : hashSplit[0];
|
| 336 |
+
const lottery = hashPart.charAt(0);
|
| 337 |
+
const parts = hashPart.substring(1).replace(new RegExp('[' + this.seps.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + ']', 'g'), ' ').split(' ').filter(Boolean);
|
| 338 |
+
const result = [];
|
| 339 |
+
for (let i = 0; i < parts.length; i++) {
|
| 340 |
+
const part = parts[i];
|
| 341 |
+
let alphabet = this._consistentShuffle(this.alphabet, (lottery + this.salt + this.alphabet).substring(0, this.alphabet.length));
|
| 342 |
+
result.push(this._unhash(part, alphabet));
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
// verify
|
| 346 |
+
const encoded = this._encode(result);
|
| 347 |
+
// if verification fails, return empty arr
|
| 348 |
+
if (encoded !== hash) return [];
|
| 349 |
+
return result;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
_unhash(input, alphabet) {
|
| 353 |
+
let num = 0n;
|
| 354 |
+
const len = BigInt(alphabet.length);
|
| 355 |
+
for (let i = 0; i < input.length; i++) {
|
| 356 |
+
const pos = BigInt(alphabet.indexOf(input.charAt(i)));
|
| 357 |
+
num = num * len + pos;
|
| 358 |
+
}
|
| 359 |
+
return num;
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// --- 对外封装的函数(与 Java 中的 HashidEncryptTool 对应) ---
|
| 364 |
+
// decrypt: Java m22018a(String str) -> 传入 hash,返回拼接的数字字符串
|
| 365 |
+
function decrypt(hashStr) {
|
| 366 |
+
if (!hashStr) return '';
|
| 367 |
+
try {
|
| 368 |
+
const h = new Hashids('1766', 6);
|
| 369 |
+
const arr = h.decode(hashStr);
|
| 370 |
+
// arr contains BigInt values. Concatenate them as decimal strings without separator (same as Java append)
|
| 371 |
+
return arr.map(x => x.toString()).join('');
|
| 372 |
+
} catch (e) {
|
| 373 |
+
console.error(e);
|
| 374 |
+
return '';
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
module.exports = {
|
| 380 |
+
HashidsJS: Hashids,
|
| 381 |
+
decrypt: decrypt
|
| 382 |
+
};
|
| 383 |
+
|
| 384 |
app.listen(3000, '0.0.0.0', () => {
|
| 385 |
+
console.log('Server listening on port 80');
|
| 386 |
});
|