npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shzlx/shop

v0.0.3

Published

HydroOJ shop module

Readme

shop

HydroOJ 硬币与兑换商店外挂(繁体中文文件)。 以 https://github.com/cqzym1985/my-hydro-plugins 为基础并新增供其他套件接入的功能

功能

  • 硬币发放与批次汇入。
  • 商品管理与兑换。
  • 支援商品 objectId(可重复绑定同一物件)。
  • 支援无限供应(num < 0)。
  • 商品 description 支援 Markdown,商城与兑换页以原生 |markdown|safe 渲染。
  • 可插拔购买逻辑(供其他套件接入)。
  • 可注册管理页扩充入口(独立页面:/shop/manage/entries)。
  • 已提供 runtime shopBridge,避免外挂间使用脆弱的相对路径静态汇入。

路由(目前启用)

  • coin_show /coin/show
  • coin_inc /coin/inc
  • coin_import /coin/import
  • coin_bill /coin/bill
  • coin_mall /coin/mall
  • coin_myrecord /coin/myrecord
  • coin_exchange /coin/exchange/:id
  • coin_record /coin/record
  • goods_add /goods/add
  • goods_manage /goods/manage
  • shop_manage_entries /shop/manage/entries
  • goods_edit /goods/:id/edit
  • uname_change /uname/change
  • domain_coin_setting /domain/coin

注:coin_gift 路由在目前版本预设未启用(程式内已注解)。

对外 API

shop/index.ts 对外提供:

  • registerGoodsPurchaseModel(modelId, model)
  • registerShopManageEntry(entry)
  • getShopManageEntries()
  • CoinModel
  • GoodsModel

如何调用原生 method 发放硬币

推荐方式:使用 CoinModel.inc(会写入发放纪录)

CoinModel.inc 会同时:

  • 新增一笔硬币帐单纪录(coin collection)
  • 调整使用者 coin_now
  • asset = 1 时,同步增加 coin_all
import { CoinModel } from '../shop';

// 例:管理员给使用者 +20 硬币
// 参数:userId, rootId, amount, text, asset, status?
await CoinModel.inc(targetUid, operatorUid, 20, '活动奖励', 1);

参数说明:

  • userId: 收款人 uid
  • rootId: 操作者 uid(谁发放)
  • amount: 正数加币,负数扣币
  • text: 帐单说明
  • asset: 1 代表会计入 coin_all0 只变动 coin_now
  • status(选填): 可用于绑定商品 ID 或其他状态码

进阶方式:直接调用 Hydro 原生 UserModel.inc

若你只想调整余额、不要写入硬币帐单,可直接使用 Hydro 原生方法:

import { UserModel } from 'hydrooj';

await UserModel.inc(targetUid, 'coin_now', 20);

注意:这种做法不会留下 coin_bill 可查的发放纪录,通常不建议拿来做正式发币流程。

另外,执行时也会提供:

  • global.Hydro.shopBridge
  • ctx.provide('shop_bridge', shopBridge)

shopBridge 内容:

  • goodsModel
  • registerGoodsPurchaseModel
  • registerShopManageEntry

建议接入方式(runtime bridge)

interface ShopBridge {
  goodsModel: {
    add: (
      name: string,
      price: number,
      num: number,
      objectId?: string,
      goodsId?: number,
      purchaseModelId?: string,
      data?: Record<string, unknown>,
      description?: string,
      redirectUrl?: string,
    ) => Promise<number | string>;
  };
  registerGoodsPurchaseModel: (
    modelId: string,
    model: {
      purchase: (
        uid: number,
        goods: any,
        amount: number,
      ) => Promise<boolean | { success: boolean; message?: string }> | (boolean | { success: boolean; message?: string });
    }
  ) => void;
  registerShopManageEntry: (entry: { key: string; title: string; href: string }) => void;
}

function getShopBridge(): ShopBridge | null {
  return (global.Hydro as any)?.shopBridge || null;
}

const shopBridge = getShopBridge();
if (shopBridge) {
  shopBridge.registerGoodsPurchaseModel('example_model', {
    async purchase(uid, goods, amount) {
      // 成功
      return true;
      // 或失败(带讯息)
      // return { success: false, message: '你已拥有此商品' };
    },
  });

  shopBridge.registerShopManageEntry({
    key: 'example_manage',
    title: '外挂管理入口',
    href: '/example/manage',
  });
}

使用 ctx.inject 的注意事项

  • 此专案整合建议使用:ctx.inject(['Shop'], ...)(大写)
  • 请保持与外挂实际注册/载入方式一致,避免 inject key 不匹配
// 建议用法
ctx.inject(['Shop'], (c) => {
  const shopBridge = (global.Hydro as any)?.shopBridge;
  if (!shopBridge) return;
  // ...
});

