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

kirakira-iris

v0.3.1

Published

Chinese-aware Aho-Corasick keyword moderation and queued, rate-limited OpenRouter AI review.

Readme

KIRAKIRA-Iris

以 Node.js 包分发的 KIRAKIRA 内容审核模块,支持 TypeScript、ESM 和 CommonJS,需要 Node.js 22 以上版本。

出于安全考虑,不提供屏蔽词库,您可以在以下开源存储库中自行获取。

| 存储库 | 网址 | |---|---| | Konsheng/Sensitive-lexicon | https://github.com/Konsheng/Sensitive-lexicon | | 57ing/Sensitive-word | https://github.com/57ing/Sensitive-word | | fwwdn/sensitive-stop-words | https://github.com/fwwdn/sensitive-stop-words |

作为最佳实践,您可以像 KIRAKIRA 主站一样,在一个持久化的数据库(例如 MongoDB)中托管关键词库,并允许管理员随时修改。

使用

安装:

npm install kirakira-iris

使用:

import { createIris } from 'kirakira-iris';

const keywords = ['badword1', 'badword2'];
const iris = createIris({
  keywords,
  ai: { // 可选的
    apiKey: 'your-openrouter-api-key',
    // model: '供应商/模型ID', // 切换模型时填写;省略则使用默认安全模型
  },
});

const content = 'user-content';                  // 待审核内容
const both = await iris.moderate(content);       // 同时使用关键词与 AI 审查
const keyword = iris.keywordModerate(content);   // 只使用关键词审查
const ai = await iris.aiModerate(content);       // 只使用 AI 审查

moderate 函数始终使用关键词检查内容,如果在 createIris 时配置了 AI,也会使用 AI 审查命中关键词的内容(AI 仅审查原文,不审查归一化后的内容)。关键词只要命中,最终 hit 必定为 true,AI 不会推翻关键词判定的结果。

提示

moderate /ˈmɒdəɹeɪt/ [adj.] [v.] 温和的,适中的; 缓和。在互联网用语中也表示(内容)审核。

当词库更新时:

// 放在你的词库更新回调或管理接口中,按需调用。
// newKeywords 是全量关键词而不是新增部分。
function onKeywordsUpdated(newKeywords: string[]) {
  iris.refreshKeywords(newKeywords);
}

刷新是同步操作,校验和编译成功后才切换;失败时保留原词库。传入 [] 可清空。已提交或排队的审核保留原关键词结果,之后的调用使用新词库。

常用函数

| 调用 | 返回类型 | 用途 | | --- | --- | --- | | createIris(options?: IrisOptions) | Iris | 初始化并编译词库 | | iris.refreshKeywords(keywords: readonly string[]) | void | 同步全量替换词库,[] 清空 | | iris.keywordModerate(content: string) | KeywordResult | 同步关键词匹配 | | iris.aiModerate(content: string, options?: ModerateOptions) | Promise<AIResult> | 独立 AI 审查原文 | | iris.moderate(content: string, options?: ModerateOptions) | Promise<ModerationResult> | 关键词命中后追加可选 AI 审查 | | iris.clearAIQueue() | number | 清空等待队列,返回移除数量 | | iris.getAIQueueStats() | AIQueueStats | 查询当前队列 |

常用初始化参数(词库只接受字符串数组,也接受只读数组;其余高级选项见详细说明):

interface IrisOptions {
  keywords?: readonly string[]; // 默认 [];调用者自行加载,初始化后复用
  maxInputLength?: number;      // 默认 100000,原文 content.length 的上限
  maxMatches?: number;          // 默认 10000,原文与其归一化文本一共最多可以匹配到的关键词数量
  ai?: {                       // 省略时不启用 AI
    apiKey: string;             // 必须显式传入,不读取环境变量
    model?: string;             // 默认下文的 NVIDIA 安全模型
    rateLimit?: { maxRequests: number; intervalMs: number }; // 默认最近 60000ms 内最多发出 20 次请求
    maxConcurrent?: number;     // 默认 3,同时执行的 AI 审核数量上限
    maxQueueSize?: number;      // 默认 100;0 表示不排队
    timeoutMs?: number;         // 默认 30000,仅计算实际请求耗时
    maxTokens?: number;         // 默认 512,最大输出 token 数
  };
}

数值配置为正整数,maxQueueSize 允许为 0。非法输入或配置抛出 TypeError / RangeError;超过长度或命中数上限也会抛出 RangeError,不会返回截断结果。

maxInputLength 与 JavaScript 的字符串 .length 一致:"测试" 是 2,"test" 是 4,"😀" 是 2,计量单位为 UTF-16 代码单元。maxMatches 按命中次数计数,不是去重后的词语种类:同一关键词在原文和归一化文本中各命中一次,共计 2 次。

关键词匹配与位置

同时匹配原文和归一化文本:NFKC、小写、去符号/空白/零宽字符,以及 OpenCC 繁简字形折叠。输入和词库采用同一规则,例如 危-險\n詞 可命中 危险词。字形折叠可能合并多义字,不做地域词汇翻译(如“軟體”→“软件”)。

