karin-plugin-kkk
Version:
Karin 的「抖音」「B 站」视频解析/动态推送插件
1,451 lines (1,450 loc) • 47.4 kB
JavaScript
import { DataSource, In, PrimaryColumn, CreateDateColumn, UpdateDateColumn, OneToMany, Entity, Column, ManyToOne, ManyToMany, JoinTable, PrimaryGeneratedColumn, LessThan } from "typeorm";
import "./vendor-BfPxWvnG.js";
import { existsSync, copyFileSync } from "node:fs";
import { join } from "node:path";
import { logger } from "node-karin";
import { karinPathBase } from "node-karin/root";
import { Root } from "../root.js";
import { C as Config, D as DynamicType } from "./main-44zR6G4D.js";
import "node-karin/axios";
import "stream/promises";
var __defProp$1 = Object.defineProperty;
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
var __decorateClass$1 = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp$1(target, key, result);
return result;
};
let Bot$1 = class Bot {
id;
createdAt;
updatedAt;
groups;
};
__decorateClass$1([
PrimaryColumn({ type: "varchar", comment: "机器人ID" })
], Bot$1.prototype, "id", 2);
__decorateClass$1([
CreateDateColumn()
], Bot$1.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], Bot$1.prototype, "updatedAt", 2);
__decorateClass$1([
OneToMany(() => Group$1, (group) => group.bot)
], Bot$1.prototype, "groups", 2);
Bot$1 = __decorateClass$1([
Entity("Bots")
], Bot$1);
let Group$1 = class Group {
id;
botId;
createdAt;
updatedAt;
bot;
bilibiliUsers;
dynamicCaches;
};
__decorateClass$1([
PrimaryColumn({ type: "varchar", comment: "群组ID" })
], Group$1.prototype, "id", 2);
__decorateClass$1([
Column({ type: "varchar", comment: "所属机器人ID" })
], Group$1.prototype, "botId", 2);
__decorateClass$1([
CreateDateColumn()
], Group$1.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], Group$1.prototype, "updatedAt", 2);
__decorateClass$1([
ManyToOne(() => Bot$1, (bot) => bot.groups)
], Group$1.prototype, "bot", 2);
__decorateClass$1([
ManyToMany(() => BilibiliUser, (user) => user.groups),
JoinTable({
name: "GroupUserSubscriptions",
joinColumn: { name: "groupId", referencedColumnName: "id" },
inverseJoinColumn: { name: "host_mid", referencedColumnName: "host_mid" }
})
], Group$1.prototype, "bilibiliUsers", 2);
__decorateClass$1([
OneToMany(() => DynamicCache, (cache) => cache.group)
], Group$1.prototype, "dynamicCaches", 2);
Group$1 = __decorateClass$1([
Entity("Groups")
], Group$1);
let BilibiliUser = class {
host_mid;
remark;
filterMode;
createdAt;
updatedAt;
groups;
dynamicCaches;
filterWords;
filterTags;
};
__decorateClass$1([
PrimaryColumn({ type: "integer", comment: "B站用户UID" })
], BilibiliUser.prototype, "host_mid", 2);
__decorateClass$1([
Column({ type: "varchar", nullable: true, comment: "B站用户昵称" })
], BilibiliUser.prototype, "remark", 2);
__decorateClass$1([
Column({
type: "varchar",
default: "blacklist",
comment: "过滤模式:黑名单或白名单"
})
], BilibiliUser.prototype, "filterMode", 2);
__decorateClass$1([
CreateDateColumn()
], BilibiliUser.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], BilibiliUser.prototype, "updatedAt", 2);
__decorateClass$1([
ManyToMany(() => Group$1, (group) => group.bilibiliUsers)
], BilibiliUser.prototype, "groups", 2);
__decorateClass$1([
OneToMany(() => DynamicCache, (cache) => cache.bilibiliUser)
], BilibiliUser.prototype, "dynamicCaches", 2);
__decorateClass$1([
OneToMany(() => FilterWord$1, (word) => word.bilibiliUser)
], BilibiliUser.prototype, "filterWords", 2);
__decorateClass$1([
OneToMany(() => FilterTag$1, (tag) => tag.bilibiliUser)
], BilibiliUser.prototype, "filterTags", 2);
BilibiliUser = __decorateClass$1([
Entity("BilibiliUsers")
], BilibiliUser);
let GroupUserSubscription$1 = class GroupUserSubscription {
groupId;
host_mid;
createdAt;
updatedAt;
};
__decorateClass$1([
PrimaryColumn({ type: "varchar", comment: "群组ID" })
], GroupUserSubscription$1.prototype, "groupId", 2);
__decorateClass$1([
PrimaryColumn({ type: "integer", comment: "B站用户UID" })
], GroupUserSubscription$1.prototype, "host_mid", 2);
__decorateClass$1([
CreateDateColumn()
], GroupUserSubscription$1.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], GroupUserSubscription$1.prototype, "updatedAt", 2);
GroupUserSubscription$1 = __decorateClass$1([
Entity("GroupUserSubscriptions")
], GroupUserSubscription$1);
let DynamicCache = class {
id;
dynamic_id;
host_mid;
groupId;
dynamic_type;
createdAt;
updatedAt;
bilibiliUser;
group;
};
__decorateClass$1([
PrimaryGeneratedColumn({ comment: "缓存ID" })
], DynamicCache.prototype, "id", 2);
__decorateClass$1([
Column({ type: "varchar", comment: "动态ID" })
], DynamicCache.prototype, "dynamic_id", 2);
__decorateClass$1([
Column({ type: "integer", comment: "B站用户UID" })
], DynamicCache.prototype, "host_mid", 2);
__decorateClass$1([
Column({ type: "varchar", comment: "群组ID" })
], DynamicCache.prototype, "groupId", 2);
__decorateClass$1([
Column({ type: "varchar", nullable: true, comment: "动态类型" })
], DynamicCache.prototype, "dynamic_type", 2);
__decorateClass$1([
CreateDateColumn()
], DynamicCache.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], DynamicCache.prototype, "updatedAt", 2);
__decorateClass$1([
ManyToOne(() => BilibiliUser, (user) => user.dynamicCaches)
], DynamicCache.prototype, "bilibiliUser", 2);
__decorateClass$1([
ManyToOne(() => Group$1, (group) => group.dynamicCaches)
], DynamicCache.prototype, "group", 2);
DynamicCache = __decorateClass$1([
Entity("DynamicCaches")
], DynamicCache);
let FilterWord$1 = class FilterWord {
id;
host_mid;
word;
createdAt;
updatedAt;
bilibiliUser;
};
__decorateClass$1([
PrimaryGeneratedColumn({ comment: "过滤词ID" })
], FilterWord$1.prototype, "id", 2);
__decorateClass$1([
Column({ type: "integer", comment: "B站用户UID" })
], FilterWord$1.prototype, "host_mid", 2);
__decorateClass$1([
Column({ type: "varchar", comment: "过滤词" })
], FilterWord$1.prototype, "word", 2);
__decorateClass$1([
CreateDateColumn()
], FilterWord$1.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], FilterWord$1.prototype, "updatedAt", 2);
__decorateClass$1([
ManyToOne(() => BilibiliUser, (user) => user.filterWords)
], FilterWord$1.prototype, "bilibiliUser", 2);
FilterWord$1 = __decorateClass$1([
Entity("FilterWords")
], FilterWord$1);
let FilterTag$1 = class FilterTag {
id;
host_mid;
tag;
createdAt;
updatedAt;
bilibiliUser;
};
__decorateClass$1([
PrimaryGeneratedColumn({ comment: "过滤标签ID" })
], FilterTag$1.prototype, "id", 2);
__decorateClass$1([
Column({ type: "integer", comment: "B站用户UID" })
], FilterTag$1.prototype, "host_mid", 2);
__decorateClass$1([
Column({ type: "varchar", comment: "过滤标签" })
], FilterTag$1.prototype, "tag", 2);
__decorateClass$1([
CreateDateColumn()
], FilterTag$1.prototype, "createdAt", 2);
__decorateClass$1([
UpdateDateColumn()
], FilterTag$1.prototype, "updatedAt", 2);
__decorateClass$1([
ManyToOne(() => BilibiliUser, (user) => user.filterTags)
], FilterTag$1.prototype, "bilibiliUser", 2);
FilterTag$1 = __decorateClass$1([
Entity("FilterTags")
], FilterTag$1);
(() => {
const dataDir = join(`${karinPathBase}/${Root.pluginName}/data`);
const oldDbPath = join(dataDir, "bilibili.db");
const backupDbPath = join(dataDir, "bilibili_sequelize_backup.db");
if (existsSync(backupDbPath)) {
logger.info("[Bilibili DB] 检测到已存在备份文件,跳过备份操作");
return;
}
if (existsSync(oldDbPath)) {
try {
copyFileSync(oldDbPath, backupDbPath);
logger.info(`[Bilibili DB] 已备份旧版数据库文件到: ${backupDbPath}`);
} catch (error) {
logger.warn(`[Bilibili DB] 备份旧版数据库文件失败: ${error}`);
}
}
})();
const AppDataSource$1 = new DataSource({
type: "sqlite",
database: join(`${karinPathBase}/${Root.pluginName}/data`, "bilibili.db"),
synchronize: true,
logging: false,
entities: [Bot$1, Group$1, BilibiliUser, GroupUserSubscription$1, DynamicCache, FilterWord$1, FilterTag$1],
migrations: [],
subscribers: []
});
class BilibiliDBBase {
botRepository;
groupRepository;
bilibiliUserRepository;
groupUserSubscriptionRepository;
dynamicCacheRepository;
filterWordRepository;
filterTagRepository;
/**
* 初始化数据库
*/
async init() {
try {
logger.debug(logger.green("--------------------------[BilibiliDB] 开始初始化数据库--------------------------"));
logger.debug("[BilibiliDB] 正在连接数据库...");
if (!AppDataSource$1.isInitialized) {
await AppDataSource$1.initialize();
logger.mark("[BilibiliDB] 数据库连接成功");
} else {
logger.mark("[BilibiliDB] 数据库连接已存在,跳过初始化");
}
this.botRepository = AppDataSource$1.getRepository(Bot$1);
this.groupRepository = AppDataSource$1.getRepository(Group$1);
this.bilibiliUserRepository = AppDataSource$1.getRepository(BilibiliUser);
this.groupUserSubscriptionRepository = AppDataSource$1.getRepository(GroupUserSubscription$1);
this.dynamicCacheRepository = AppDataSource$1.getRepository(DynamicCache);
this.filterWordRepository = AppDataSource$1.getRepository(FilterWord$1);
this.filterTagRepository = AppDataSource$1.getRepository(FilterTag$1);
logger.debug("[BilibiliDB] 数据库模型同步成功");
logger.debug("[BilibiliDB] 正在同步配置订阅...");
logger.debug("[BilibiliDB] 配置项数量:", Config.pushlist.bilibili?.length || 0);
await this.syncConfigSubscriptions(Config.pushlist.bilibili);
logger.debug("[BilibiliDB] 配置订阅同步成功");
logger.debug(logger.green("--------------------------[BilibiliDB] 初始化数据库完成--------------------------"));
} catch (error) {
logger.error("[BilibiliDB] 数据库初始化失败:", error);
throw error;
}
return this;
}
/**
* 获取或创建机器人记录
* @param botId 机器人ID
*/
async getOrCreateBot(botId) {
let bot = await this.botRepository.findOne({ where: { id: botId } });
if (!bot) {
bot = this.botRepository.create({ id: botId });
await this.botRepository.save(bot);
}
return bot;
}
/**
* 获取或创建群组记录
* @param groupId 群组ID
* @param botId 机器人ID
*/
async getOrCreateGroup(groupId, botId) {
await this.getOrCreateBot(botId);
let group = await this.groupRepository.findOne({ where: { id: groupId, botId } });
if (!group) {
group = this.groupRepository.create({ id: groupId, botId });
await this.groupRepository.save(group);
}
return group;
}
/**
* 获取或创建B站用户记录
* @param host_mid B站用户UID
* @param remark UP主昵称
*/
async getOrCreateBilibiliUser(host_mid, remark = "") {
let user = await this.bilibiliUserRepository.findOne({ where: { host_mid } });
if (!user) {
user = this.bilibiliUserRepository.create({ host_mid, remark });
await this.bilibiliUserRepository.save(user);
} else if (remark && user.remark !== remark) {
user.remark = remark;
await this.bilibiliUserRepository.save(user);
}
return user;
}
/**
* 订阅B站用户
* @param groupId 群组ID
* @param botId 机器人ID
* @param host_mid B站用户UID
* @param remark UP主昵称
*/
async subscribeBilibiliUser(groupId, botId, host_mid, remark = "") {
await this.getOrCreateGroup(groupId, botId);
await this.getOrCreateBilibiliUser(host_mid, remark);
let subscription = await this.groupUserSubscriptionRepository.findOne({
where: { groupId, host_mid }
});
if (!subscription) {
subscription = this.groupUserSubscriptionRepository.create({ groupId, host_mid });
await this.groupUserSubscriptionRepository.save(subscription);
}
return subscription;
}
/**
* 取消订阅B站用户
* @param groupId 群组ID
* @param host_mid B站用户UID
*/
async unsubscribeBilibiliUser(groupId, host_mid) {
const result = await this.groupUserSubscriptionRepository.delete({ groupId, host_mid });
await this.dynamicCacheRepository.delete({ groupId, host_mid });
return result.affected > 0;
}
/**
* 添加动态缓存
* @param dynamic_id 动态ID
* @param host_mid B站用户UID
* @param groupId 群组ID
* @param dynamic_type 动态类型
*/
async addDynamicCache(dynamic_id, host_mid, groupId, dynamic_type) {
let cache = await this.dynamicCacheRepository.findOne({
where: { dynamic_id, host_mid, groupId }
});
if (!cache) {
cache = this.dynamicCacheRepository.create({ dynamic_id, host_mid, groupId, dynamic_type });
await this.dynamicCacheRepository.save(cache);
}
return cache;
}
/**
* 检查动态是否已推送
* @param dynamic_id 动态ID
* @param host_mid B站用户UID
* @param groupId 群组ID
*/
async isDynamicPushed(dynamic_id, host_mid, groupId) {
const count = await this.dynamicCacheRepository.count({
where: { dynamic_id, host_mid, groupId }
});
return count > 0;
}
/**
* 获取机器人管理的所有群组
* @param botId 机器人ID
*/
async getBotGroups(botId) {
return await this.groupRepository.find({ where: { botId } });
}
/**
* 获取群组订阅的所有B站用户
* @param groupId 群组ID
*/
async getGroupSubscriptions(groupId) {
return await this.groupUserSubscriptionRepository.find({ where: { groupId } });
}
/**
* 获取B站用户的所有订阅群组
* @param host_mid B站用户UID
*/
async getUserSubscribedGroups(host_mid) {
const subscriptions = await this.groupUserSubscriptionRepository.find({
where: { host_mid }
});
if (subscriptions.length === 0) {
return [];
}
const groupIds = subscriptions.map((sub) => sub.groupId);
const groups = await this.groupRepository.find({
where: { id: In(groupIds) }
});
return groups;
}
/**
* 获取群组的动态缓存
* @param groupId 群组ID
* @param host_mid 可选的B站用户UID过滤
*/
async getGroupDynamicCache(groupId, host_mid) {
const where = { groupId };
if (host_mid) where.host_mid = host_mid;
return await this.dynamicCacheRepository.find({
where,
order: { createdAt: "DESC" }
});
}
/**
* 检查群组是否已订阅B站用户
* @param host_mid B站用户UID
* @param groupId 群组ID
*/
async isSubscribed(host_mid, groupId) {
const count = await this.groupUserSubscriptionRepository.count({
where: { host_mid, groupId }
});
return count > 0;
}
/**
* 批量同步配置文件中的订阅到数据库
* @param configItems 配置文件中的订阅项
*/
async syncConfigSubscriptions(configItems) {
const configSubscriptions = /* @__PURE__ */ new Map();
for (const item of configItems) {
const host_mid = item.host_mid;
const remark = item.remark ?? "";
await this.getOrCreateBilibiliUser(host_mid, remark);
for (const groupWithBot of item.group_id) {
const [groupId, botId] = groupWithBot.split(":");
if (!groupId || !botId) continue;
await this.getOrCreateGroup(groupId, botId);
if (!configSubscriptions.has(groupId)) {
configSubscriptions.set(groupId, /* @__PURE__ */ new Set());
}
configSubscriptions.get(groupId)?.add(host_mid);
const isSubscribed = await this.isSubscribed(host_mid, groupId);
if (!isSubscribed) {
await this.subscribeBilibiliUser(groupId, botId, host_mid, remark);
}
}
}
const allGroups = await this.groupRepository.find();
for (const group of allGroups) {
const groupId = group.id;
const configUps = configSubscriptions.get(groupId) ?? /* @__PURE__ */ new Set();
const dbSubscriptions = await this.getGroupSubscriptions(groupId);
for (const subscription of dbSubscriptions) {
const host_mid = subscription.host_mid;
if (!configUps.has(host_mid)) {
await this.unsubscribeBilibiliUser(groupId, host_mid);
logger.mark(`已删除群组 ${groupId} 对UP主 ${host_mid} 的订阅`);
}
}
}
const allUsers = await this.bilibiliUserRepository.find();
for (const user of allUsers) {
const host_mid = user.host_mid;
const subscribedGroups = await this.getUserSubscribedGroups(host_mid);
if (subscribedGroups.length === 0) {
await this.filterWordRepository.delete({ host_mid });
await this.filterTagRepository.delete({ host_mid });
await this.bilibiliUserRepository.delete({ host_mid });
logger.mark(`已删除UP主 ${host_mid} 的记录及相关过滤设置(不再被任何群组订阅)`);
}
}
}
/**
* 更新用户的过滤模式
* @param host_mid B站用户UID
* @param filterMode 过滤模式
*/
async updateFilterMode(host_mid, filterMode) {
const user = await this.getOrCreateBilibiliUser(host_mid);
user.filterMode = filterMode;
await this.bilibiliUserRepository.save(user);
return user;
}
/**
* 添加过滤词
* @param host_mid B站用户UID
* @param word 过滤词
*/
async addFilterWord(host_mid, word) {
await this.getOrCreateBilibiliUser(host_mid);
let filterWord = await this.filterWordRepository.findOne({ where: { host_mid, word } });
if (!filterWord) {
filterWord = this.filterWordRepository.create({ host_mid, word });
await this.filterWordRepository.save(filterWord);
}
return filterWord;
}
/**
* 删除过滤词
* @param host_mid B站用户UID
* @param word 过滤词
*/
async removeFilterWord(host_mid, word) {
const result = await this.filterWordRepository.delete({ host_mid, word });
return result.affected > 0;
}
/**
* 添加过滤标签
* @param host_mid B站用户UID
* @param tag 过滤标签
*/
async addFilterTag(host_mid, tag) {
await this.getOrCreateBilibiliUser(host_mid);
let filterTag = await this.filterTagRepository.findOne({ where: { host_mid, tag } });
if (!filterTag) {
filterTag = this.filterTagRepository.create({ host_mid, tag });
await this.filterTagRepository.save(filterTag);
}
return filterTag;
}
/**
* 删除过滤标签
* @param host_mid B站用户UID
* @param tag 过滤标签
*/
async removeFilterTag(host_mid, tag) {
const result = await this.filterTagRepository.delete({ host_mid, tag });
return result.affected > 0;
}
/**
* 获取用户的所有过滤词
* @param host_mid B站用户UID
*/
async getFilterWords(host_mid) {
const filterWords = await this.filterWordRepository.find({ where: { host_mid } });
return filterWords.map((word) => word.word);
}
/**
* 获取用户的所有过滤标签
* @param host_mid B站用户UID
*/
async getFilterTags(host_mid) {
const filterTags = await this.filterTagRepository.find({ where: { host_mid } });
return filterTags.map((tag) => tag.tag);
}
/**
* 获取用户的过滤配置
* @param host_mid B站用户UID
*/
async getFilterConfig(host_mid) {
const user = await this.getOrCreateBilibiliUser(host_mid);
const filterWords = await this.getFilterWords(host_mid);
const filterTags = await this.getFilterTags(host_mid);
return {
filterMode: user.filterMode,
filterWords,
filterTags
};
}
/**
* 从动态中提取文本内容和标签
* @param dynamicData 动态数据
* @returns 提取的文本内容和标签
*/
extractTextAndTags(dynamicData) {
let text = "";
const tags = [];
if (!dynamicData || !dynamicData.modules || !dynamicData.modules.module_dynamic) {
return { text, tags };
}
const moduleDynamic = dynamicData.modules.module_dynamic;
if (moduleDynamic.desc && moduleDynamic.desc.text) {
text += moduleDynamic.desc.text + " ";
}
if (moduleDynamic.major && moduleDynamic.major.archive && moduleDynamic.major.archive.title) {
text += moduleDynamic.major.archive.title + " ";
}
if (moduleDynamic.desc && moduleDynamic.desc.rich_text_nodes) {
for (const node of moduleDynamic.desc.rich_text_nodes) {
if (node.type !== "RICH_TEXT_NODE_TYPE_TEXT") {
tags.push(node.orig_text);
}
}
}
if (dynamicData.type === DynamicType.FORWARD && "orig" in dynamicData) {
if (dynamicData.orig.type === DynamicType.AV) {
text += dynamicData.orig.modules.module_dynamic.major.archive.title + "";
} else {
text += dynamicData.orig.modules.module_dynamic.desc.text + " ";
for (const node of dynamicData.orig.modules.module_dynamic.desc.rich_text_nodes) {
tags.push(node.orig_text);
}
}
}
return { text: text.trim(), tags };
}
/**
* 检查内容是否应该被过滤
* @param PushItem 推送项
* @param tags 额外的标签列表
*/
async shouldFilter(PushItem, extraTags = []) {
if (PushItem.Dynamic_Data.type === DynamicType.LIVE_RCMD) {
return false;
}
const { filterMode, filterWords, filterTags } = await this.getFilterConfig(PushItem.host_mid);
const { text: mainText, tags: mainTags } = this.extractTextAndTags(PushItem.Dynamic_Data);
let allTags = [...mainTags, ...extraTags];
let allText = mainText;
if (PushItem.Dynamic_Data.type === DynamicType.FORWARD && "orig" in PushItem.Dynamic_Data) {
const { text: origText, tags: origTags } = this.extractTextAndTags(PushItem.Dynamic_Data.orig);
allText += " " + origText;
allTags = [...allTags, ...origTags];
}
const hasFilterWord = filterWords.some((word) => allText.includes(word));
const hasFilterTag = filterTags.some(
(filterTag) => allTags.some((tag) => tag.includes(filterTag))
);
logger.debug(`
UP主UID:${PushItem.host_mid}
检查内容:${allText}
检查标签:${allTags.join(", ")}
命中词:${filterWords.join(", ")}
命中标签:${filterTags.join(", ")}
过滤模式:${filterMode}
是否过滤:${hasFilterWord || hasFilterTag ? logger.red(`${hasFilterWord || hasFilterTag}`) : logger.green(`${hasFilterWord || hasFilterTag}`)}
动态地址:${logger.green(`https://t.bilibili.com/${PushItem.Dynamic_Data.id_str}`)}
动态类型:${PushItem.dynamic_type}
`);
if (filterMode === "blacklist") {
if (hasFilterWord) {
return true;
}
if (hasFilterTag) {
return true;
}
return false;
} else {
if (filterWords.length === 0 && filterTags.length === 0) {
return false;
}
if (hasFilterWord || hasFilterTag) {
return false;
}
return true;
}
}
}
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
let Bot2 = class {
id;
createdAt;
updatedAt;
groups;
};
__decorateClass([
PrimaryColumn({ type: "varchar", comment: "机器人ID" })
], Bot2.prototype, "id", 2);
__decorateClass([
CreateDateColumn()
], Bot2.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], Bot2.prototype, "updatedAt", 2);
__decorateClass([
OneToMany(() => Group2, (group) => group.bot)
], Bot2.prototype, "groups", 2);
Bot2 = __decorateClass([
Entity("Bots")
], Bot2);
let Group2 = class {
id;
botId;
createdAt;
updatedAt;
bot;
subscriptions;
awemeCaches;
subscribedUsers;
};
__decorateClass([
PrimaryColumn({ type: "varchar", comment: "群组ID" })
], Group2.prototype, "id", 2);
__decorateClass([
Column({ type: "varchar", comment: "所属机器人ID" })
], Group2.prototype, "botId", 2);
__decorateClass([
CreateDateColumn()
], Group2.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], Group2.prototype, "updatedAt", 2);
__decorateClass([
ManyToOne(() => Bot2, (bot) => bot.groups)
], Group2.prototype, "bot", 2);
__decorateClass([
OneToMany(() => GroupUserSubscription2, (subscription) => subscription.group)
], Group2.prototype, "subscriptions", 2);
__decorateClass([
OneToMany(() => AwemeCache, (cache) => cache.group)
], Group2.prototype, "awemeCaches", 2);
__decorateClass([
ManyToMany(() => DouyinUser, (user) => user.subscribedGroups),
JoinTable({
name: "GroupUserSubscriptions",
joinColumn: { name: "groupId", referencedColumnName: "id" },
inverseJoinColumn: { name: "sec_uid", referencedColumnName: "sec_uid" }
})
], Group2.prototype, "subscribedUsers", 2);
Group2 = __decorateClass([
Entity("Groups")
], Group2);
let DouyinUser = class {
sec_uid;
short_id;
remark;
living;
filterMode;
createdAt;
updatedAt;
subscriptions;
awemeCaches;
filterWords;
filterTags;
subscribedGroups;
};
__decorateClass([
PrimaryColumn({ type: "varchar", comment: "抖音用户sec_uid" })
], DouyinUser.prototype, "sec_uid", 2);
__decorateClass([
Column({ type: "varchar", nullable: true, comment: "抖音号" })
], DouyinUser.prototype, "short_id", 2);
__decorateClass([
Column({ type: "varchar", nullable: true, comment: "抖音用户昵称" })
], DouyinUser.prototype, "remark", 2);
__decorateClass([
Column({ type: "boolean", default: false, comment: "是否正在直播" })
], DouyinUser.prototype, "living", 2);
__decorateClass([
Column({
type: "varchar",
default: "blacklist",
comment: "过滤模式:黑名单或白名单"
})
], DouyinUser.prototype, "filterMode", 2);
__decorateClass([
CreateDateColumn()
], DouyinUser.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], DouyinUser.prototype, "updatedAt", 2);
__decorateClass([
OneToMany(() => GroupUserSubscription2, (subscription) => subscription.douyinUser)
], DouyinUser.prototype, "subscriptions", 2);
__decorateClass([
OneToMany(() => AwemeCache, (cache) => cache.douyinUser)
], DouyinUser.prototype, "awemeCaches", 2);
__decorateClass([
OneToMany(() => FilterWord2, (filterWord) => filterWord.douyinUser)
], DouyinUser.prototype, "filterWords", 2);
__decorateClass([
OneToMany(() => FilterTag2, (filterTag) => filterTag.douyinUser)
], DouyinUser.prototype, "filterTags", 2);
__decorateClass([
ManyToMany(() => Group2, (group) => group.subscribedUsers)
], DouyinUser.prototype, "subscribedGroups", 2);
DouyinUser = __decorateClass([
Entity("DouyinUsers")
], DouyinUser);
let GroupUserSubscription2 = class {
groupId;
sec_uid;
createdAt;
updatedAt;
group;
douyinUser;
};
__decorateClass([
PrimaryColumn({ type: "varchar", comment: "群组ID" })
], GroupUserSubscription2.prototype, "groupId", 2);
__decorateClass([
PrimaryColumn({ type: "varchar", comment: "抖音用户sec_uid" })
], GroupUserSubscription2.prototype, "sec_uid", 2);
__decorateClass([
CreateDateColumn()
], GroupUserSubscription2.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], GroupUserSubscription2.prototype, "updatedAt", 2);
__decorateClass([
ManyToOne(() => Group2, (group) => group.subscriptions)
], GroupUserSubscription2.prototype, "group", 2);
__decorateClass([
ManyToOne(() => DouyinUser, (user) => user.subscriptions)
], GroupUserSubscription2.prototype, "douyinUser", 2);
GroupUserSubscription2 = __decorateClass([
Entity("GroupUserSubscriptions")
], GroupUserSubscription2);
let AwemeCache = class {
id;
aweme_id;
sec_uid;
groupId;
createdAt;
updatedAt;
douyinUser;
group;
};
__decorateClass([
PrimaryGeneratedColumn({ comment: "缓存ID" })
], AwemeCache.prototype, "id", 2);
__decorateClass([
Column({ type: "varchar", comment: "作品ID" })
], AwemeCache.prototype, "aweme_id", 2);
__decorateClass([
Column({ type: "varchar", comment: "抖音用户sec_uid" })
], AwemeCache.prototype, "sec_uid", 2);
__decorateClass([
Column({ type: "varchar", comment: "群组ID" })
], AwemeCache.prototype, "groupId", 2);
__decorateClass([
CreateDateColumn()
], AwemeCache.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], AwemeCache.prototype, "updatedAt", 2);
__decorateClass([
ManyToOne(() => DouyinUser, (user) => user.awemeCaches)
], AwemeCache.prototype, "douyinUser", 2);
__decorateClass([
ManyToOne(() => Group2, (group) => group.awemeCaches)
], AwemeCache.prototype, "group", 2);
AwemeCache = __decorateClass([
Entity("AwemeCaches")
], AwemeCache);
let FilterWord2 = class {
id;
sec_uid;
word;
createdAt;
updatedAt;
douyinUser;
};
__decorateClass([
PrimaryGeneratedColumn({ comment: "过滤词ID" })
], FilterWord2.prototype, "id", 2);
__decorateClass([
Column({ type: "varchar", comment: "抖音用户sec_uid" })
], FilterWord2.prototype, "sec_uid", 2);
__decorateClass([
Column({ type: "varchar", comment: "过滤词" })
], FilterWord2.prototype, "word", 2);
__decorateClass([
CreateDateColumn()
], FilterWord2.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], FilterWord2.prototype, "updatedAt", 2);
__decorateClass([
ManyToOne(() => DouyinUser, (user) => user.filterWords)
], FilterWord2.prototype, "douyinUser", 2);
FilterWord2 = __decorateClass([
Entity("FilterWords")
], FilterWord2);
let FilterTag2 = class {
id;
sec_uid;
tag;
createdAt;
updatedAt;
douyinUser;
};
__decorateClass([
PrimaryGeneratedColumn({ comment: "过滤标签ID" })
], FilterTag2.prototype, "id", 2);
__decorateClass([
Column({ type: "varchar", comment: "抖音用户sec_uid" })
], FilterTag2.prototype, "sec_uid", 2);
__decorateClass([
Column({ type: "varchar", comment: "过滤标签" })
], FilterTag2.prototype, "tag", 2);
__decorateClass([
CreateDateColumn()
], FilterTag2.prototype, "createdAt", 2);
__decorateClass([
UpdateDateColumn()
], FilterTag2.prototype, "updatedAt", 2);
__decorateClass([
ManyToOne(() => DouyinUser, (user) => user.filterTags)
], FilterTag2.prototype, "douyinUser", 2);
FilterTag2 = __decorateClass([
Entity("FilterTags")
], FilterTag2);
(() => {
const dataDir = join(`${karinPathBase}/${Root.pluginName}/data`);
const oldDbPath = join(dataDir, "douyin.db");
const backupDbPath = join(dataDir, "douyin_sequelize_backup.db");
if (existsSync(backupDbPath)) {
logger.info("[Douyin DB] 检测到已存在备份文件,跳过备份操作");
return;
}
if (existsSync(oldDbPath)) {
try {
copyFileSync(oldDbPath, backupDbPath);
logger.info(`[Douyin DB] 已备份旧版数据库文件到: ${backupDbPath}`);
} catch (error) {
logger.warn(`[Douyin DB] 备份旧版数据库文件失败: ${error}`);
}
}
})();
const AppDataSource = new DataSource({
type: "sqlite",
database: join(`${karinPathBase}/${Root.pluginName}/data`, "douyin.db"),
synchronize: true,
logging: false,
entities: [Bot2, Group2, DouyinUser, GroupUserSubscription2, AwemeCache, FilterWord2, FilterTag2],
extra: {
// SQLite 连接池配置
max: 5,
min: 0,
acquireTimeoutMillis: 3e4,
idleTimeoutMillis: 1e4
}
});
class DouyinDBBase {
botRepository;
groupRepository;
douyinUserRepository;
groupUserSubscriptionRepository;
awemeCacheRepository;
filterWordRepository;
filterTagRepository;
/**
* 初始化数据库
*/
async init() {
try {
logger.debug(logger.green("--------------------------[DouyinDB] 开始初始化数据库--------------------------"));
logger.debug("[DouyinDB] 正在连接数据库...");
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
logger.mark("[DouyinDB] 数据库连接成功");
} else {
logger.mark("[DouyinDB] 数据库连接已存在,跳过初始化");
}
this.botRepository = AppDataSource.getRepository(Bot2);
this.groupRepository = AppDataSource.getRepository(Group2);
this.douyinUserRepository = AppDataSource.getRepository(DouyinUser);
this.groupUserSubscriptionRepository = AppDataSource.getRepository(GroupUserSubscription2);
this.awemeCacheRepository = AppDataSource.getRepository(AwemeCache);
this.filterWordRepository = AppDataSource.getRepository(FilterWord2);
this.filterTagRepository = AppDataSource.getRepository(FilterTag2);
logger.debug("[DouyinDB] 数据库模型同步成功");
logger.debug("[DouyinDB] 正在同步配置订阅...");
logger.debug("[DouyinDB] 配置项数量:", Config.pushlist.douyin?.length || 0);
await this.syncConfigSubscriptions(Config.pushlist.douyin);
logger.debug("[DouyinDB] 配置订阅同步成功");
logger.debug(logger.green("--------------------------[DouyinDB] 初始化数据库完成--------------------------"));
} catch (error) {
logger.error("[DouyinDB] 数据库初始化失败:", error);
throw error;
}
return this;
}
/**
* 获取或创建机器人记录
* @param botId 机器人ID
*/
async getOrCreateBot(botId) {
let bot = await this.botRepository.findOne({ where: { id: botId } });
if (!bot) {
bot = this.botRepository.create({ id: botId });
await this.botRepository.save(bot);
}
return bot;
}
/**
* 获取或创建群组记录
* @param groupId 群组ID
* @param botId 机器人ID
*/
async getOrCreateGroup(groupId, botId) {
await this.getOrCreateBot(botId);
let group = await this.groupRepository.findOne({ where: { id: groupId, botId } });
if (!group) {
group = this.groupRepository.create({ id: groupId, botId });
await this.groupRepository.save(group);
}
return group;
}
/**
* 获取或创建抖音用户记录
* @param sec_uid 抖音用户sec_uid
* @param short_id 抖音号
* @param remark 用户昵称
*/
async getOrCreateDouyinUser(sec_uid, short_id = "", remark = "") {
let user = await this.douyinUserRepository.findOne({ where: { sec_uid } });
if (!user) {
user = this.douyinUserRepository.create({ sec_uid, short_id, remark });
await this.douyinUserRepository.save(user);
} else {
let needUpdate = false;
if (remark && user.remark !== remark) {
user.remark = remark;
needUpdate = true;
}
if (short_id && user.short_id !== short_id) {
user.short_id = short_id;
needUpdate = true;
}
if (needUpdate) {
await this.douyinUserRepository.save(user);
}
}
return user;
}
/**
* 订阅抖音用户
* @param groupId 群组ID
* @param botId 机器人ID
* @param sec_uid 抖音用户sec_uid
* @param short_id 抖音号
* @param remark 用户昵称
*/
async subscribeDouyinUser(groupId, botId, sec_uid, short_id = "", remark = "") {
await this.getOrCreateGroup(groupId, botId);
await this.getOrCreateDouyinUser(sec_uid, short_id, remark);
let subscription = await this.groupUserSubscriptionRepository.findOne({
where: { groupId, sec_uid }
});
if (!subscription) {
subscription = this.groupUserSubscriptionRepository.create({ groupId, sec_uid });
await this.groupUserSubscriptionRepository.save(subscription);
}
return subscription;
}
/**
* 取消订阅抖音用户
* @param groupId 群组ID
* @param sec_uid 抖音用户sec_uid
*/
async unsubscribeDouyinUser(groupId, sec_uid) {
const result = await this.groupUserSubscriptionRepository.delete({ groupId, sec_uid });
await this.awemeCacheRepository.delete({ groupId, sec_uid });
return result.affected > 0;
}
/**
* 添加作品缓存
* @param aweme_id 作品ID
* @param sec_uid 抖音用户sec_uid
* @param groupId 群组ID
*/
async addAwemeCache(aweme_id, sec_uid, groupId) {
let cache = await this.awemeCacheRepository.findOne({
where: { aweme_id, sec_uid, groupId }
});
if (!cache) {
cache = this.awemeCacheRepository.create({ aweme_id, sec_uid, groupId });
await this.awemeCacheRepository.save(cache);
}
return cache;
}
/**
* 检查作品是否已推送
* @param aweme_id 作品ID
* @param sec_uid 抖音用户sec_uid
* @param groupId 群组ID
*/
async isAwemePushed(aweme_id, sec_uid, groupId) {
const count = await this.awemeCacheRepository.count({
where: { aweme_id, sec_uid, groupId }
});
return count > 0;
}
/**
* 获取机器人管理的所有群组
* @param botId 机器人ID
*/
async getBotGroups(botId) {
return await this.groupRepository.find({ where: { botId } });
}
/**
* 获取群组订阅的所有抖音用户
* @param groupId 群组ID
*/
async getGroupSubscriptions(groupId) {
return await this.groupUserSubscriptionRepository.find({
where: { groupId },
relations: ["douyinUser"]
});
}
/**
* 获取抖音用户的所有订阅群组
* @param sec_uid 抖音用户sec_uid
*/
async getUserSubscribedGroups(sec_uid) {
const subscriptions = await this.groupUserSubscriptionRepository.find({
where: { sec_uid }
});
if (subscriptions.length === 0) {
return [];
}
const groupIds = subscriptions.map((sub) => sub.groupId);
const groups = await this.groupRepository.find({
where: { id: In(groupIds) }
});
return groups;
}
/**
* 检查群组是否已订阅抖音用户
* @param sec_uid 抖音用户sec_uid
* @param groupId 群组ID
*/
async isSubscribed(sec_uid, groupId) {
const count = await this.groupUserSubscriptionRepository.count({
where: { sec_uid, groupId }
});
return count > 0;
}
/**
* 获取抖音用户信息
* @param sec_uid 抖音用户sec_uid
* @returns 返回用户信息,如果不存在则返回null
*/
async getDouyinUser(sec_uid) {
return await this.douyinUserRepository.findOne({ where: { sec_uid } });
}
/**
* 更新用户直播状态
* @param sec_uid 抖音用户sec_uid
* @param living 是否正在直播
*/
async updateLiveStatus(sec_uid, living) {
const user = await this.douyinUserRepository.findOne({ where: { sec_uid } });
if (!user) return false;
user.living = living;
await this.douyinUserRepository.save(user);
return true;
}
/**
* 获取用户直播状态
* @param sec_uid 抖音用户sec_uid
*/
async getLiveStatus(sec_uid) {
const user = await this.douyinUserRepository.findOne({ where: { sec_uid } });
if (!user) return { living: false };
return { living: user.living };
}
/**
* 批量同步配置文件中的订阅到数据库
* @param configItems 配置文件中的订阅项
*/
async syncConfigSubscriptions(configItems) {
const configSubscriptions = /* @__PURE__ */ new Map();
for (const item of configItems) {
const sec_uid = item.sec_uid;
const short_id = item.short_id ?? "";
const remark = item.remark ?? "";
await this.getOrCreateDouyinUser(sec_uid, short_id, remark);
for (const groupWithBot of item.group_id) {
const [groupId, botId] = groupWithBot.split(":");
if (!groupId || !botId) continue;
await this.getOrCreateGroup(groupId, botId);
if (!configSubscriptions.has(groupId)) {
configSubscriptions.set(groupId, /* @__PURE__ */ new Set());
}
configSubscriptions.get(groupId)?.add(sec_uid);
const isSubscribed = await this.isSubscribed(sec_uid, groupId);
if (!isSubscribed) {
await this.subscribeDouyinUser(groupId, botId, sec_uid, short_id, remark);
}
}
}
const allGroups = await this.groupRepository.find();
for (const group of allGroups) {
const groupId = group.id;
const configUsers = configSubscriptions.get(groupId) ?? /* @__PURE__ */ new Set();
const dbSubscriptions = await this.getGroupSubscriptions(groupId);
for (const subscription of dbSubscriptions) {
const sec_uid = subscription.sec_uid;
if (!configUsers.has(sec_uid)) {
await this.unsubscribeDouyinUser(groupId, sec_uid);
logger.mark(`已删除群组 ${groupId} 对抖音用户 ${sec_uid} 的订阅`);
}
}
}
const allUsers = await this.douyinUserRepository.find();
for (const user of allUsers) {
const sec_uid = user.sec_uid;
const subscribedGroups = await this.getUserSubscribedGroups(sec_uid);
if (subscribedGroups.length === 0) {
await this.filterWordRepository.delete({ sec_uid });
await this.filterTagRepository.delete({ sec_uid });
await this.douyinUserRepository.delete({ sec_uid });
logger.mark(`已删除抖音用户 ${sec_uid} 的记录及相关过滤设置(不再被任何群组订阅)`);
}
}
}
/**
* 通过ID获取群组信息
* @param groupId 群组ID
*/
async getGroupById(groupId) {
return await this.groupRepository.findOne({ where: { id: groupId } });
}
/**
* 更新用户的过滤模式
* @param sec_uid 抖音用户sec_uid
* @param filterMode 过滤模式
*/
async updateFilterMode(sec_uid, filterMode) {
const user = await this.getOrCreateDouyinUser(sec_uid);
user.filterMode = filterMode;
await this.douyinUserRepository.save(user);
return user;
}
/**
* 添加过滤词
* @param sec_uid 抖音用户sec_uid
* @param word 过滤词
*/
async addFilterWord(sec_uid, word) {
await this.getOrCreateDouyinUser(sec_uid);
let filterWord = await this.filterWordRepository.findOne({
where: { sec_uid, word }
});
if (!filterWord) {
filterWord = this.filterWordRepository.create({ sec_uid, word });
await this.filterWordRepository.save(filterWord);
}
return filterWord;
}
/**
* 删除过滤词
* @param sec_uid 抖音用户sec_uid
* @param word 过滤词
*/
async removeFilterWord(sec_uid, word) {
const result = await this.filterWordRepository.delete({ sec_uid, word });
return result.affected > 0;
}
/**
* 添加过滤标签
* @param sec_uid 抖音用户sec_uid
* @param tag 过滤标签
*/
async addFilterTag(sec_uid, tag) {
await this.getOrCreateDouyinUser(sec_uid);
let filterTag = await this.filterTagRepository.findOne({
where: { sec_uid, tag }
});
if (!filterTag) {
filterTag = this.filterTagRepository.create({ sec_uid, tag });
await this.filterTagRepository.save(filterTag);
}
return filterTag;
}
/**
* 删除过滤标签
* @param sec_uid 抖音用户sec_uid
* @param tag 过滤标签
*/
async removeFilterTag(sec_uid, tag) {
const result = await this.filterTagRepository.delete({ sec_uid, tag });
return result.affected > 0;
}
/**
* 获取用户的所有过滤词
* @param sec_uid 抖音用户sec_uid
*/
async getFilterWords(sec_uid) {
const filterWords = await this.filterWordRepository.find({ where: { sec_uid } });
return filterWords.map((word) => word.word);
}
/**
* 获取用户的所有过滤标签
* @param sec_uid 抖音用户sec_uid
*/
async getFilterTags(sec_uid) {
const filterTags = await this.filterTagRepository.find({ where: { sec_uid } });
return filterTags.map((tag) => tag.tag);
}
/**
* 获取用户的过滤配置
* @param sec_uid 抖音用户sec_uid
*/
async getFilterConfig(sec_uid) {
const user = await this.getOrCreateDouyinUser(sec_uid);
const filterWords = await this.getFilterWords(sec_uid);
const filterTags = await this.getFilterTags(sec_uid);
return {
filterMode: user.filterMode,
filterWords,
filterTags
};
}
/**
* 检查内容是否应该被过滤
* @param PushItem 推送项
* @param tags 标签列表
*/
async shouldFilter(PushItem, tags = []) {
const sec_uid = PushItem.sec_uid;
if (!sec_uid) {
logger.warn(`推送项缺少 sec_uid 参数: ${JSON.stringify(PushItem)}`);
return false;
}
const { filterMode, filterWords, filterTags } = await this.getFilterConfig(sec_uid);
const desc = PushItem.Detail_Data.desc ?? "";
const hasFilterWord = filterWords.some((word) => desc.includes(word));
const hasFilterTag = filterTags.some(
(filterTag) => tags.some((tag) => tag === filterTag)
);
logger.debug(`
作者:${PushItem.remark}
检查内容:${desc}
命中词:${filterWords.join(", ")}
命中标签:${filterTags.join(", ")}
过滤模式:${filterMode}
是否过滤:${hasFilterWord || hasFilterTag ? logger.red(`${hasFilterWord || hasFilterTag}`) : logger.green(`${hasFilterWord || hasFilterTag}`)}
作品地址:${logger.green(`https://www.douyin.com/video/${PushItem.Detail_Data.aweme_id}`)}
`);
if (filterMode === "blacklist") {
return hasFilterWord || hasFilterTag;
} else {
if (filterWords.length === 0 && filterTags.length === 0) {
return false;
}
return !(hasFilterWord || hasFilterTag);
}
}
}
let douyinDB = null;
let douyinInitializing = false;
let bilibiliDB = null;
let bilibiliInitializing = false;
const getDouyinDB = async () => {
if (douyinDB) {
return douyinDB;
}
if (douyinInitializing) {
while (douyinInitializing) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
return douyinDB;
}
douyinInitializing = true;
try {
douyinDB = await new DouyinDBBase().init();
return douyinDB;
} finally {
douyinInitializing = false;
}
};
const getBilibiliDB = async () => {
if (bilibiliDB) {
return bilibiliDB;
}
if (bilibiliInitializing) {
while (bilibiliInitializing) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
return bilibiliDB;
}
bilibiliInitializing = true;
try {
bilibiliDB = await new BilibiliDBBase().init();
return bilibiliDB;
} finally {
bilibiliInitializing = false;
}
};
const douyinDBInstance = await getDouyinDB();
const bilibiliDBInstance = await getBilibiliDB();
const cleanOldDynamicCache = async (platform, days = 7) => {
const cutoffDate = /* @__PURE__ */ new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
let result;
if (platform === "douyin") {
const db = await getDouyinDB();
result = await db["awemeCacheRepository"].delete({
createdAt: LessThan(cutoffDate)
});
} else {
const db = await getBilibiliDB();
result = await db["dynamicCacheRepository"].delete({
createdAt: LessThan(cutoffDate)
});
}
return result.affected ?? 0;
};
export {
getBilibiliDB as a,
bilibiliDBInstance as b,
cleanOldDynamicCache as c,
douyinDBInstance as d,
getDouyinDB as g
};