Greasy Fork is available in English.
Tag users on US Card Forum with personal labels
// ==UserScript==
// @name US Card Forum User Tag
// @namespace https://www.uscardforum.com/
// @version 1.2
// @description Tag users on US Card Forum with personal labels
// @author Your Name
// @match https://www.uscardforum.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration
const STORAGE_KEY = 'uscardforum.user_tags.v1';
const TAG_STYLE = 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 4px; padding: 2px 8px; margin-left: 8px; font-size: 11px; font-weight: 600; display: inline-block; box-shadow: 0 2px 4px rgba(0,0,0,0.2); vertical-align: middle;';
const BUTTON_STYLE = 'background: #667eea; color: white; border: none; border-radius: 4px; padding: 3px 8px; margin-left: 8px; cursor: pointer; font-size: 11px; transition: all 0.2s; vertical-align: middle;';
// Storage functions
function getTagMap() {
const tagJsonStr = GM_getValue(STORAGE_KEY, '{}');
return JSON.parse(tagJsonStr);
}
function saveTagMap(tagMap) {
GM_setValue(STORAGE_KEY, JSON.stringify(tagMap));
}
function deleteTag(username) {
const tagMap = getTagMap();
delete tagMap[username];
saveTagMap(tagMap);
}
// Create and display tag element
function createTagElement(tagText) {
const tagSpan = document.createElement('span');
tagSpan.textContent = tagText;
tagSpan.setAttribute('style', TAG_STYLE);
tagSpan.className = 'user-custom-tag';
return tagSpan;
}
// Create tag button
function createTagButton(username, containerElement) {
const tagMap = getTagMap();
const button = document.createElement('button');
button.textContent = '🏷️';
button.className = 'tag-user-btn';
button.setAttribute('style', BUTTON_STYLE);
button.title = 'Tag this user';
button.addEventListener('mouseenter', () => {
button.style.background = '#5568d3';
});
button.addEventListener('mouseleave', () => {
button.style.background = '#667eea';
});
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const currentTag = tagMap[username] || '';
const newTag = prompt(
`Edit tag for user: ${username}\n\nEnter tags (comma-separated):\nExamples: "excellent debater", "helpful", "expert"`,
currentTag
);
if (newTag !== null) {
if (newTag.trim() === '') {
if (currentTag && confirm(`Remove all tags for ${username}?`)) {
deleteTag(username);
location.reload();
}
} else {
tagMap[username] = newTag;
saveTagMap(tagMap);
location.reload();
}
}
});
return button;
}
// Add tags and button to a user element
function processUserElement(userLink) {
// Avoid processing twice
if (userLink.hasAttribute('data-tag-processed')) {
return;
}
userLink.setAttribute('data-tag-processed', 'true');
// Skip if inside avatar elements
if (userLink.closest('.post-avatar') || userLink.closest('.topic-avatar') || userLink.closest('.topic-poster') || userLink.closest('.topic-list-data')) {
return;
}
const username = userLink.getAttribute('data-user-card') || userLink.textContent.trim();
if (!username) return;
const tagMap = getTagMap();
const parentSpan = userLink.closest('.first.username') || userLink.parentElement;
if (!parentSpan) return;
// Add existing tags
if (tagMap[username]) {
const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
tags.forEach(tag => {
const tagElement = createTagElement(tag);
parentSpan.appendChild(tagElement);
});
}
// Add tag button
const button = createTagButton(username, parentSpan);
parentSpan.appendChild(button);
}
// Process all user links in posts
function processPosts() {
// Find all user links in post headers
const userLinks = document.querySelectorAll('a[data-user-card][href^="/u/"]');
userLinks.forEach(link => {
processUserElement(link);
});
}
// Process user profile page
function processUserProfile() {
// Profile page has different structure
const profileHeader = document.querySelector('.user-profile-names .username');
if (profileHeader && !profileHeader.hasAttribute('data-tag-processed')) {
profileHeader.setAttribute('data-tag-processed', 'true');
const username = profileHeader.textContent.trim();
const tagMap = getTagMap();
// Add tags
if (tagMap[username]) {
const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
tags.forEach(tag => {
const tagElement = createTagElement(tag);
profileHeader.appendChild(tagElement);
});
}
// Add button
const button = createTagButton(username, profileHeader);
profileHeader.appendChild(button);
}
}
// Process user cards (popup when clicking username)
function processUserCards() {
const userCards = document.querySelectorAll('.user-card .names-link, .user-card .username');
userCards.forEach(nameElement => {
if (nameElement.hasAttribute('data-tag-processed')) return;
nameElement.setAttribute('data-tag-processed', 'true');
const username = nameElement.textContent.trim();
const tagMap = getTagMap();
// Add tags
if (tagMap[username]) {
const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
tags.forEach(tag => {
const tagElement = createTagElement(tag);
nameElement.appendChild(tagElement);
});
}
// Add button
const button = createTagButton(username, nameElement);
nameElement.appendChild(button);
});
}
// Process topic lists
function processTopicLists() {
// Topic list poster names
const posterLinks = document.querySelectorAll('.topic-list .posters a[data-user-card], .latest-topic-list-item a[data-user-card]');
posterLinks.forEach(link => {
if (link.hasAttribute('data-tag-processed')) return;
link.setAttribute('data-tag-processed', 'true');
const username = link.getAttribute('data-user-card') || link.getAttribute('title');
if (!username) return;
const tagMap = getTagMap();
if (tagMap[username]) {
const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
tags.forEach(tag => {
const tagElement = createTagElement(tag);
tagElement.style.marginLeft = '4px';
link.parentElement.appendChild(tagElement);
});
}
});
}
// Main processing function
function processAll() {
processPosts();
processUserProfile();
processUserCards();
processTopicLists();
}
// Observer for dynamic content
const observer = new MutationObserver((mutations) => {
// Debounce to avoid excessive processing
clearTimeout(window.tagProcessTimeout);
window.tagProcessTimeout = setTimeout(() => {
processAll();
}, 100);
});
// Initialize
function init() {
console.log('US Card Forum User Tag script initialized');
processAll();
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('Found', document.querySelectorAll('a[data-user-card][href^="/u/"]').length, 'user links');
}
// Wait for page to load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();