Elon Musk recently open-sourced the Twitter recommendation algorithm. Can this be implemented in this plugin?
Below is a Tampermonkey script written by a forum user to implement the Twitter recommendation algorithm. Theoretically, it can be used on any Discourse forum:
// ==UserScript==
// @name *** Algorithm
// @namespace http://tampermonkey.net/
// @version 0.9
// @description Intelligent recommendation system integrating the X algorithm and DeepSeek AI, providing personalized topic recommendations
// @author ***
// @match ****
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @connect 2c2ch1u11-share-api-0.hf.space
// ==/UserScript==
(function() {
'use strict';
const API_KEY = '***';
const API_URL = '***/v1/chat/completions';
const MODEL = 'deepseek-chat';
// ========== Algorithm Configuration Parameters (Adjustable for Optimization) ==========
const CONFIG = {
// X Algorithm Weight Parameters
WEIGHT_LIKES: 0.5,
WEIGHT_REPLIES: 13.5,
WEIGHT_VIEWS: 0.015,
PINNED_BOOST: 2.0,
TIME_DECAY_FACTOR: 1.5,
TIME_DECAY_OFFSET: 2,
// Maximum Score Parameters
X_SCORE_MAX: 45,
AI_SCORE_MAX: 55,
// Score Threshold Parameters
MAX_SCORE: 100,
MIN_DISPLAY_SCORE: 20,
// Cache Parameters
PROFILE_CACHE_TTL: 86400000, // User profile cache: 24 hours
TOPIC_SCORE_CACHE_TTL: 3600000, // Single topic AI score cache: 1 hour
// API Parameters
MAX_TOPICS_PER_BATCH: 30,
MAX_LIKED_TITLES: 15, // Number of liked topics
MAX_REPLIED_TITLES: 10, // Number of replied topics
MAX_CREATED_TITLES: 5, // Number of created topics
API_RETRY_COUNT: 2,
API_RETRY_DELAY: 1000,
// User Profile Analysis Dimensions
PROFILE_CATEGORIES: ['Technical Interests', 'Content Preferences', 'Interaction Habits', 'Professional Fields', 'Reading Depth'],
};
let isRecommendMode = false;
let scoreMap = {};
let allTopicsData = {};
let sortTimeout = null;
let userProfile = "";
let isLoading = false;
let isProcessingNewTopics = false;
// ========== CSS Styles ==========
GM_addStyle(`
.nav-pills > li > a,
.nav-pills > li.ember-view > a,
.navigation-container .nav-pills > li > a {
border-bottom: 3px solid transparent !important;
transition: all 0.2s ease;
}
body.recommend-mode-active .nav-pills > li:not(#nav-item-recommend) > a,
body.recommend-mode-active .nav-pills > li.ember-view:not(#nav-item-recommend) > a,
body.recommend-mode-active .navigation-container .nav-pills > li:not(#nav-item-recommend) > a {
color: var(--primary-medium) !important;
border-bottom-color: transparent !important;
}
.nav-pills li#nav-item-recommend.active > a,
body.recommend-mode-active .nav-pills li#nav-item-recommend > a {
color: var(--tertiary) !important;
border-bottom: 3px solid var(--tertiary) !important;
font-weight: 600;
}
.nav-pills li#nav-item-recommend > a:hover {
color: var(--tertiary) !important;
opacity: 0.8;
}
.recommend-loading-container {
width: 100%;
padding: 60px 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 300px;
}
.recommend-loading-text {
color: var(--primary-medium);
font-size: 15px;
margin-bottom: 24px;
text-align: center;
}
.recommend-spinner {
width: 36px;
height: 36px;
border: 3px solid var(--primary-low);
border-top: 3px solid var(--primary-medium);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.manus-score-badge {
font-size: 0.75em;
color: var(--tertiary);
margin-left: 8px;
padding: 2px 6px;
background: var(--tertiary-very-low);
border-radius: 4px;
font-weight: 500;
transition: all 0.3s ease;
}
.manus-score-badge.calculating {
color: var(--primary-medium);
background: var(--primary-very-low);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.manus-score-loading {
font-size: 0.75em;
color: var(--primary-medium);
margin-left: 8px;
}
.recommend-full-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--secondary);
z-index: 100;
min-height: 500px;
}
.recommend-loading-row {
position: relative;
z-index: 101;
}
`);
// ========== Topic AI Score Cache Functions ==========
function getTopicScoreCache() {
return GM_getValue('topic_ai_scores_cache', {});
}
function setTopicScoreCache(topicId, aiScore) {
const cache = getTopicScoreCache();
cache[topicId] = { score: aiScore, time: Date.now() };
GM_setValue('topic_ai_scores_cache', cache);
}
function getCachedTopicScore(topicId) {
const cache = getTopicScoreCache();
const entry = cache[topicId];
if (entry && (Date.now() - entry.time < CONFIG.TOPIC_SCORE_CACHE_TTL)) {
return entry.score;
}
return null;
}
function cleanExpiredCache() {
const cache = getTopicScoreCache();
const now = Date.now();
let cleaned = false;
for (const [id, entry] of Object.entries(cache)) {
if (now - entry.time > CONFIG.TOPIC_SCORE_CACHE_TTL) {
delete cache[id];
cleaned = true;
}
}
if (cleaned) {
GM_setValue('topic_ai_scores_cache', cache);
}
}
// ========== Inject Recommendation Tab ==========
function injectRecommendTab() {
if (document.querySelector('#nav-item-recommend')) return;
const navPills = document.querySelector('.nav-pills');
if (!navPills) return;
const recommendTab = document.createElement('li');
recommendTab.id = 'nav-item-recommend';
recommendTab.className = 'ember-view nav-item_recommend';
recommendTab.innerHTML = `<a href="javascript:void(0)">Recommend</a>`;
const latestTab = navPills.querySelector('.nav-item_latest') ||
navPills.querySelector('[data-filter-type="latest"]')?.parentElement ||
navPills.firstChild;
navPills.insertBefore(recommendTab, latestTab);
recommendTab.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
if (isLoading) return;
isRecommendMode = true;
document.body.classList.add('recommend-mode-active');
navPills.querySelectorAll('li').forEach(li => {
li.classList.remove('active');
li.querySelector('a')?.classList.remove('active');
});
recommendTab.classList.add('active');
cleanExpiredCache();
showLoading(true);
await startRecommendation();
showLoading(false);
sortAndDisplayRows();
});
navPills.querySelectorAll('li:not(#nav-item-recommend)').forEach(tab => {
tab.addEventListener('click', () => {
isRecommendMode = false;
document.body.classList.remove('recommend-mode-active');
recommendTab.classList.remove('active');
document.querySelectorAll('.manus-score-badge').forEach(b => b.remove());
document.querySelectorAll('.manus-score-loading').forEach(b => b.remove());
});
});
window.addEventListener('popstate', () => {
if (isRecommendMode) {
isRecommendMode = false;
document.body.classList.remove('recommend-mode-active');
recommendTab.classList.remove('active');
}
});
}
// ========== Loading Indicator ==========
let originalTableContent = null;
function showLoading(show, text = 'Integrating X Algorithm and DeepSeek AI recommendations...') {
isLoading = show;
const topicListContainer = document.querySelector('.topic-list-container') ||
document.querySelector('.topic-list')?.parentElement ||
document.querySelector('.topic-list');
const tbody = document.querySelector('.topic-list tbody');
if (!topicListContainer) return;
if (show) {
if (!originalTableContent && tbody) {
originalTableContent = tbody.innerHTML;
}
if (tbody) {
tbody.innerHTML = `
<tr class="recommend-loading-row">
<td colspan="100%">
<div class="recommend-loading-container">
<div class="recommend-loading-text">${text}</div>
<div class="recommend-spinner"></div>
</div>
</td>
</tr>
`;
}
topicListContainer.style.position = 'relative';
let overlay = topicListContainer.querySelector('.recommend-full-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'recommend-full-overlay';
topicListContainer.appendChild(overlay);
}
} else {
topicListContainer.querySelector('.recommend-full-overlay')?.remove();
if (originalTableContent && tbody) {
tbody.innerHTML = originalTableContent;
originalTableContent = null;
}
}
}
// ========== Helper function to extract titles ==========
function extractTitles(data, maxCount) {
if (!data?.user_actions) return '';
const titles = [...new Set(
data.user_actions
.filter(a => a.title)
.map(a => a.title)
)].slice(0, maxCount);
return titles.length > 0 ? titles.join('\n') : '';
}
// ========== Optimization: More detailed user profile retrieval ==========
async function fetchUserProfile(username) {
if (!username) {
console.log('[Recommendation] Not logged in, using default profile');
return "Default";
}
const cacheKey = `user_profile_v9_${username}`;
const cached = GM_getValue(cacheKey);
if (cached && (Date.now() - cached.time < CONFIG.PROFILE_CACHE_TTL)) {
console.log(`[Recommendation] User profile (cache):\n${cached.profile}`);
return cached.profile;
}
try {
// Retrieve various user behavior data
const [likedData, repliedData, createdData] = await Promise.all([
fetch(`/user_actions.json?username=${username}&filter=1`).then(r => r.json()).catch(() => null), // Likes
fetch(`/user_actions.json?username=${username}&filter=5`).then(r => r.json()).catch(() => null), // Replies
fetch(`/user_actions.json?username=${username}&filter=4`).then(r => r.json()).catch(() => null), // Created
]);
// Extract titles of different types
const likedTitles = extractTitles(likedData, CONFIG.MAX_LIKED_TITLES);
const repliedTitles = extractTitles(repliedData, CONFIG.MAX_REPLIED_TITLES);
const createdTitles = extractTitles(createdData, CONFIG.MAX_CREATED_TITLES);
if (!likedTitles && !repliedTitles && !createdTitles) return "Default";
// Build a more detailed analysis prompt
const prompt = `You are a professional user behavior analyst. Please generate a detailed user profile based on the following user behavior data:
**Topics liked by the user** (best reflects interests):
${likedTitles || 'No data'}
**Topics replied to by the user** (reflects engagement and expertise):
${repliedTitles || 'No data'}
**Topics created by the user** (reflects active focus areas):
${createdTitles || 'No data'}
Please analyze the user profile from the following dimensions, summarizing each dimension in one sentence:
1. **Technical Interests**: Which tech stacks, tools, or platforms the user is interested in
2. **Content Preferences**: Preference for tutorials, discussions, news, or Q&A content
3. **Interaction Habits**: Whether the user is a deep participant or a shallow browser
4. **Professional Field**: Main technical fields or industry directions of focus
5. **Reading Depth**: Preference for beginner, intermediate, or expert-level content
Output format (no numbering, output 5 lines directly):
Technical Interests: [Analysis Result]
Content Preferences: [Analysis Result]
Interaction Habits: [Analysis Result]
Professional Field: [Analysis Result]
Reading Depth: [Analysis Result]`;
const profile = await callDeepSeek(prompt, false);
if (profile && profile.length > 0) {
GM_setValue(cacheKey, { time: Date.now(), profile: profile });
console.log(`[Recommendation] User profile:\n${profile}`);
return profile;
}
return "Default";
} catch (e) {
console.error('[Recommendation] Failed to retrieve user profile:', e);
return "Default";
}
}
// ========== Start recommendation process ==========
async function startRecommendation() {
try {
const currentUser = getCurrentUserInfo();
if (!userProfile) {
userProfile = await fetchUserProfile(currentUser?.username);
}
const response = await fetch('/latest.json');
const data = await response.json();
const topics = data.topic_list.topics;
topics.forEach(t => {
allTopicsData[t.id] = t;
});
const uncachedTopics = [];
const xScoresRaw = {};
const aiScoresMap = {};
topics.forEach(topic => {
xScoresRaw[topic.id] = calculateXScore(topic);
const cachedScore = getCachedTopicScore(topic.id);
if (cachedScore !== null) {
aiScoresMap[topic.id] = cachedScore;
} else {
uncachedTopics.push(topic);
}
});
if (uncachedTopics.length > 0) {
await batchScoreTopics(uncachedTopics, aiScoresMap);
}
calculateFinalScores(topics, xScoresRaw, aiScoresMap);
} catch (e) {
console.error('[Recommendation] Recommendation process failed:', e);
}
}
// ========== Batch score topics ==========
async function batchScoreTopics(topics, aiScoresMap) {
for (let i = 0; i < topics.length; i += CONFIG.MAX_TOPICS_PER_BATCH) {
const batch = topics.slice(i, i + CONFIG.MAX_TOPICS_PER_BATCH);
const aiScores = await getAIScoresForBatch(batch);
batch.forEach(topic => {
const aiScore = aiScores[topic.id] || 5;
setTopicScoreCache(topic.id, aiScore);
aiScoresMap[topic.id] = aiScore;
});
}
}
// ========== Optimization: More precise batch topic scoring ==========
async function getAIScoresForBatch(topics) {
const topicList = topics.map((t, idx) =>
`${idx + 1}. [ID:${t.id}] ${t.title}`
).join('\n');
const prompt = `You are a content recommendation expert. Please score the following topics based on the user profile.
**User Profile**:
${userProfile}
**Topics to Score**:
${topicList}
**Scoring Criteria** (0-10 points):
- **9-10 points**: Highly matches user profile, user is very likely to be interested
- **7-8 points**: Matches user profile well, user is likely to be interested
- **5-6 points**: Partially matches user profile, user might be interested
- **3-4 points**: Weakly related to user profile, user is unlikely to be interested
- **0-2 points**: Unrelated or opposite to user profile, user is not interested
**Scoring Considerations**:
1. Match between topic content and user technical interests
2. Consistency between topic type and user content preferences
3. Adaptability between topic depth and user reading depth
4. Relevance between topic field and user professional field
Please return in JSON format, with keys as topic IDs (pure numbers) and values as scores (0-10 numbers).
Example: {"123": 8.5, "456": 5.0}
Return only JSON, no other text.`;
const scores = await callDeepSeek(prompt, true);
// Clean and validate scores
const cleanedScores = {};
for (const [id, score] of Object.entries(scores)) {
const numId = parseInt(id, 10);
const numScore = parseFloat(score);
if (!isNaN(numId) && !isNaN(numScore) && numScore >= 0 && numScore <= 10) {
cleanedScores[numId] = Math.round(numScore * 10) / 10;
}
}
return cleanedScores;
}
// ========== Optimization: More robust final score calculation ==========
function calculateFinalScores(topics, xScoresRaw, aiScoresMap) {
const xScores = Object.values(xScoresRaw);
if (xScores.length === 0) return;
// Use percentile normalization to avoid extreme value impact
const sortedX = [...xScores].sort((a, b) => a - b);
const p5 = sortedX[Math.floor(sortedX.length * 0.05)]; // 5th percentile
const p95 = sortedX[Math.floor(sortedX.length * 0.95)]; // 95th percentile
const rangeX = p95 - p5;
topics.forEach(topic => {
const id = topic.id;
let xScoreNormalized;
if (rangeX === 0) {
xScoreNormalized = CONFIG.X_SCORE_MAX / 2;
} else {
// Clamp within p5 to p95 range
const clampedX = Math.max(p5, Math.min(p95, xScoresRaw[id]));
xScoreNormalized = ((clampedX - p5) / rangeX) * CONFIG.X_SCORE_MAX;
}
const aiScore = aiScoresMap[id] || 5;
const aiScoreNormalized = (aiScore / 10) * CONFIG.AI_SCORE_MAX;
// Final Score: X Algorithm (45%) + AI Score (55%)
const finalScore = xScoreNormalized + aiScoreNormalized;
scoreMap[id] = Math.round(finalScore * 10) / 10;
});
}
// ========== Calculate final scores for new topics ==========
function calculateFinalScoresForNew(topicIds, xScoresRaw, aiScoresMap) {
const allXScores = Object.values(scoreMap).length > 0
? [...Object.values(xScoresRaw), ...Object.keys(allTopicsData).map(id => calculateXScore(allTopicsData[id]))]
: Object.values(xScoresRaw);
if (allXScores.length === 0) return;
const sortedX = [...allXScores].sort((a, b) => a - b);
const p5 = sortedX[Math.floor(sortedX.length * 0.05)];
const p95 = sortedX[Math.floor(sortedX.length * 0.95)];
const rangeX = p95 - p5;
topicIds.forEach(id => {
if (xScoresRaw[id] === undefined) return;
let xScoreNormalized;
if (rangeX === 0) {
xScoreNormalized = CONFIG.X_SCORE_MAX / 2;
} else {
const clampedX = Math.max(p5, Math.min(p95, xScoresRaw[id]));
xScoreNormalized = ((clampedX - p5) / rangeX) * CONFIG.X_SCORE_MAX;
}
const aiScore = aiScoresMap[id] || 5;
const aiScoreNormalized = (aiScore / 10) * CONFIG.AI_SCORE_MAX;
const finalScore = xScoreNormalized + aiScoreNormalized;
scoreMap[id] = Math.round(finalScore * 10) / 10;
});
}
// ========== Handle newly loaded topics (scroll loading) ==========
async function processNewTopics(newRows) {
if (!isRecommendMode || isProcessingNewTopics) return;
isProcessingNewTopics = true;
try {
const newTopicIds = [];
newRows.forEach(row => {
const id = row.getAttribute('data-topic-id');
if (id && !scoreMap[id]) {
newTopicIds.push(id);
}
});
if (newTopicIds.length === 0) {
isProcessingNewTopics = false;
return;
}
newTopicIds.forEach(id => {
const row = document.querySelector(`tr[data-topic-id="${id}"]`);
if (row) {
addCalculatingBadge(row);
}
});
const uncachedTopics = [];
const xScoresRaw = {};
const aiScoresMap = {};
for (const id of newTopicIds) {
const topicData = allTopicsData[id] || await fetchTopicData(id);
if (!topicData) continue;
allTopicsData[id] = topicData;
xScoresRaw[id] = calculateXScore(topicData);
const cachedScore = getCachedTopicScore(id);
if (cachedScore !== null) {
aiScoresMap[id] = cachedScore;
} else {
uncachedTopics.push(topicData);
}
}
if (uncachedTopics.length > 0) {
await batchScoreTopics(uncachedTopics, aiScoresMap);
}
calculateFinalScoresForNew(newTopicIds, xScoresRaw, aiScoresMap);
updateRowBadges();
} catch (e) {
console.error('[Recommendation] Failed to process new topics:', e);
}
isProcessingNewTopics = false;
}
// ========== Get single topic data ==========
async function fetchTopicData(topicId) {
try {
const row = document.querySelector(`tr[data-topic-id="${topicId}"]`);
if (row) {
const title = row.querySelector('.title')?.textContent?.trim() || '';
const replies = parseInt(row.querySelector('.posts')?.textContent) || 0;
const views = parseInt(row.querySelector('.views')?.textContent?.replace(/[k,]/gi, '')) || 0;
const likes = parseInt(row.querySelector('.likes')?.textContent) || 0;
return {
id: topicId,
title: title,
posts_count: replies,
views: views,
like_count: likes,
created_at: new Date().toISOString(),
pinned: row.classList.contains('pinned')
};
}
return null;
} catch (e) {
return null;
}
}
// ========== Sort and display ==========
function sortAndDisplayRows() {
if (!isRecommendMode) return;
const tbody = document.querySelector('.topic-list tbody');
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll('tr.topic-list-item'));
rows.sort((a, b) => {
const idA = a.getAttribute('data-topic-id');
const idB = b.getAttribute('data-topic-id');
const scoreA = scoreMap[idA] || 0;
const scoreB = scoreMap[idB] || 0;
return scoreB - scoreA;
});
let hiddenCount = 0;
rows.forEach(row => {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
if (score === undefined) {
row.style.display = '';
addCalculatingBadge(row);
} else if (score < CONFIG.MIN_DISPLAY_SCORE) {
row.style.display = 'none';
hiddenCount++;
} else {
row.style.display = '';
addScoreBadge(row);
}
tbody.appendChild(row);
});
}
// ========== Update badges for all rows ==========
function updateRowBadges() {
if (!isRecommendMode) return;
const rows = document.querySelectorAll('tr.topic-list-item');
rows.forEach(row => {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
if (score === undefined) {
addCalculatingBadge(row);
} else if (score < CONFIG.MIN_DISPLAY_SCORE) {
row.style.display = 'none';
} else {
row.style.display = '';
addScoreBadge(row);
}
});
}
// ========== Add score badge ==========
function addScoreBadge(row) {
const id = row.getAttribute('data-topic-id');
const score = scoreMap[id];
row.querySelector('.manus-score-loading')?.remove();
if (score !== undefined) {
let badge = row.querySelector('.manus-score-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'manus-score-badge';
const titleContainer = row.querySelector('.link-top-line') ||
row.querySelector('.title')?.parentElement ||
row.querySelector('.main-link') ||
row.querySelector('td:first-child');
if (titleContainer) {
titleContainer.appendChild(badge);
}
}
if (badge) {
badge.textContent = `🔥 ${score.toFixed(1)}`;
badge.classList.remove('calculating');
}
}
}
// ========== Add "Calculating" badge ==========
function addCalculatingBadge(row) {
const id = row.getAttribute('data-topic-id');
if (scoreMap[id] !== undefined) return;
let badge = row.querySelector('.manus-score-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'manus-score-badge calculating';
const titleContainer = row.querySelector('.link-top-line') ||
row.querySelector('.title')?.parentElement ||
row.querySelector('.main-link') ||
row.querySelector('td:first-child');
if (titleContainer) {
titleContainer.appendChild(badge);
}
}
if (badge) {
badge.textContent = '⏳ Calculating...';
badge.classList.add('calculating');
}
}
// ========== Optimization: Improved JSON cleaning logic ==========
function cleanJsonResponse(content) {
if (!content) return content;
let cleaned = content.trim();
// Remove Markdown code block markers
cleaned = cleaned.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '');
// Remove possible text explanations
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
if (jsonMatch) {
cleaned = jsonMatch[0];
}
return cleaned.trim();
}
// ========== API Call ==========
async function callDeepSeek(prompt, isJson = true, retryCount = 0) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "POST",
url: API_URL,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
data: JSON.stringify({
model: MODEL,
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
max_tokens: 1000
}),
timeout: 30000,
onload: (res) => {
try {
if (res.status >= 200 && res.status < 300) {
let content = JSON.parse(res.responseText).choices[0].message.content;
if (isJson) {
content = cleanJsonResponse(content);
resolve(JSON.parse(content));
} else {
resolve(content);
}
} else {
throw new Error(`HTTP ${res.status}`);
}
} catch(e) {
console.error('[Recommendation] API response parsing failed:', e);
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
}
},
onerror: (err) => {
console.error('[Recommendation] API call failed:', err);
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
},
ontimeout: () => {
console.error('[Recommendation] API call timed out');
if (retryCount < CONFIG.API_RETRY_COUNT) {
setTimeout(() => {
callDeepSeek(prompt, isJson, retryCount + 1).then(resolve);
}, CONFIG.API_RETRY_DELAY);
} else {
resolve(isJson ? {} : "");
}
}
});
});
}
// ========== Optimization: Smarter X Algorithm Scoring ==========
function calculateXScore(topic) {
const now = new Date();
const created = new Date(topic.created_at);
const hoursOld = Math.max(0, (now - created) / (1000 * 60 * 60));
// Base engagement score
const likeScore = (topic.like_count || 0) * CONFIG.WEIGHT_LIKES;
const replyScore = (topic.posts_count || 0) * CONFIG.WEIGHT_REPLIES;
const viewScore = (topic.views || 0) * CONFIG.WEIGHT_VIEWS;
// Calculate engagement rate bonus (high engagement rate indicates higher content quality)
const views = topic.views || 1;
const engagementRate = ((topic.like_count || 0) + (topic.posts_count || 0)) / views;
const engagementBoost = 1 + Math.min(engagementRate * 10, 2); // Max 2x bonus
let score = (likeScore + replyScore + viewScore) * engagementBoost;
// Pin bonus
if (topic.pinned) {
score *= CONFIG.PINNED_BOOST;
}
// Time decay (freshness)
const timeFactor = Math.pow(hoursOld + CONFIG.TIME_DECAY_OFFSET, CONFIG.TIME_DECAY_FACTOR);
return score / timeFactor;
}
// ========== Get current user info ==========
function getCurrentUserInfo() {
try {
// Method 1: Discourse container
const container = window.Discourse?.__container__;
if (container) {
const currentUser = container.lookup('service:current-user');
if (currentUser?.username) {
return {
username: currentUser.username,
id: currentUser.id,
name: currentUser.name,
trust_level: currentUser.trust_level
};
}
}
// Method 2: User.current()
if (window.User?.current?.()?.username) {
const user = window.User.current();
return { username: user.username, id: user.id };
}
// Method 3: #current-user
const userLink = document.querySelector('#current-user a[data-user-card]');
if (userLink) {
return { username: userLink.getAttribute('data-user-card') };
}
// Method 4: header
const headerUser = document.querySelector('.header-dropdown-toggle.current-user button');
if (headerUser) {
const img = headerUser.querySelector('img');
if (img?.alt) {
return { username: img.alt };
}
}
// Method 5: d-header
const anyUserCard = document.querySelector('.d-header [data-user-card]');
if (anyUserCard) {
return { username: anyUserCard.getAttribute('data-user-card') };
}
// Method 6: preload data
const preloadData = document.querySelector('#data-preloaded');
if (preloadData) {
try {
const data = JSON.parse(preloadData.dataset.preloaded || '{}');
const currentUser = JSON.parse(data.currentUser || '{}');
if (currentUser.username) {
return { username: currentUser.username };
}
} catch (e) {}
}
return null;
} catch (e) {
return null;
}
}
// ========== Listen for DOM changes ==========
const listObserver = new MutationObserver((mutations) => {
if (!isRecommendMode || isLoading) return;
const newRows = [];
mutations.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.classList?.contains('topic-list-item')) {
newRows.push(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('.topic-list-item').forEach(row => newRows.push(row));
}
}
});
});
if (newRows.length > 0) {
newRows.forEach(row => {
const id = row.getAttribute('data-topic-id');
if (id && scoreMap[id] === undefined) {
addCalculatingBadge(row);
} else if (scoreMap[id] !== undefined) {
if (scoreMap[id] >= CONFIG.MIN_DISPLAY_SCORE) {
addScoreBadge(row);
} else {
row.style.display = 'none';
}
}
});
clearTimeout(sortTimeout);
sortTimeout = setTimeout(() => processNewTopics(newRows), 500);
}
});
const navObserver = new MutationObserver(() => {
injectRecommendTab();
const tbody = document.querySelector('.topic-list tbody');
if (tbody && !tbody.dataset.observing) {
tbody.dataset.observing = 'true';
listObserver.observe(tbody, { childList: true });
}
});
// ========== Start script ==========
navObserver.observe(document.body, { childList: true, subtree: true });
injectRecommendTab();
console.log('[Recommendation System] Loaded - Integrating X Algorithm and DeepSeek AI');
})();
Here is the effect on the Discourse forum:

