New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

koishi-plugin-common

Package Overview
Dependencies
Maintainers
1
Versions
141
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

koishi-plugin-common - npm Package Compare versions

Comparing version 3.0.3 to 4.0.0-alpha.0

651

dist/index.js

@@ -1,35 +0,624 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
var __commonJS = (callback, module2) => () => {
if (!module2) {
module2 = {exports: {}};
callback(module2.exports, module2);
}
return module2.exports;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
var __export = (target, all) => {
__markAsModule(target);
for (var name2 in all)
__defProp(target, name2, {get: all[name2], enumerable: true});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.apply = exports.name = void 0;
const repeater_1 = __importDefault(require("./repeater"));
const handler_1 = __importDefault(require("./handler"));
const sender_1 = __importDefault(require("./sender"));
__exportStar(require("./admin"), exports);
__exportStar(require("./info"), exports);
__exportStar(require("./repeater"), exports);
exports.name = 'common';
function apply(ctx, config = {}) {
ctx.plugin(handler_1.default, config);
ctx.plugin(repeater_1.default, config);
ctx.plugin(sender_1.default, config);
ctx.plugin(require('./admin'));
ctx.plugin(require('./info'));
if (config.debug) {
ctx.plugin(require('./debug'), config.debug);
var __exportStar = (target, module2, desc) => {
__markAsModule(target);
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
}
return target;
};
var __toModule = (module2) => {
if (module2 && module2.__esModule)
return module2;
return __exportStar(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", {value: module2, enumerable: true}), module2);
};
// packages/plugin-common/src/admin.ts
var require_admin = __commonJS((exports2) => {
__export(exports2, {
apply: () => apply5
});
const koishi_utils3 = __toModule(require("koishi-utils"));
const koishi_core2 = __toModule(require("koishi-core"));
function flagAction(map, {target, options}, ...flags) {
if (options.set || options.unset) {
const notFound = koishi_utils3.difference(flags, koishi_utils3.enumKeys(map));
if (notFound.length)
return `未找到标记 ${notFound.join(", ")}。`;
for (const name2 of flags) {
options.set ? target.flag |= map[name2] : target.flag &= ~map[name2];
}
return;
}
if (options.list) {
return `全部标记为:${koishi_utils3.enumKeys(map).join(", ")}。`;
}
let flag = target.flag;
const keys = [];
while (flag) {
const value = 2 ** Math.floor(Math.log2(flag));
flag -= value;
keys.unshift(map[value]);
}
if (!keys.length)
return "未设置任何标记。";
return `当前的标记为:${keys.join(", ")}。`;
}
koishi_core2.Command.prototype.adminUser = function(callback) {
const command = this.userFields(["authority"]).option("user", "-u [user] 指定目标用户", {authority: 3});
command._action = async (argv) => {
const {options, session, args} = argv;
const fields = koishi_core2.Command.collect(argv, "user");
let target;
if (options.user) {
const qq = koishi_core2.getTargetId(options.user);
if (!qq)
return "请指定正确的目标。";
const {database} = session.$app;
const data = await database.getUser(session.kind, "" + qq, [...fields]);
if (!data)
return "未找到指定的用户。";
if ("" + qq === session.userId) {
target = await session.$observeUser(fields);
} else if (session.$user.authority <= data.authority) {
return "权限不足。";
} else {
target = koishi_utils3.observe(data, (diff) => database.setUser(session.kind, "" + qq, diff), `user ${qq}`);
}
} else {
target = await session.$observeUser(fields);
}
const diffKeys = Object.keys(target._diff);
const result = await callback({...argv, target}, ...args);
if (typeof result === "string")
return result;
if (!koishi_utils3.difference(Object.keys(target._diff), diffKeys).length)
return "用户数据未改动。";
await target._update();
return "用户数据已修改。";
};
return command;
};
koishi_core2.Command.prototype.adminGruop = function(callback) {
const command = this.userFields(["authority"]).option("group", "-g [group] 指定目标群", {authority: 3});
command._action = async (argv) => {
const {options, session, args} = argv;
const fields = koishi_core2.Command.collect(argv, "group");
let target;
if (options.group) {
const {database} = session.$app;
if (!koishi_utils3.isInteger(options.group) || options.group <= 0)
return "请指定正确的目标。";
const data = await session.$getGroup(options.group, [...fields]);
if (!data)
return "未找到指定的群。";
target = koishi_utils3.observe(data, (diff) => database.setGroup(session.kind, options.group, diff), `group ${options.group}`);
} else if (session.subType === "group") {
target = await session.$observeGroup(fields);
} else {
return "当前不在群上下文中,请使用 -g 参数指定目标群。";
}
const result = await callback({...argv, target}, ...args);
if (typeof result === "string")
return result;
if (!Object.keys(target._diff).length)
return "群数据未改动。";
await target._update();
return "群数据已修改。";
};
return command;
};
function apply5(ctx) {
ctx.command("user", "用户管理", {authority: 3});
ctx.command("group", "群管理", {authority: 3});
const tokens = {};
ctx.private().command("user.bind", "绑定到账号", {authority: 0}).action(({session}) => {
const token = koishi_utils3.Random.uuid();
const data = tokens[token] = [session.kind, session.userId];
setTimeout(() => {
if (tokens[token] === data)
delete tokens[token];
}, 5 * koishi_utils3.Time.minute);
return [
"请在 5 分钟内使用你的账号在已绑定的平台内向四季酱私聊发送以下文本:",
token,
"注意:每个账号只能绑定到每个平台一次,此操作将会抹去你当前平台上的数据,请谨慎操作!"
].join("\n");
});
ctx.prependMiddleware(async (session, next) => {
if (session.subType !== "private")
return next();
const data = tokens[session.message];
if (!data)
return next();
const user = await session.$observeUser(["authority", data[0]]);
if (!user.authority)
return next();
if (user[data[0]])
return session.$send("账号绑定失败:你已经绑定过该平台。");
user[data[0]] = data[1];
await user._update();
return session.$send("账号绑定成功!");
});
ctx.command("user.auth <value>", "权限信息", {authority: 4}).adminUser(({session, target}, value) => {
const authority = Number(value);
if (!koishi_utils3.isInteger(authority) || authority < 0)
return "参数错误。";
if (authority >= session.$user.authority)
return "权限不足。";
target.authority = authority;
});
ctx.command("user.flag [-s|-S] [...flags]", "标记信息", {authority: 3}).userFields(["flag"]).option("list", "-l 标记列表").option("set", "-s 添加标记", {authority: 4}).option("unset", "-S 删除标记", {authority: 4}).adminUser(flagAction.bind(null, koishi_core2.User.Flag));
ctx.command("usage [key]", "调用次数信息").alias("usages").userFields(["usage"]).option("set", "-s 设置调用次数", {authority: 4}).option("clear", "-c 清空调用次数", {authority: 4}).adminUser(({target, options}, name2, value) => {
if (options.clear) {
name2 ? delete target.usage[name2] : target.usage = {};
return;
}
if (options.set) {
if (value === void 0)
return "参数不足。";
const count = +value;
if (!koishi_utils3.isInteger(count) || count < 0)
return "参数错误。";
target.usage[name2] = count;
return;
}
if (name2)
return `今日 ${name2} 功能的调用次数为:${target.usage[name2] || 0}`;
const output = [];
for (const name3 of Object.keys(target.usage).sort()) {
if (name3.startsWith("$"))
continue;
output.push(`${name3}:${target.usage[name3]}`);
}
if (!output.length)
return "今日没有调用过消耗次数的功能。";
output.unshift("今日各功能的调用次数为:");
return output.join("\n");
});
ctx.command("timer [key]", "定时器信息").alias("timers").userFields(["timers"]).option("set", "-s 设置定时器", {authority: 4}).option("clear", "-c 清空定时器", {authority: 4}).adminUser(({target, options}, name2, value) => {
if (options.clear) {
name2 ? delete target.timers[name2] : target.timers = {};
return;
}
if (options.set) {
if (value === void 0)
return "参数不足。";
const timestamp = +koishi_utils3.Time.parseDate(value);
if (!timestamp)
return "请输入合法的时间。";
target.timers[name2] = timestamp;
return;
}
const now = Date.now();
if (name2) {
const delta = target.timers[name2] - now;
if (delta > 0)
return `定时器 ${name2} 的生效时间为:剩余 ${koishi_utils3.Time.formatTime(delta)}`;
return `定时器 ${name2} 当前并未生效。`;
}
const output = [];
for (const name3 of Object.keys(target.timers).sort()) {
if (name3.startsWith("$"))
continue;
output.push(`${name3}:剩余 ${koishi_utils3.Time.formatTime(target.timers[name3] - now)}`);
}
if (!output.length)
return "当前没有生效的定时器。";
output.unshift("各定时器的生效时间为:");
return output.join("\n");
});
ctx.command("group.assign [bot]", "受理者账号", {authority: 4}).groupFields(["assignee"]).adminGruop(({session, target}, value) => {
const assignee = value ? "" + koishi_core2.getTargetId(value) : session.selfId;
if (!assignee)
return "参数错误。";
target.assignee = assignee;
});
ctx.command("group.flag [-s|-S] [...flags]", "标记信息", {authority: 3}).groupFields(["flag"]).option("list", "-l 标记列表").option("set", "-s 添加标记", {authority: 4}).option("unset", "-S 删除标记", {authority: 4}).adminGruop(flagAction.bind(null, koishi_core2.Group.Flag));
}
});
// packages/plugin-common/src/info.ts
var require_info = __commonJS((exports2) => {
__export(exports2, {
apply: () => apply5,
registerUserInfo: () => registerUserInfo
});
const koishi_core2 = __toModule(require("koishi-core"));
const infoFields = new Set(["authority"]);
const infoList = [];
function registerUserInfo(callback, fields = [], order = 0) {
const index = infoList.findIndex((a) => a.order > order);
if (index >= 0) {
infoList.splice(index, 0, {order, callback});
} else {
infoList.push({order, callback});
}
for (const field of fields) {
infoFields.add(field);
}
}
function apply5(ctx) {
ctx.command("info", "查看用户信息", {authority: 0}).alias("profile").shortcut("我的信息").userFields(["name"]).before((session) => !session.$app.database).option("user", "-u [target] 指定目标", {authority: 3}).action(async ({session, options}) => {
let user;
const output = [];
if (options.user) {
const id = "" + koishi_core2.getTargetId(options.user);
if (!id)
return "未找到用户。";
user = await session.$getUser(id, Array.from(infoFields));
if (!user)
return "未找到用户。";
if (!user.name) {
output.push(`${id} 的权限为 ${user.authority} 级。`);
} else {
output.push(`${user.name} (${id}) 的权限为 ${user.authority} 级。`);
}
} else {
user = await session.$observeUser(infoFields);
output.push(`${session.$username},您的权限为 ${user.authority} 级。`);
}
for (const {callback} of infoList) {
const result = callback(user);
if (result)
output.push(result);
}
return output.join("\n");
});
}
});
// packages/plugin-common/src/debug.ts
var require_debug = __commonJS((exports2) => {
__export(exports2, {
apply: () => apply5
});
const koishi_utils3 = __toModule(require("koishi-utils"));
const cqTypes = {
face: "表情",
record: "语音",
video: "短视频",
image: "图片",
music: "音乐",
reply: "回复",
forward: "合并转发",
dice: "掷骰子",
rps: "猜拳",
poke: "戳一戳",
json: "JSON",
xml: "XML"
};
function apply5(ctx, config = {}) {
const {
refreshUserName = koishi_utils3.Time.hour,
refreshGroupName = koishi_utils3.Time.hour,
includeUsers = [],
includeGroups = [],
showUserId,
showGroupId
} = config;
const logger = new koishi_utils3.Logger("message", true);
koishi_utils3.Logger.levels.message = 3;
const groupMap = {};
async function getGroupName(session) {
if (session.subType === "private")
return "私聊";
const {groupId: id, $bot} = session;
const timestamp = Date.now();
if (!groupMap[id] || timestamp - groupMap[id][1] >= refreshGroupName) {
const promise = $bot.getGroup(id).then((d) => d.name, () => "" + id);
groupMap[id] = [promise, timestamp];
}
let output = await groupMap[id][0];
if (showGroupId && output !== "" + id) {
output += ` (${id})`;
}
return output;
}
const userMap = {};
function getSenderName({anonymous, sender: sender2, userId}) {
return anonymous ? anonymous.name + (showUserId ? ` (${anonymous.id})` : "") : (userMap[userId] = [sender2.nickname, Date.now()])[0] + (showUserId ? ` (${userId})` : "");
}
async function formatMessage(session) {
const codes = koishi_utils3.CQCode.parseAll(session.message);
let output = "";
for (const code of codes) {
if (typeof code === "string") {
output += koishi_utils3.CQCode.unescape(code);
} else if (code.type === "at") {
if (code.data.qq === "all") {
output += "@全体成员";
} else {
const id = code.data.qq;
const timestamp = Date.now();
if (!userMap[id] || timestamp - userMap[id][1] >= refreshUserName) {
const promise = session.$bot.getGroupMember(session.groupId, id).then((d) => d.nick, () => id);
userMap[id] = [promise, timestamp];
}
output += "@" + await userMap[id][0];
}
} else if (code.type === "share" || code.type === "location") {
output += `[分享:${code.data.title}]`;
} else if (code.type === "contact") {
output += `[推荐${code.data.type === "qq" ? "好友" : "群"}:${code.data.id}]`;
} else {
output += `[${cqTypes[code.type]}]`;
}
}
return output;
}
ctx.on("connect", () => {
koishi_utils3.Logger.lastTime = Date.now();
});
async function onMessage(session) {
const groupName = await getGroupName(session);
const senderName = getSenderName(session);
const message = await formatMessage(session);
logger.debug(`[${groupName}] ${senderName}: ${message}`);
}
if (includeUsers) {
ctx.private(...includeUsers).on("message", onMessage);
}
if (includeGroups) {
ctx.group(...includeGroups).on("message", onMessage);
}
}
});
// packages/plugin-common/src/index.ts
__export(exports, {
apply: () => apply4,
name: () => name
});
// packages/plugin-common/src/repeater.ts
function apply(ctx, options = {}) {
ctx = ctx.group();
const states = {};
function getState(groupId) {
return states[groupId] || (states[groupId] = {
message: "",
repeated: false,
times: 0,
users: {}
});
}
ctx.on("before-send", ({groupId, message}) => {
const state = getState(groupId);
state.repeated = true;
if (state.message === message) {
state.times += 1;
} else {
state.message = message;
state.times = 1;
state.users = {};
}
});
ctx.middleware((session, next) => {
const {message, groupId, userId} = session;
if (ctx.app.bots[userId])
return;
const state = getState(groupId);
const check = (handle) => {
const text = handle == null ? void 0 : handle(state, message, userId);
return text && next(() => {
ctx.emit("repeater", session, state);
return session.$send(text);
});
};
if (message === state.message) {
state.times += 1;
state.users[userId] = (state.users[userId] || 0) + 1;
return check(options.onRepeat) || next();
}
const result = check(options.onInterrupt);
if (result)
return result;
state.message = message;
state.repeated = false;
state.times = 1;
state.users = {[userId]: 1};
return next();
});
}
exports.apply = apply;
//# sourceMappingURL=index.js.map
// packages/plugin-common/src/handler.ts
const koishi_utils = __toModule(require("koishi-utils"));
const defaultMessage = (session) => `欢迎新大佬 [CQ:at,qq=${session.userId}]!`;
async function getHandleResult(handler2, session) {
return typeof handler2 === "function" ? handler2(session) : handler2;
}
function apply2(ctx, options = {}) {
ctx.on("request/friend", async (session) => {
const result = await getHandleResult(options.onFriend, session);
return result !== void 0 && session.$bot.setFriendAddRequest(session.flag, result);
});
ctx.on("request/group/add", async (session) => {
const result = await getHandleResult(options.onGroupAdd, session);
return result !== void 0 && session.$bot.setGroupAddRequest(session.flag, session.subType, result);
});
ctx.on("request/group/invite", async (session) => {
const result = await getHandleResult(options.onGroupInvite, session);
return result !== void 0 && session.$bot.setGroupAddRequest(session.flag, session.subType, result);
});
const {respondents = [], welcome = defaultMessage} = options;
respondents.length && ctx.middleware((session, next) => {
const message = koishi_utils.simplify(session.message);
for (const {match, reply} of respondents) {
const capture = typeof match === "string" ? message === match && [message] : message.match(match);
if (capture)
return session.$send(typeof reply === "string" ? reply : reply(...capture));
}
return next();
});
const throttleConfig = koishi_utils.makeArray(options.throttle);
if (throttleConfig.length) {
const counters = {};
for (const {interval, messages} of throttleConfig) {
counters[interval] = messages;
}
ctx.on("before-send", () => {
for (const {interval} of throttleConfig) {
counters[interval]--;
setTimeout(() => counters[interval]++, interval);
}
});
ctx.prependMiddleware((session, next) => {
for (const interval in counters) {
if (counters[interval] <= 0)
return;
}
return next();
});
}
ctx.on("group-member-added", async (session) => {
if (ctx.bots[session.userId])
return;
if (ctx.database) {
const group = await ctx.database.getGroup(session.kind, session.groupId, ["assignee"]);
if (group.assignee !== session.selfId)
return;
}
const output = typeof welcome === "string" ? welcome : await welcome(session);
await session.$bot.sendMessage(session.channelId, output);
});
}
// packages/plugin-common/src/sender.ts
const koishi_core = __toModule(require("koishi-core"));
const koishi_utils2 = __toModule(require("koishi-utils"));
function apply3(ctx, config = {}) {
ctx.command("broadcast <message...>", "全服广播", {authority: 4}).before((session) => !session.$app.database).option("forced", "-f 无视 silent 标签进行广播").option("only", "-o 仅向当前 Bot 负责的群进行广播").action(async ({options, session}, message) => {
if (!message)
return "请输入要发送的文本。";
if (!options.only) {
await ctx.broadcast(message, options.forced);
return;
}
let groups = await ctx.database.getAllGroups(["id", "flag"], [session.selfId]);
if (!options.forced) {
groups = groups.filter((g) => !(g.flag & koishi_core.Group.Flag.silent));
}
await session.$bot.broadcast(groups.map((g) => g.id), message);
});
ctx.command("echo <message...>", "向当前上下文发送消息", {authority: 2}).option("anonymous", "-a 匿名发送消息", {authority: 3}).option("forceAnonymous", "-A 匿名发送消息", {authority: 3}).option("unescape", "-e 发送非转义的消息", {authority: 3}).action(async ({options}, message) => {
if (!message)
return "请输入要发送的文本。";
if (options.unescape) {
message = koishi_utils2.CQCode.unescape(message);
}
if (options.forceAnonymous) {
message = koishi_utils2.CQCode.stringify("anonymous") + message;
} else if (options.anonymous) {
message = koishi_utils2.CQCode.stringify("anonymous", {ignore: true}) + message;
}
return message;
});
const interactions = {};
config.operator && ctx.command("feedback <message...>", "发送反馈信息给作者").userFields(["name", "id"]).action(async ({session}, text) => {
if (!text)
return "请输入要发送的文本。";
const {$username: name2, userId} = session;
const nickname = name2 === "" + userId ? userId : `${name2} (${userId})`;
const message = `收到来自 ${nickname} 的反馈信息:
${text}`;
const id = await session.$bot.sendPrivateMessage(config.operator, message);
interactions[id] = userId;
return "反馈信息发送成功!";
});
ctx.middleware((session, next) => {
const {$reply, $parsed} = session;
if (!$parsed || !$reply)
return next();
const userId = interactions[$reply.id];
if (!userId)
return next();
return session.$bot.sendPrivateMessage(userId, $parsed);
});
ctx.command("contextify <message...>", "在特定上下文中触发指令", {authority: 3}).alias("ctxf").userFields(["authority"]).before((session) => !session.$app.database).option("user", "-u [id] 使用私聊上下文").option("group", "-g [id] 使用群聊上下文").option("member", "-m [id] 使用当前群/讨论组成员上下文").option("type", "-t [type] 确定发送信息的子类型").usage([
"私聊的子类型包括 other(默认),friend,group。",
"群聊的子类型包括 normal(默认),notice,anonymous。",
"讨论组聊天没有子类型。"
].join("\n")).action(async ({session, options}, message) => {
if (!message)
return "请输入要触发的指令。";
if (options.member) {
if (session.subType === "private") {
return "无法在私聊上下文使用 --member 选项。";
}
options.group = session.groupId;
options.user = options.member;
}
if (!options.user && !options.group) {
return "请提供新的上下文。";
}
const newSession = new koishi_core.Session(ctx.app, session);
newSession.$send = session.$send.bind(session);
newSession.$sendQueued = session.$sendQueued.bind(session);
delete newSession.groupId;
if (!options.group) {
newSession.subType = "private";
delete newSession.$group;
} else if (options.group !== session.groupId) {
newSession.groupId = options.group;
newSession.subType = "group";
delete newSession.$group;
await newSession.$observeGroup(koishi_core.Group.fields);
}
if (options.user) {
const id = "" + koishi_core.getTargetId(options.user);
if (!id)
return "未指定目标。";
newSession.userId = id;
newSession.sender.userId = id;
delete newSession.$user;
const user = await newSession.$observeUser(koishi_core.User.fields);
if (session.$user.authority <= user.authority) {
return "权限不足。";
}
}
if (options.group) {
const info = await session.$bot.getGroupMember(newSession.groupId, newSession.userId).catch(() => ({}));
Object.assign(newSession.sender, info);
} else if (options.user) {
const info = await session.$bot.getUser(newSession.userId).catch(() => ({}));
Object.assign(newSession.sender, info);
}
return newSession.$execute(message);
});
}
// packages/plugin-common/src/index.ts
__exportStar(exports, __toModule(require_admin()));
__exportStar(exports, __toModule(require_info()));
const name = "common";
function apply4(ctx, config = {}) {
ctx.plugin(apply2, config);
ctx.plugin(apply, config);
ctx.plugin(apply3, config);
ctx.plugin(require_admin());
ctx.plugin(require_info());
if (config.debug) {
ctx.plugin(require_debug(), config.debug);
}
}
//# sourceMappingURL=index.js.map

11

package.json
{
"name": "koishi-plugin-common",
"description": "Common plugins for Koishi",
"version": "3.0.3",
"version": "4.0.0-alpha.0",
"main": "dist/index.js",

@@ -13,4 +13,3 @@ "typings": "dist/index.d.ts",

"scripts": {
"lint": "eslint src --ext .ts",
"prepack": "tsc -b"
"lint": "eslint src --ext .ts"
},

@@ -35,8 +34,8 @@ "repository": {

"peerDependencies": {
"koishi-core": "^2.3.2",
"koishi-utils": "^3.1.5"
"koishi-core": "^2.4.2",
"koishi-utils": "^3.2.0"
},
"devDependencies": {
"koishi-test-utils": "^5.0.2"
"koishi-test-utils": "^5.0.3"
}
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc