下载地址:https://www.pan38.com/share.php?code=pvvmX 提取码:6728 【仅供学习】
下面的代码只能用于在QQ上面生成自定义的卡片,在其他社交APP是用不了的,并且在QQ企点版也用不了哦,只能在官方QQapp才能使用。
express = require('express');
const bodyParser = require('body-parser');
const xml2js = require('xml2js');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
// 中间件
app.use(bodyParser.text({ type: 'text/xml' }));
app.use(express.static('public'));
// 卡片数据库
let cardsDB = {};
const DB_FILE = path.join(__dirname, 'cards.json');
// 加载数据库
function loadDatabase() {
try {
if (fs.existsSync(DB_FILE)) {
cardsDB = JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
}
} catch (err) {
console.error('加载数据库失败:', err);
}
}
// 保存数据库
function saveDatabase() {
try {
fs.writeFileSync(DB_FILE, JSON.stringify(cardsDB, null, 2));
} catch (err) {
console.error('保存数据库失败:', err);
}
}
// 验证XML结构
function validateCardXML(xmlString) {
return new Promise((resolve, reject) => {
xml2js.parseString(xmlString, (err, result) => {
if (err) return reject(new Error('无效的XML格式'));
if (!result.msg || !result.msg.$ || !result.msg.$.serviceID) {
return reject(new Error('缺少必要的msg属性'));
}
if (!result.msg.item || !result.msg.item[0]) {
return reject(new Error('缺少item元素'));
}
resolve(result);
});
});
}
// 路由
app.post('/api/cards', async (req, res) => {
try {
const xmlString = req.body;
if (!xmlString) {
return res.status(400).json({ error: '请提供XML内容' });
}
// 验证XML
await validateCardXML(xmlString);
// 生成卡片ID
const cardId = 'card_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
// 存储卡片
cardsDB[cardId] = {
xml: xmlString,
createdAt: new Date().toISOString(),
views: 0
};
saveDatabase();
res.json({
success: true,
cardId,
viewUrl: `https://yourdomain.com/cards/${cardId}`,
xmlUrl: `https://yourdomain.com/api/cards/${cardId}/xml`
});
} catch (err) {
res.status(400).json({ error: err.message });
}
});
app.get('/api/cards/:id/xml', (req, res) => {
const cardId = req.params.id;
const card = cardsDB[cardId];
if (!card) {
return res.status(404).json({ error: '卡片不存在' });
}
// 更新查看次数
card.views++;
saveDatabase();
res.type('application/xml');
res.send(card.xml);
});
app.get('/cards/:id', (req, res) => {
const cardId = req.params.id;
const card = cardsDB[cardId];
if (!card) {
return res.status(404).send('卡片不存在');
}
// 渲染卡片查看页面
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>QQ卡片查看器</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.card-container { max-width: 500px; margin: 0 auto; }
.card-info { margin-top: 20px; font-size: 14px; color: #666; }
</style>
</head>
<body>
<div class="card-container" id="card-view"></div>
<div class="card-info">
<p>创建时间: ${new Date(card.createdAt).toLocaleString()}</p>
<p>查看次数: ${card.views}</p>
</div>
<script>
fetch('/api/cards/${cardId}/xml')
.then(response => response.text())
.then(xml => {
// 这里添加XML解析和渲染逻辑
document.getElementById('card-view').innerHTML =
'<pre>' + xml + '</pre>';
});
</script>
</body>
</html>
`);
});
// 启动服务器
loadDatabase();
app.listen(PORT, () => {
console.log(`QQ卡片生成器服务运行在 http://localhost:${PORT}`);
});
class QQCardParser {
constructor(xmlString) {
this.xmlString = xmlString;
this.parsedData = null;
this.errors = [];
}
async parse() {
try {
const parser = new xml2js.Parser({
explicitArray: false,
ignoreAttrs: false
});
this.parsedData = await parser.parseStringPromise(this.xmlString);
this.validateStructure();
return this.getNormalizedData();
} catch (err) {
this.errors.push(`XML解析错误: ${err.message}`);
throw new Error(this.errors.join('\n'));
}
}
validateStructure() {
if (!this.parsedData.msg) {
this.errors.push('缺少根元素msg');
return;
}
const msg = this.parsedData.msg;
const requiredMsgAttrs = ['serviceID', 'templateID', 'action'];
requiredMsgAttrs.forEach(attr => {
if (!msg.$[attr]) {
this.errors.push(`msg元素缺少必要属性: ${attr}`);
}
});
if (!msg.item) {
this.errors.push('缺少item元素');
} else {
this.validateItem(msg.item);
}
if (this.errors.length > 0) {
throw new Error('XML结构验证失败');
}
}
validateItem(item) {
if (!item.layout) {
this.errors.push('item元素缺少layout属性');
}
if (!item.title && !item.summary) {
this.errors.push('item元素必须包含title或summary');
}
}
getNormalizedData() {
const msg = this.parsedData.msg;
const result = {
meta: {
serviceID: msg.$.serviceID,
templateID: msg.$.templateID,
action: msg.$.action,
brief: msg.$.brief || '',
sourceMsgId: msg.$.sourceMsgId || '0'
},
content: {
layout: parseInt(msg.item.$.layout) || 0,
title: msg.item.title || '',
summary: msg.item.summary || '',
picture: msg.item.picture ? {
cover: msg.item.picture.$.cover || '',
width: msg.item.picture.$.width ? parseInt(msg.item.picture.$.width) : null,
height: msg.item.picture.$.height ? parseInt(msg.item.picture.$.height) : null
} : null
},
source: msg.source ? {
name: msg.source.$.name || '',
icon: msg.source.$.icon || '',
url: msg.source.$.url || '',
actionData: msg.source.$.actionData || ''
} : null
};
return result;
}
static getTemplate(templateName) {
const templates = {
announcement: {
meta: {
serviceID: "1",
templateID: "2",
action: "web",
brief: "公告"
},
content: {
layout: 2,
title: "重要公告",
summary: "请全体成员注意...",
picture: {
cover: "https://example.com/announce.png"
}
},
source: {
name: "官方公告",
icon: "https://example.com/logo.png"
}
},
invitation: {
meta: {
serviceID: "1",
templateID: "3",
action: "web",
brief: "邀请函"
},
content: {
layout: 1,
title: "活动邀请",
summary: "诚邀您参加我们的活动",
picture: {
cover: "https://example.com/event.jpg"
}
},
source: {
name: "活动中心",
url: "https://example原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。