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

@purduehackers/discord-markdown-utils

v0.0.4

Published

A [remark](https://github.com/remarkjs/remark) plugin that parses Discord mention syntax into mdast nodes. Comes with rehype handlers for rendering them as HTML.

Readme

@purduehackers/discord-markdown-utils

A remark plugin that parses Discord mention syntax into mdast nodes. Comes with rehype handlers for rendering them as HTML.

Also enables GFM autolink literals so plain URLs in Discord messages become links.

Install

bun add @purduehackers/discord-markdown-utils

Usage

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import {
	remarkDiscord,
	discordRemarkRehypeHandlers,
} from "@purduehackers/discord-markdown-utils";

const processor = unified()
	.use(remarkParse)
	.use(remarkDiscord, { resolver })
	.use(remarkRehype, { handlers: discordRemarkRehypeHandlers })
	.use(rehypeStringify);

const html = String(await processor.process(discordMessage));

The plugin requires a resolver object that looks up display values for each mention type by ID. You implement it against whatever data source you have — a Discord API client, a database cache, etc.

Providing a resolver

interface Resolver {
	user(mention: { type: "user"; id: string }): Promise<string | null>;
	role(mention: {
		type: "role";
		id: string;
	}): Promise<{ name: string; color?: string } | null>;
	channel(mention: { type: "channel"; id: string }): Promise<string | null>;
	emoji(mention: {
		type: "emoji";
		animated: boolean;
		name: string;
		id: string;
	}): Promise<string | null>;
	timestamp(mention: {
		type: "timestamp";
		date: Date;
	}): Promise<string | null>;
}

Each method receives the parsed mention and should return the resolved display value, or null if unknown. Returning null falls back to a default value (see below).

Example using a discord.js Guild:

import { type Guild, Colors } from "discord.js";

function makeResolver(guild: Guild): Resolver {
	return {
		async user({ id }) {
			const member = await guild.members.fetch(id).catch(() => null);
			return member?.displayName ?? null;
		},
		async role({ id }) {
			const role = await guild.roles.fetch(id).catch(() => null);
			if (!role) return null;
			return {
				name: role.name,
				color:
					role.color !== Colors.Default ? role.hexColor : undefined,
			};
		},
		async channel({ id }) {
			const channel = await guild.channels.fetch(id).catch(() => null);
			return channel?.name ?? null;
		},
		async emoji({ id }) {
			const emoji = await guild.emojis.fetch(id).catch(() => null);
			return emoji?.imageURL() ?? null;
		},
		async timestamp({ date }) {
			return date.toLocaleString("en-US", {
				timeZone: "America/New_York",
			});
		},
	};
}

Supported Syntax

| Discord syntax | Type | Notes | | -------------------- | --------------- | ---------------------------------- | | <@123> / <@!123> | user mention | ! prefix is legacy | | <@&123> | role mention | | | <#123> | channel mention | | | <:name:123> | custom emoji | static | | <a:name:123> | custom emoji | animated | | <t:1700000000> | timestamp | Unix seconds; format flags ignored | | </command:123> | slash command | |

HTML Output

When using discordRemarkRehypeHandlers with remark-rehype, each mention type renders as seen in the table below.

This library makes no assumptions about the styling of each component. A class is added to each element representing a Discord mention so you can style them using CSS. For roles with colors, the role color is added a a custom property (--role-color) so you can reference it in your own styles.

| Type | HTML | | -------------------------------- | ------------------------------------------------------------------------------------------- | | User | <span class="discord-user">Display Name</span> | | Role | <span class="discord-role">Role Name</span> | | Role (with color) | <span class="discord-role" style="--role-color: ...;">Role Name</span> | | Channel | <span class="discord-channel">Channel Name</span> | | Emoji | <img src="..." alt="name" class="discord-emoji"> | | Emoji (fallback when null URL) | <span class="discord-emoji">:name:</span> | | Timestamp | <time datetime="2026-04-07T03:16:58.576Z" class="discord-timestamp">formatted date</time> | | Command | <span class="discord-command">/command</span> |

Fallback Values

When a resolver returns null, the plugin uses these defaults:

| Type | Fallback | | --------- | ------------------------------------ | | User | unknown-user | | Role | unknown-role | | Channel | unknown-channel | | Emoji | renders as :name: text | | Timestamp | ISO string from Date.toISOString() |

AST Nodes

If you need access to the parsed nodes directly (without converting to HTML), all node types are exported:

import type {
	DiscordMentionNode,
	DiscordUserMentionNode,
	DiscordRoleMentionNode,
	DiscordChannelMentionNode,
	DiscordEmojiMentionNode,
	DiscordTimestampMentionNode,
	DiscordCommandMentionNode,
} from "@purduehackers/discord-markdown-utils";

Each node type is also declared in the mdast module's RootContentMap, so they work correctly with unist-util-visit and other mdast utilities.