registerShopManageEntry 的 href 建议写法

下面提供一套可重复使用的最小模板,适合用在你自己的外挂管理页。

1) 先注册管理入口(key, title, href)

shopBridge.registerShopManageEntry({
  key: 'example_manage',
  title: 'Example 管理',
  href: '/example/manage',
});

重点:

  • key 要全域唯一,建议用与ctx.Route的相同(例如 example_manage)。
  • href 请使用固定路径,不要带动态参数,方便管理页入口稳定显示。
  • title 建议用清楚动词,例如 新增、发布、同步、设定。

2) 对应 href 的 Handler 范本

import { Context, Handler, PERM, Types, param } from 'hydrooj';

class ExampleManageHandler extends Handler {
  async get() {
    this.checkPerm(PERM.PERM_SET_PERM);
    this.response.template = 'example_manage.html';
    this.response.body = {
      page_name: 'example_manage',
      message: '',
    };
  }

  @param('name', Types.String)
  async post(domainId: string, name: string) {
    this.checkPerm(PERM.PERM_SET_PERM);

    // TODO: 在这里放你的业务逻辑

    this.response.template = 'example_manage.html';
    this.response.body = {
      page_name: 'example_manage',
      message: `已完成:${name}`,
    };
  }
}

export function applyExample(ctx: Context) {
  ctx.Route('example_manage', '/example/manage', ExampleManageHandler, PERM.PERM_SET_PERM);
}

3) 可重复使用的 HTML 模板范本

{% extends "coin_base.html" %}
{% block coin_content %}
<div class="section">
  <div class="section__header">
    <h1 class="section__title">{{ _('Example 管理') }}</h1>
  </div>
  <div class="section__body">
    {% if message %}
    <blockquote class="note typo">
      <p>{{ message }}</p>
    </blockquote>
    {% endif %}

    <form method="post">
      {{ form.form_text({
        label: '名称',
        name: 'name',
        required: true
      }) }}

      <button type="submit" class="rounded primary button">{{ _('提交') }}</button>
    </form>
  </div>
</div>
{% endblock %}

4) 命名与落地建议

  • Route name、page_name、manage entry key 建议统一同一前缀,便于维护。
  • 模板建议放在外挂自己的 templates 目录,避免和其他外挂重名。
  • 若页面是资料建立型流程,成功后可保留在原页并显示 message; 若是清单型流程,建议 redirect 到清单页。

5) 常见错误

  • href 打错(例如写成 herf)导致入口可见但无法进页。
  • Route 权限比入口预期高,造成点得进去但被拒绝。
  • page_name 未设定,导致侧栏 active 样式不正确。

商品资料栏位

GoodsModel 主要栏位:

  • _id: number 商品 ID
  • objectId?: string 物件 ID(可重复)
  • name: string 商品名称
  • description?: string 商品描述
  • descriptionFormat?: 'markdown' | 'html' 描述格式(预设 markdown
  • redirectUrl?: string 购买成功后跳转连结(支援 /pathhttp(s)://
  • price: number 商品价格
  • num: number 库存(-1 或任何 < 0 代表无限)
  • purchaseModelId?: string 购买处理器 ID
  • data?: Record<string, unknown> 扩充资料

外挂整合范例(徽章)

徽章外挂可在发布商品时:

  • name 使用 badge.title
  • description 使用 badge.content
  • purchaseModelId 使用 badge_purchase
  • redirectUrl 可设为 /badge/mybadge(购买成功后直接跳到我的徽章)

如此可让商城直接以 Markdown 显示徽章说明内容。

注意事项

  • 兑换有限库存商品时会先扣库存;若外挂处理器失败,系统会自动回补库存。
  • 无限供应商品(num < 0)不会扣库存。
  • 购买处理器可回传 false{ success: false, message } 拒绝兑换;若有 message 会直接显示给使用者。
  • 取消订单流程在目前版本已停用(coin_myrecordPOST 会回复功能已停用)。

⚠️ HTML 渲染安全提醒

只有具有足够权限的管理员或信任使用者才应该允许使用 HTML 渲染功能descriptionFormat: 'html')。

  • HTML 渲染允许使用者输入 HTML 标签,系统虽会进行安全清理(移除 <script>、事件属性等),但建议:
    1. 限制使用者权限:只给有管理权限的人士添加/编辑使用 HTML 格式的商品
    2. 定期审计:检查已发布的 HTML 商品内容,确认无不当内容
    3. 依赖清理函数:系统会自动清理危险标签,但防御原则是多层次的
  • 若没有明确需求,建议预设使用 Markdown 格式(更安全、更易维护)
  • 外挂程式(如徽章)发布的 HTML 商品已按安全规范进行内容逃脱和验证