hexo-afdian-plugin
Version:
爱发电插件,用于展示爱发电赞助信息
407 lines (358 loc) • 13.7 kB
JavaScript
;
const path = require('path');
const fs = require('fs');
const axios = require('axios');
const crypto = require('crypto-js');
const moment = require('moment');
// 默认配置
const DEFAULT_CONFIG = {
title: '赞助名单',
description: '感谢您的赞助!',
cacheTime: 60, // 缓存时间,单位分钟
pageSize: 10, // 每页显示数量
emptyText: '暂无赞助',
moreText: '更多赞助',
moreLink: 'https://afdian.com/',
buttonText: '赞助',
buttonLink: 'https://afdian.com/',
sponsorNumber: 66, // 高亮显示金额阈值
sponsorUrl: 'https://afdian.com/' // 赞助链接
};
// 注册插件
module.exports = function(hexo) {
// 输出插件信息
hexo.log.info('==========================================');
hexo.log.info('Hexo 爱发电插件已加载');
hexo.log.info('版本: 1.0.1');
hexo.log.info('作者: 心都');
hexo.log.info('==========================================');
// 合并配置
hexo.config.afdian = Object.assign({}, DEFAULT_CONFIG, hexo.config.afdian);
// 复制布局文件到主题目录
const themeName = hexo.config.theme;
const themeLayoutDir = path.join(hexo.base_dir, 'themes', themeName, 'layout');
const pluginLayoutFile = path.join(__dirname, 'layout/afdian.ejs');
const themeLayoutFile = path.join(themeLayoutDir, 'afdian.ejs');
try {
// 确保主题布局目录存在
if (!fs.existsSync(themeLayoutDir)) {
hexo.log.warn('爱发电插件:主题布局目录不存在,将尝试创建');
fs.mkdirSync(themeLayoutDir, { recursive: true });
}
// 复制布局文件(如果不存在)
if (!fs.existsSync(themeLayoutFile)) {
hexo.log.info('爱发电插件:正在复制布局文件到主题目录');
fs.copyFileSync(pluginLayoutFile, themeLayoutFile);
hexo.log.info('爱发电插件:布局文件复制成功');
}
} catch (error) {
hexo.log.error('爱发电插件:复制布局文件失败', error.message);
hexo.log.warn('爱发电插件:请手动将 node_modules/hexo-plugin-afdian/layout/afdian.ejs 文件复制到 themes/' + themeName + '/layout/ 目录下');
}
// 注册静态资源
hexo.extend.generator.register('afdian_assets', function() {
return [
{
path: 'afdian/css/afdian.css',
data: function() {
return fs.createReadStream(path.join(__dirname, 'assets/afdian.css'));
}
},
{
path: 'afdian/css/afdian-widget.css',
data: function() {
return fs.createReadStream(path.join(__dirname, 'assets/afdian-widget.css'));
}
},
{
path: 'afdian/images/default-avatar.svg',
data: function() {
return fs.createReadStream(path.join(__dirname, 'static/images/default-avatar.svg'));
}
}
];
});
// 注册路由
hexo.extend.generator.register('afdian', function(locals) {
const sponsors = getSponsors(hexo);
return {
path: 'afdian/index.html',
layout: ['afdian', 'page', 'index'],
data: {
title: hexo.config.afdian.title || '赞助名单',
sponsors: sponsors,
currentPage: 1,
totalPages: Math.ceil(sponsors.length / hexo.config.afdian.pageSize)
}
};
});
// 注册分页路由
hexo.extend.generator.register('afdian_page', function(locals) {
const sponsors = getSponsors(hexo);
const pageSize = hexo.config.afdian.pageSize;
const totalPages = Math.ceil(sponsors.length / pageSize);
const pages = [];
for (let i = 2; i <= totalPages; i++) {
pages.push({
path: `afdian/page/${i}/index.html`,
layout: ['afdian', 'page', 'index'],
data: {
title: hexo.config.afdian.title || '赞助名单',
sponsors: sponsors.slice((i - 1) * pageSize, i * pageSize),
currentPage: i,
totalPages: totalPages
}
});
}
return pages;
});
// 注册辅助函数
hexo.extend.helper.register('afdian_sponsors', function() {
return getSponsors(hexo);
});
// 注册小部件辅助函数
hexo.extend.helper.register('afdian_widget', function(options = {}) {
const config = Object.assign({}, hexo.config.afdian, options);
return `
<div class="afdian-widget">
<h3 class="afdian-widget-title">${config.title || '赞助名单'}</h3>
<p class="afdian-widget-description">${config.description || '感谢您的赞助!'}</p>
<div class="afdian-widget-list">
${renderSponsors(getSponsors(hexo).slice(0, 5), config)}
</div>
<div class="afdian-widget-footer">
<a href="/afdian/" class="afdian-widget-more">${config.moreText || '更多赞助'}</a>
<a href="${config.sponsorUrl || 'https://afdian.com/'}" class="afdian-widget-button" target="_blank">
${config.buttonText || '赞助'}
</a>
</div>
</div>
<link rel="stylesheet" href="/afdian/css/afdian-widget.css">
`;
});
// 注册统计辅助函数
hexo.extend.helper.register('afdian_stats', function() {
const sponsors = getSponsors(hexo);
const totalAmount = sponsors.reduce((sum, sponsor) => sum + sponsor.amount, 0);
return `
<div class="afdian-stats">
<div class="afdian-stats-item">
<span class="afdian-stats-number">${sponsors.length}</span>
<span class="afdian-stats-label">赞助人数</span>
</div>
<div class="afdian-stats-item">
<span class="afdian-stats-number">¥${totalAmount.toFixed(2)}</span>
<span class="afdian-stats-label">赞助总额</span>
</div>
</div>
`;
});
// 注册时间格式化辅助函数
hexo.extend.helper.register('parse_time', function(timestamp) {
return moment(timestamp * 1000).format('YYYY-MM-DD');
});
};
// 渲染赞助者列表
function renderSponsors(sponsors, config) {
if (!sponsors || sponsors.length === 0) {
return `<div class="afdian-empty">${config.emptyText || '暂无赞助'}</div>`;
}
return sponsors.map(sponsor => {
const isVip = sponsor.amount >= (config.sponsorNumber || 66);
return `
<div class="afdian-sponsor-item ${isVip ? 'highlight' : ''}">
<div class="afdian-sponsor-avatar">
<img src="${sponsor.avatar}" alt="${sponsor.name}" onerror="this.src='/afdian/images/default-avatar.svg'; this.onerror=null;">
</div>
<div class="afdian-sponsor-info">
<div class="afdian-sponsor-name">${sponsor.name}</div>
<div class="afdian-sponsor-amount ${isVip ? 'vip' : ''}">¥${sponsor.amount}</div>
<div class="afdian-sponsor-time">${sponsor.time}</div>
</div>
</div>
`;
}).join('');
}
// 获取赞助者数据
async function getSponsorsData(hexo) {
const config = hexo.config.afdian;
// 检查必要配置
if (!config.userId || !config.token) {
hexo.log.warn('爱发电插件:缺少必要的配置项 userId 或 token');
// 如果配置了使用示例数据,则返回示例数据
if (hexo.config.afdian.useExampleData) {
hexo.log.info('爱发电插件:使用示例数据');
return getExampleData();
}
// 尝试读取本地数据文件
try {
const dataDir = path.join(hexo.source_dir, '_data');
const dataFile = path.join(dataDir, 'sponsors.json');
if (fs.existsSync(dataFile)) {
hexo.log.info('爱发电插件:使用本地数据文件');
return {
timestamp: new Date(),
data: {
data: {
list: JSON.parse(fs.readFileSync(dataFile, 'utf8'))
}
}
};
}
} catch (error) {
hexo.log.error('爱发电插件:读取本地数据文件失败', error.message);
}
return { error: '配置错误' };
}
// 缓存文件路径
const cacheDir = path.join(hexo.public_dir, 'data');
const cacheFile = path.join(cacheDir, 'sponsors.json');
// 检查缓存
if (fs.existsSync(cacheFile)) {
try {
const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
const cacheTime = new Date(cacheData.timestamp);
const now = new Date();
// 如果缓存未过期,直接返回缓存数据
if ((now - cacheTime) / 1000 / 60 < config.cacheTime) {
hexo.log.debug('爱发电插件:使用缓存数据');
return cacheData;
}
} catch (error) {
hexo.log.error('爱发电插件:读取缓存文件失败', error.message);
}
}
// 准备API请求参数
const timestamp = Math.floor(Date.now() / 1000);
const params = {
user_id: config.userId,
params: JSON.stringify({}),
ts: timestamp,
sign: crypto.MD5(`${config.token}params{}ts${timestamp}user_id${config.userId}`).toString()
};
try {
// 发起API请求
hexo.log.debug('爱发电插件:正在请求API数据');
const response = await axios.post('https://afdian.com/api/open/query-sponsor', params);
// 确保缓存目录存在
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
// 写入缓存
const data = {
timestamp: new Date(),
data: response.data
};
fs.writeFileSync(cacheFile, JSON.stringify(data));
hexo.log.info('爱发电插件:成功获取赞助数据');
return data;
} catch (error) {
hexo.log.error('爱发电插件:获取赞助数据失败', error.message);
// 如果有缓存,返回过期的缓存
if (fs.existsSync(cacheFile)) {
hexo.log.warn('爱发电插件:使用过期的缓存数据');
return JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
}
// 如果配置了使用示例数据,则返回示例数据
if (hexo.config.afdian.useExampleData) {
hexo.log.info('爱发电插件:使用示例数据');
return getExampleData();
}
return { error: '获取数据失败' };
}
}
// 处理赞助者数据
function getSponsors(hexo) {
// 尝试从缓存获取数据
let cachedSponsors = hexo.locals.get('afdian_sponsors');
if (cachedSponsors) {
return cachedSponsors;
}
// 如果没有缓存,则获取数据
try {
const sponsorsData = getSponsorsData(hexo);
// 处理Promise返回的情况
if (sponsorsData instanceof Promise) {
return sponsorsData.then(processSponsorsData).catch(error => {
hexo.log.error('爱发电插件:处理赞助数据失败', error.message);
return [];
});
}
// 处理直接返回的情况
const sponsors = processSponsorsData(sponsorsData);
// 缓存处理后的数据
hexo.locals.set('afdian_sponsors', sponsors);
return sponsors;
} catch (error) {
hexo.log.error('爱发电插件:处理赞助数据失败', error.message);
return [];
}
// 处理赞助数据的辅助函数
function processSponsorsData(sponsorsData) {
if (sponsorsData.error || !sponsorsData.data || !sponsorsData.data.data || !sponsorsData.data.data.list) {
// 如果配置了使用示例数据,则返回示例数据
if (hexo.config.afdian.useExampleData) {
const exampleData = getExampleData();
return exampleData.data.data.list.map(formatSponsor);
}
// 尝试读取本地数据文件
try {
const dataDir = path.join(hexo.source_dir, '_data');
const dataFile = path.join(dataDir, 'sponsors.json');
if (fs.existsSync(dataFile)) {
return JSON.parse(fs.readFileSync(dataFile, 'utf8')).map(sponsor => {
return {
name: sponsor.name,
avatar: sponsor.avatar || '/afdian/images/default-avatar.svg',
amount: parseFloat(sponsor.amount),
time: sponsor.time
};
});
}
} catch (error) {
hexo.log.error('爱发电插件:读取本地数据文件失败', error.message);
}
return [];
}
return sponsorsData.data.data.list.map(formatSponsor);
}
// 格式化赞助者数据
function formatSponsor(sponsor) {
return {
name: sponsor.user ? sponsor.user.name : sponsor.name,
avatar: (sponsor.user ? sponsor.user.avatar : sponsor.avatar) || '/afdian/images/default-avatar.svg',
amount: parseFloat(sponsor.all_sum_amount || sponsor.amount),
time: sponsor.first_pay_time ? moment(sponsor.first_pay_time * 1000).format('YYYY-MM-DD') : sponsor.time
};
}
}
// 获取示例数据
function getExampleData() {
return {
timestamp: new Date(),
data: {
data: {
list: [
{
name: '赞助者1',
avatar: '/afdian/images/default-avatar.svg',
amount: 50,
time: '2023-05-15'
},
{
name: '赞助者2',
avatar: '/afdian/images/default-avatar.svg',
amount: 88,
time: '2023-06-20'
},
{
name: '赞助者3',
avatar: '/afdian/images/default-avatar.svg',
amount: 100,
time: '2023-07-05'
}
]
}
}
};
}