interface KeywordResult {
  hit: boolean;                        // 任一列表非空即为 true
  normalizeString: string;              // 完整归一化输入
  matchesInSource: KeywordMatch[];      // 在原文直接命中的结果
  matchesInNormalize: KeywordMatch[];   // 在归一化文本命中的结果
}

interface KeywordMatch {
  hitWord: string; // 词库中未经归一化的关键词
  start: number;   // 所属文本中的起点
  end: number;     // 所属文本中的终点,不含该位置
}

两组列表独立保留重叠和重复词条命中,分别按各自文本的起点、终点排序;同一位置可出现在两组中,未命中的列表为 []。每条匹配只提供一组 UTF-16、左闭右开 的下标:matchesInSource 用于 content.slice(start, end),matchesInNormalize 用于 normalizeString.slice(start, end),两者的位置不能混用。归一化后为空的关键词只参与原文匹配。

词库初始化和刷新时编译为 Aho–Corasick 自动机,扫描为 O(n + z),归一化和排序另有开销。重复使用同一实例。npm run bench 可与逐词 indexOf 比较;小词库不保证 AC 更快。

AI 审查与队列

默认模型:nvidia/nemotron-3.5-content-safety:free。普通接入只需 apiKey,切换模型再提供 model。 提示词、输出格式选择和解析由 Iris 处理,返回类型保持一致。模型支持情况、高级选项及取消示例见详细说明。

interface ModerateOptions {
  signal?: AbortSignal; // 可选取消通知;例如 controller.signal 或 AbortSignal.timeout(5000)
}

interface AIResult {
  status: 'pass' | 'block' | 'drop'; // 通过 / 未通过 / 未完成审核
  comment: string;                 // 审核说明,或未审核的原因
  model: string;                   // 实际响应模型 ID;无响应时为配置值或默认值
  skipReason: 'not-configured' | 'empty-input' | 'no-keyword-hit' | 'queue-full' | 'queue-cleared' | null;
  error: AIErrorInfo | null;        // 失败详情;正常通过、未通过或主动跳过时为 null
}

interface ModerationResult {
  hit: boolean; // keywordResult.hit || aiResult.status === 'block';AI 不会推翻关键词命中
  keywordResult: KeywordResult;
  aiResult: AIResult;
}

interface AIQueueStats {
  active: number;        // 已占用执行槽的审核,包含等待限速的审核
  queued: number;        // 等待执行槽的审核数
  maxConcurrent: number;
  maxQueueSize: number;
}

pass / block 表示 AI 已完成判断。空输入、未配置 AI、moderate 未命中关键词、取消、超时、请求失败、队列满或被清空,均为 drop,不表示 AI 判定通过。skipReason 说明主动跳过或队列丢弃原因;error 包含 code、message、HTTP status(可为 null)和 retryable,供失败排查,完整类型可在编辑器中查看。

moderate 未命中时不会进入 AI 队列;aiModerate 不要求关键词命中。AI 始终只审查原文,每次最多一次请求。归一化仅用于关键词匹配。

队列按实例共享,先进先出;不同实例/进程不共享限额。满额时丢弃新审核,返回 drop / QUEUE_FULL。clearAIQueue() 同步移除所有 queued,对应 Promise 返回 drop / QUEUE_CLEARED;已有关键词结果保留,active 继续执行,限速记录不重置。空队列返回 0,之后仍可提交新审核。

intervalMs 是限速统计窗口,单位毫秒。有可用额度和空闲执行位置时立即发请求,不需要固定等待 60 秒。例如最近 60 秒已经发满 20 次,就等最早那次请求移出统计窗口后再发送;大量消息积压时,等待可能超过 60 秒。

maxConcurrent 控制同时执行多少次 AI 审核:1 表示逐个执行,3 表示最多同时执行 3 个,其余排队。已经取得执行位置、但正在等待限速额度的审核也占用这个数量。提高并发数不会提高 rateLimit 的请求额度。

signal 由调用者控制,可用于用户撤回、请求断开或给整个排队和审查过程设置期限。取消后返回 drop / ABORTED。ai.timeoutMs 仅限制实际请求耗时,超时为 drop / TIMEOUT。库不自动重试或切换付费模型。

构建与发布

npm 包名为 kirakira-iris,以公开包发布。发布时需要使用具有该包维护权限的 npm 账号登录。

npm run check       # 构建、类型检查和测试
npm pack            # 生成可安装的 tgz
npm login
npm run release     # 验证后发布 npm

也可配置 npm Trusted Publisher:GitHub 组织 KIRAKIRA-DOUGA、仓库 KIRAKIRA-Iris、工作流 publish.yml,允许直接发布;或配置仓库 Secret NPM_TOKEN。创建与包版本一致的 vX.Y.Z GitHub Release 后自动发布,Actions 手动运行需选择对应 tag。

BSD-3-Clause。