Skip to content

Instantly share code, notes, and snippets.

@TypeA2
Created September 21, 2026 04:09
Show Gist options
  • Select an option

  • Save TypeA2/727b279cee0a03b680226df0b4643e93 to your computer and use it in GitHub Desktop.

Select an option

Save TypeA2/727b279cee0a03b680226df0b4643e93 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name XHS - Copy DText
// @author hdk5, TypeA2
// @version 20260921-1
// @namespace https://gist.github.com/TypeA2/
// @updateURL https://gist.github.com/TypeA2/727b279cee0a03b680226df0b4643e93/raw/xhs-copy-dtext.user.js
// @downloadURL https://gist.github.com/TypeA2/727b279cee0a03b680226df0b4643e93/raw/xhs-copy-dtext.user.js
// @match https://www.xiaohongshu.com/*
// @match https://www.rednote.com/*
// @grant none
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// ==/UserScript==
/* globals $ */
function reprNode(node) {
const tag = node.tagName.toLowerCase();
const id = node.id ? `#${node.id}` : '';
const cls = node.className.split(' ').map(c => `.${c}`).join('');
return `${tag}${id}${cls}`;
}
function convertHtml(el) {
const errors = [];
const text = $(el).contents().filter(function () {
return this.nodeType === Node.ELEMENT_NODE;
}).map(function () {
const $node = $(this);
const redmojiMap = window.__INITIAL_STATE__?.redMoji?.mojiData?._rawValue?.redmojiMap || {};
const redmojiMapInvert = Object.fromEntries(
Object.entries(redmojiMap).map(([key, value]) => [value, key]),
);
if ($node.hasClass('tag')) {
// TPT fucks up the layout a bit - can't use just .text()
const tag = $node.contents().filter(function () {
return this.nodeType === Node.TEXT_NODE;
}).text();
return `"${tag}":[https://www.xiaohongshu.com/search_result?keyword=${encodeURIComponent(tag.slice(1))}]`;
}
if ($node.hasClass('note-content-user')) {
const user = $node.text().trim();
const userId = $node.attr('data-user-id');
return `"${user}":[https://www.xiaohongshu.com/user/profile/${userId}]`;
}
if ($node.hasClass('note-content-emoji')) {
const emojiUrl = $node.attr('src');
const emojiTag = redmojiMapInvert[emojiUrl] || '[emoji]';
return `"${emojiTag}":[${emojiUrl}]`;
}
if (
this.tagName !== 'SPAN'
|| this.id !== ''
|| this.className !== ''
) {
errors.push(`Unsupported element: ${reprNode(this)}`);
console.warn('Unsupported element:', this);
}
return $node.text();
}).get().join('');
return { text, errors };
}
function addButtons() {
$('.note-text:not(:has(+ .ex-copy-dtext))').each(function () {
const note = this;
const $note = $(note);
const $btn = $('<a>', {
class: 'ex-copy-dtext',
text: 'Copy DText',
css: {
display: 'block',
cursor: 'pointer',
},
});
$btn.on('click', (ev) => {
ev.preventDefault();
const { text, errors } = convertHtml(note);
if (errors.length) {
alert(errors.join("\n"));
}
navigator.clipboard.writeText(text);
$btn.text("Copied!")
});
$note.after($btn);
});
}
new MutationObserver(addButtons).observe(document.body, { childList: true, subtree: true });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment