244 lines
6.6 KiB
JavaScript
244 lines
6.6 KiB
JavaScript
const axios = require('axios');
|
||
|
||
const BAIDU_OCR_API = {
|
||
token: 'https://aip.baidubce.com/oauth/2.0/token',
|
||
generalBasic: 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic',
|
||
accurateBasic: 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic'
|
||
};
|
||
|
||
let accessTokenCache = {
|
||
token: null,
|
||
expireAt: 0
|
||
};
|
||
|
||
async function getAccessToken() {
|
||
const now = Date.now();
|
||
if (accessTokenCache.token && accessTokenCache.expireAt > now + 60000) {
|
||
return accessTokenCache.token;
|
||
}
|
||
|
||
const apiKey = process.env.BAIDU_OCR_API_KEY;
|
||
const secretKey = process.env.BAIDU_OCR_SECRET_KEY;
|
||
|
||
if (!apiKey || !secretKey) {
|
||
throw new Error('百度OCR配置缺失:请检查 BAIDU_OCR_API_KEY 和 BAIDU_OCR_SECRET_KEY');
|
||
}
|
||
|
||
const response = await axios.post(BAIDU_OCR_API.token, null, {
|
||
params: {
|
||
grant_type: 'client_credentials',
|
||
client_id: apiKey,
|
||
client_secret: secretKey
|
||
},
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
}
|
||
});
|
||
|
||
const { access_token, expires_in } = response.data;
|
||
|
||
if (!access_token) {
|
||
throw new Error(`获取百度OCR Token失败: ${JSON.stringify(response.data)}`);
|
||
}
|
||
|
||
accessTokenCache = {
|
||
token: access_token,
|
||
expireAt: now + (expires_in * 1000)
|
||
};
|
||
|
||
return access_token;
|
||
}
|
||
|
||
async function recognizeText(imageBase64, options = {}) {
|
||
const accessToken = await getAccessToken();
|
||
|
||
const url = `${BAIDU_OCR_API.generalBasic}?access_token=${accessToken}`;
|
||
|
||
const params = new URLSearchParams();
|
||
params.append('image', imageBase64);
|
||
|
||
if (options.language_type) {
|
||
params.append('language_type', options.language_type);
|
||
}
|
||
|
||
const response = await axios.post(url, params.toString(), {
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded'
|
||
}
|
||
});
|
||
|
||
return response.data;
|
||
}
|
||
|
||
async function recognizeTextAccurate(imageBase64, options = {}) {
|
||
const accessToken = await getAccessToken();
|
||
|
||
const url = `${BAIDU_OCR_API.accurateBasic}?access_token=${accessToken}`;
|
||
|
||
const params = new URLSearchParams();
|
||
params.append('image', imageBase64);
|
||
|
||
if (options.language_type) {
|
||
params.append('language_type', options.language_type);
|
||
}
|
||
|
||
const response = await axios.post(url, params.toString(), {
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded'
|
||
}
|
||
});
|
||
|
||
return response.data;
|
||
}
|
||
|
||
function getDefaultPrice(type, times) {
|
||
const prices = {
|
||
year: '120',
|
||
halfYear: '60',
|
||
quarter: '30',
|
||
month: '10'
|
||
};
|
||
|
||
if (type === 'times') {
|
||
const timesCount = parseInt(times) || 1;
|
||
return String(timesCount * 10);
|
||
}
|
||
|
||
return prices[type] || '10';
|
||
}
|
||
|
||
function extractMembershipInfo(ocrResult) {
|
||
if (!ocrResult || !ocrResult.words_result || ocrResult.words_result.length === 0) {
|
||
return [];
|
||
}
|
||
|
||
const text = ocrResult.words_result.map(w => w.words).join('\n');
|
||
const lines = ocrResult.words_result.map(w => w.words);
|
||
|
||
const platformKeywords = {
|
||
'淘宝': ['淘宝', 'taobao', '88vip', '88VIP'],
|
||
'京东': ['京东', 'jd', 'JD', 'plus', 'PLUS'],
|
||
'拼多多': ['拼多多', 'pdd', 'PDD'],
|
||
'美团': ['美团', 'meituan'],
|
||
'饿了么': ['饿了么', 'eleme', 'ele.me'],
|
||
'抖音': ['抖音', 'douyin', 'tiktok'],
|
||
'快手': ['快手', 'kuaishou'],
|
||
'网易云音乐': ['网易云', 'netease', '163'],
|
||
'QQ音乐': ['QQ音乐', 'qq音乐'],
|
||
'优酷': ['优酷', 'youku'],
|
||
'爱奇艺': ['爱奇艺', 'iqiyi'],
|
||
'腾讯视频': ['腾讯视频', 'v.qq'],
|
||
'哔哩哔哩': ['哔哩哔哩', 'bilibili', 'B站'],
|
||
'喜马拉雅': ['喜马拉雅', 'ximalaya'],
|
||
'知乎': ['知乎', 'zhihu'],
|
||
'百度网盘': ['百度网盘', '百度云']
|
||
};
|
||
|
||
let platform = null;
|
||
for (const [pName, keywords] of Object.entries(platformKeywords)) {
|
||
for (const keyword of keywords) {
|
||
if (text.toLowerCase().includes(keyword.toLowerCase())) {
|
||
platform = pName;
|
||
break;
|
||
}
|
||
}
|
||
if (platform) break;
|
||
}
|
||
|
||
const typePatterns = [
|
||
{ patterns: [/年卡/, /年度会员/, /\d+年/], type: 'year' },
|
||
{ patterns: [/半年卡/, /半年会员/, /6个月/], type: 'halfYear' },
|
||
{ patterns: [/季卡/, /季度会员/, /3个月/], type: 'quarter' },
|
||
{ patterns: [/月卡/, /月度会员/, /1个月/], type: 'month' },
|
||
{ patterns: [/次卡/, /按次数/], type: 'times' }
|
||
];
|
||
|
||
let detectedType = 'month';
|
||
for (const { patterns, type } of typePatterns) {
|
||
for (const pattern of patterns) {
|
||
if (pattern.test(text)) {
|
||
detectedType = type;
|
||
break;
|
||
}
|
||
}
|
||
if (detectedType !== 'month') break;
|
||
}
|
||
|
||
const datePatterns = [
|
||
/(\d{4})[年/-](\d{1,2})[月/-](\d{1,2})/,
|
||
/(\d{4})(\d{2})(\d{2})/,
|
||
/(\d{2})[年/-](\d{1,2})[月/-](\d{1,2})/,
|
||
/有效期[至到::]\s*(\d{4})[年/-](\d{1,2})[月/-](\d{1,2})/,
|
||
/到期[时间日]:?\s*(\d{4})[年/-](\d{1,2})[月/-](\d{1,2})/,
|
||
/(\d{4})\.(\d{1,2})\.(\d{1,2})/
|
||
];
|
||
|
||
let expireDate = '9999-12-31';
|
||
for (const pattern of datePatterns) {
|
||
const match = text.match(pattern);
|
||
if (match) {
|
||
let year = match[1];
|
||
const month = match[2].padStart(2, '0');
|
||
const day = match[3].padStart(2, '0');
|
||
|
||
if (year.length === 2) {
|
||
year = '20' + year;
|
||
}
|
||
|
||
expireDate = `${year}-${month}-${day}`;
|
||
break;
|
||
}
|
||
}
|
||
|
||
const benefitKeywords = [
|
||
'优酷', '网易云', 'QQ音乐', '酷狗', '酷我',
|
||
'爱奇艺', '腾讯视频', '芒果TV', '哔哩哔哩',
|
||
'饿了么', '美团', '高德打车', '滴滴',
|
||
'夸克', '百度网盘', '迅雷',
|
||
'喜马拉雅', '知乎', '微博',
|
||
'淘票票', '飞猪', '希尔顿', '万豪',
|
||
'视频会员', '超级吃货卡', '天猫超市', '天猫国际',
|
||
'阿里健康', '专属客服', '省钱卡', '网盘会员',
|
||
'打车会员', '金卡', '皮肤装扮', '每日领券',
|
||
'出行礼遇', '专享立减', '游戏特权'
|
||
];
|
||
|
||
const benefits = [];
|
||
for (const line of lines) {
|
||
for (const keyword of benefitKeywords) {
|
||
if (line.includes(keyword)) {
|
||
const existing = benefits.find(b => b.name === keyword);
|
||
if (!existing) {
|
||
benefits.push({
|
||
name: keyword,
|
||
type: detectedType,
|
||
times: detectedType === 'times' ? null : null,
|
||
price: getDefaultPrice(detectedType, null),
|
||
expireDate: expireDate
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (benefits.length === 0 && platform) {
|
||
benefits.push({
|
||
name: platform,
|
||
type: detectedType,
|
||
times: null,
|
||
price: getDefaultPrice(detectedType, null),
|
||
expireDate: expireDate
|
||
});
|
||
}
|
||
|
||
return benefits;
|
||
}
|
||
|
||
module.exports = {
|
||
getAccessToken,
|
||
recognizeText,
|
||
recognizeTextAccurate,
|
||
extractMembershipInfo
|
||
};
|