koishi-plugin-pointmintmarket
Version:
积分商城系统 - 为其他插件提供商品注册和购买功能
468 lines (460 loc) • 16.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
MarketService: () => MarketService,
apply: () => apply,
description: () => description,
inject: () => inject,
logs: () => logs,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi3 = require("koishi");
// src/database.ts
var MARKET_ITEMS_TABLE = "market_items";
var market_database = class {
static {
__name(this, "market_database");
}
ctx;
logs;
lock = [];
_itemsCache = [];
get Items() {
return this._itemsCache;
}
async updateCache() {
this._itemsCache = await this.ctx.database.select(MARKET_ITEMS_TABLE).execute();
}
// 用于确认数据库是否完成初始化
_initialized = false;
get initialized() {
return this._initialized;
}
constructor(ctx) {
this.ctx = ctx;
this.logs = logs;
this.ctx.model.extend(MARKET_ITEMS_TABLE, {
id: "integer",
name: "string",
description: "string",
price: "integer",
status: "string",
registered: "boolean",
image: "string",
tags: "json",
stock: "integer",
pluginName: "string"
}, { primary: "id" });
this._initialized = true;
}
async getNewItemId() {
await this.updateCache();
try {
if (this._itemsCache.length === 0) return 1;
return this._itemsCache[this._itemsCache.length - 1].id + 1;
} catch (error) {
this.logs.warn(`获取最新商品ID时出错: ${error.message}`);
return 1;
}
}
async getAllMarketItem() {
return this.ctx.database.select(MARKET_ITEMS_TABLE).execute();
}
async getMarketItemById(itemId) {
await this.updateCache();
const item = this._itemsCache.find((item2) => item2.id == itemId);
return item || null;
}
async addMarketItem(item) {
if (this._itemsCache.find((Items) => Items.name === item.name && Items.pluginName === item.pluginName)) {
logs.warn(`商品 ${item.name} 已存在`);
return;
}
await this.ctx.database.create(MARKET_ITEMS_TABLE, item);
this.updateCache();
return;
}
// 交换两个商品的位置,id保持原位置
async swapMarketItem(itemId1, itemId2) {
const item1 = await this.getMarketItemById(itemId1);
const item2 = await this.getMarketItemById(itemId2);
if (!item1 || !item2) {
logs.warn(`商品 ${item1} 或 ${item2} 不存在`);
return;
}
item1.id = itemId2;
item2.id = itemId1;
await this.updateMarketItem([item1, item2]);
this.updateCache();
return;
}
// 将商品信息更新到数据库中
async updateMarketItem(items) {
for (const item of items) {
const { id, ...updateData } = item;
await this.ctx.database.set(MARKET_ITEMS_TABLE, item.id, updateData);
}
this.updateCache();
return;
}
async deleteMarketItem(itemId, pluginName) {
if (pluginName) {
await this.ctx.database.remove(MARKET_ITEMS_TABLE, { pluginName });
} else {
await this.ctx.database.remove(MARKET_ITEMS_TABLE, { id: itemId });
}
this.updateCache();
return;
}
// 减少库存,需要使用内存锁
async reduceStock(itemId) {
if (this.lock.includes(itemId.toString())) {
while (this.lock.includes(itemId.toString())) {
await new Promise((resolve2) => setTimeout(resolve2, 10));
}
}
this.lock.push(itemId.toString());
const item = await this.getMarketItemById(itemId);
if (!item) {
logs.warn(`商品 ${itemId} 不存在`);
this.lock = this.lock.filter((id) => id !== itemId.toString());
return false;
}
if (item.stock === void 0 || item.stock === null || item.stock == -1) {
logs.warn(`商品 ${itemId} 没有库存或库存设为了无限`);
this.lock = this.lock.filter((id) => id !== itemId.toString());
return false;
}
if (item.stock == 0) {
logs.warn(`商品 ${itemId} 库存不足`);
this.lock = this.lock.filter((id) => id !== itemId.toString());
return false;
}
item.stock--;
await this.updateMarketItem([item]);
this.lock = this.lock.filter((id) => id !== itemId.toString());
return true;
}
};
// src/services.ts
var import_koishi = require("koishi");
var import_koishi_plugin_pointmint = require("koishi-plugin-pointmint");
var MarketService = class extends import_koishi.Service {
static {
__name(this, "MarketService");
}
static inject = ["points"];
static [import_koishi.Service.provide] = "market";
get Items() {
return this.db.Items;
}
cfg;
db;
registerLock = false;
constructor(ctx, cfg) {
super(ctx, "market");
this.cfg = cfg;
this.db = new market_database(ctx);
}
// 商品id和回调函数的映射
registeredCallbacks = /* @__PURE__ */ new Map();
// 注册商品
async registerItem(pluginName, options) {
while (!this.db.initialized) {
logs.info("等待数据库初始化");
await new Promise((resolve2) => setTimeout(resolve2, 100));
}
while (this.registerLock) {
await new Promise((resolve2) => setTimeout(resolve2, 10));
}
this.registerLock = true;
const Items = await this.db.getAllMarketItem();
const existingItem = Items.find((item) => item.name == options.name && item.pluginName == pluginName);
if (existingItem) {
existingItem.image = options.image || null;
existingItem.registered = true;
await this.db.updateMarketItem([existingItem]);
this.registeredCallbacks.set(existingItem.id, options.onPurchase);
logs.info(`插件 ${pluginName} 已更新商品 ${existingItem.name}`);
} else {
const newItemId = await this.db.getNewItemId();
const newItem = {
id: newItemId,
name: options.name,
description: options.description,
tags: options.tags,
price: options.price || 0,
stock: 0,
status: "unavailable",
registered: true,
image: options.image,
pluginName
};
this.db.addMarketItem(newItem);
this.registeredCallbacks.set(newItemId, options.onPurchase);
logs.info(`插件 ${pluginName} 已注册商品 ${newItem.name}`);
}
this.registerLock = false;
}
// 购买商品
async purchaseItem(userId, itemId, session) {
const item = await this.db.getMarketItemById(itemId);
if (!item) {
return { success: false, message: "商品不存在" };
}
if (item.stock == 0) {
return { success: false, message: "商品已售罄" };
}
const transactionId = this.ctx.points.generateTransactionId();
const callback = this.registeredCallbacks.get(itemId);
if (!callback) {
await this.ctx.points.rollback(userId, transactionId, name);
return { success: false, message: "商品回调函数未注册", item };
}
const result = await this.ctx.points.reduce(userId, transactionId, item.price, name);
if (result.code < 200 || result.code >= 300) {
return { success: false, message: result.msg, item };
}
const callbackResult = await callback(session);
if (typeof callbackResult === "boolean") {
if (!callbackResult) {
await this.ctx.points.rollback(userId, transactionId, name);
return { success: false, message: "购买失败", item };
}
} else if (typeof callbackResult === "string") {
await this.ctx.points.rollback(userId, transactionId, name);
return { success: false, message: callbackResult, item };
} else {
if (callbackResult.code !== 0) {
await this.ctx.points.rollback(userId, transactionId, name);
return { success: false, message: callbackResult.msg, item };
}
}
if (item.stock !== void 0 || item.stock !== null) {
item.stock--;
}
return { success: true, message: "购买成功", transactionId, item };
}
// 取消注册商品
async unregisterItem(itemId) {
this.registeredCallbacks.delete(itemId);
let item = await this.db.getMarketItemById(itemId);
if (!item) {
return;
}
this.db.updateMarketItem([item]);
}
// 取消注册某插件的所有商品
async unregisterItems(pluginName) {
if (this.registerLock) {
while (this.registerLock) {
await new Promise((resolve2) => setTimeout(resolve2, 100));
}
}
this.registerLock = true;
const Items = await this.db.getAllMarketItem();
const items = Items.filter((item) => item.pluginName === pluginName);
if (items.length === 0) {
this.registerLock = false;
return;
}
for (const item of items) {
this.registeredCallbacks.delete(item.id);
item.registered = false;
}
this.db.updateMarketItem(items);
logs.info(`插件 ${pluginName} 已取消注册所有商品`);
this.registerLock = false;
}
// 删除某商品
async deleteItemById(itemId) {
if (this.registerLock) {
while (this.registerLock) {
await new Promise((resolve2) => setTimeout(resolve2, 100));
}
}
this.registerLock = true;
this.registeredCallbacks.delete(itemId);
await this.db.deleteMarketItem(itemId);
this.registerLock = false;
}
};
// src/command.ts
async function registerCommands(ctx, config) {
switch (config.commandType) {
case "easy": {
const shop = ctx.command("商城").alias("shop");
shop.action(async ({ session }) => {
switch (config.marketStyle) {
case "text":
const items = ctx.market.Items;
let page = 1;
const totalPages = Math.ceil(items.length / config.marketStyle_maxItemsPerPage);
const startIndex = (page - 1) * config.marketStyle_maxItemsPerPage;
const endIndex = startIndex + config.marketStyle_maxItemsPerPage;
const pageItems = items.slice(startIndex, endIndex);
let itemsString = "";
for (const item of pageItems) {
itemsString += `ID:${item.id}
${item.name} - ${item.price}积分
${item.description}
`;
}
let marketString = config.marketStyleTextStyle;
marketString = marketString.replace("{items}", itemsString);
marketString = marketString.replace("{page}", page.toString());
marketString = marketString.replace("{totalPages}", totalPages.toString());
await session.send(marketString);
}
});
const exchange = ctx.command("兑换 <id:number>").alias("exchange <id:string>");
exchange.action(async ({ session }, id) => {
if (!session.userId) return "无法识别您的id";
const currentPoints = await ctx.points.get(session.userId);
if (currentPoints < 0) return "您还没有积分账户,快去获得一些吧~";
if (id == void 0) {
await session.send("请输入商品id");
const id2 = await session.prompt(6e4);
}
const result = await ctx.market.purchaseItem(session.userId, id, session);
if (!result.success) {
session.send(result.message);
}
});
}
}
}
__name(registerCommands, "registerCommands");
// src/console/index.ts
var import_path = require("path");
var consoleData = class {
static {
__name(this, "consoleData");
}
ctx;
db;
constructor(ctx) {
this.ctx = ctx;
this.db = new market_database(ctx);
this.init();
}
init() {
this.ctx.console.addListener("get-Items", () => this.db.getAllMarketItem());
this.ctx.console.addListener("save-Items", async (data) => {
const items = data.map((item) => {
return {
id: item.id,
name: item.name,
description: item.description,
price: item.price,
stock: item.stock,
tags: item.tags,
status: item.status
};
});
this.db.updateMarketItem(items);
});
this.ctx.console.addListener("swap-Items", (data) => {
try {
this.db.swapMarketItem(data.id1, data.id2);
return "success";
} catch (error) {
return "failed";
}
});
this.ctx.console.addListener("delete-Item", (id) => {
this.ctx.market.deleteItemById(id);
});
this.ctx.console.addEntry({
dev: (0, import_path.resolve)(__dirname, "../../client/index.ts"),
prod: (0, import_path.resolve)(__dirname, "../dist")
});
}
};
// src/config.ts
var import_koishi2 = require("koishi");
var Config = import_koishi2.Schema.intersect([
import_koishi2.Schema.object({
commandType: import_koishi2.Schema.union([
import_koishi2.Schema.const("easy").description("简单模式,使用`积分商城`和`兑换`命令"),
import_koishi2.Schema.const("interactive").disabled().description("交互模式,使用指令`积分商城`进入后通过交互选择并兑换商品")
]).default("easy").description("选择使用哪种命令模式")
}).description("基本设置"),
import_koishi2.Schema.object({
purchaseOrder: import_koishi2.Schema.union([
import_koishi2.Schema.const("points-first").description("先扣除积分,再兑换商品"),
import_koishi2.Schema.const("item-first").description("先兑换商品,再扣除积分")
]).description("购买时先操作积分还是先操作积分")
}),
import_koishi2.Schema.object({
marketStyleDefaultError: import_koishi2.Schema.string().default("未知错误").description("未知错误时的默认提示,使用`{code}`表示错误码,`{msg}`表示错误信息")
}).description("基本样式设置"),
import_koishi2.Schema.object({
marketStyle_maxItemsPerPage: import_koishi2.Schema.number().default(3).min(1).description("单页最大商品数量"),
marketStyle: import_koishi2.Schema.union([
import_koishi2.Schema.const("text").description("使用纯文本输出商品信息"),
import_koishi2.Schema.const("plugin").disabled().experimental().description("使用其他插件输出商品信息")
]).default("text").description("选择输出积分商城的商品信息的方式")
}).description("积分商城个性化设置"),
import_koishi2.Schema.union([
import_koishi2.Schema.object({
marketStyle: import_koishi2.Schema.const("text").required()
}),
import_koishi2.Schema.object({
marketStyle: import_koishi2.Schema.const("text"),
marketStyleTextStyle: import_koishi2.Schema.string().role("textarea", { rows: [4, 3] }).default("【积分商城】\n {items} \n 当前为{page}页,共{totalPages}页 \n up上一页,down下一页,回复其他退出").description("选择输出积分商城的商品信息的文本样式,使用`{items}`表示商品信息,`{page}`表示当前页数,`{totalPages}`表示总页数")
}),
import_koishi2.Schema.object({
marketStyle: import_koishi2.Schema.const("plugin"),
marketStylePlugin: import_koishi2.Schema.union([]).description("选择输出积分商城的商品信息的插件"),
marketStylePlugin_marketName: import_koishi2.Schema.string().description("商城的名称")
})
]),
import_koishi2.Schema.object({
debug: import_koishi2.Schema.boolean().default(false).description("是否启用调试日志")
}).description("调试设置")
]);
// src/index.ts
var name = "pointmintmarket";
var description = "积分商城系统 - 为其他插件提供商品注册和购买功能";
var inject = {
required: ["points", "database", "console"]
};
var logs = new import_koishi3.Logger(name);
function apply(ctx, config) {
const db = new market_database(ctx);
const cd = new consoleData(ctx);
registerCommands(ctx, config);
ctx.plugin(MarketService);
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
MarketService,
apply,
description,
inject,
logs,
name
});