Greasy Fork is available in English.
Find messages with the most reactions. Compact view, precise jump, and auto-clear on chat switch.
// ==UserScript==
// @name Telegram Web Sort by Reactions (Auto-Clear & Jump)
// @name:zh-CN Telegram Web 按回应排序 (自动清空 & 精准跳转)
// @namespace https://gist.github.com/panwanke/c30874ba5a0996be2fff5bcf609efd6f
// @version 1.0.1
// @description Find messages with the most reactions. Compact view, precise jump, and auto-clear on chat switch.
// @description:zh-CN 帮助您查找 Telegram 中回应最多的消息。支持简约面板预览、精准跳转以及切换频道自动清空。
// @author epool
// @license MIT
// @match https://web.telegram.org/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=telegram.org
// @require http://code.jquery.com/jquery-3.3.1.min.js
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// 1. 注入 UI 样式 (极致去图纯文本模式 + 新按钮)
// ==========================================
let style = `
#sBtn {
position: fixed; top: 73px; right: 16px; width: 45px; height: 45px;
padding: 5px; border-radius: 10px; z-index: 9999; opacity: 0.8;
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtGVYSWZJSSoACAAAAAYAEgEDAAEAAAABAAAAGgEFAAEAAABWAAAAGwEFAAEAAABeAAAAKAEDAAEAAAACAAAAEwIDAAEAAAABAAAAaYcEAAEAAABmAAAAAAAAAEgAAAABAAAASAAAAAEAAAAGAACQBwAEAAAAMDIxMAGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA//8AAAKgBAABAAAAQAAAAAOgBAABAAAAQAAAAAAAAABuEYT5AAABkklEQVR4nO2bPU7EMBBGX74OcQIKjgFnYMVPh1YcZ7kMFQeh4g4sghtQYhSJaoWCHduJZ8dPcud8sZ3VTPZJgU6n05nmGngHwsHYA5uCeaXyi7OfWORb4bwS+UkMEXNCgYyUvNz8JIRzhHOEc7TCPccOkFIwVyf8M1LZRHaCsQNccYRdoCmEc4RzhHOEc4RzhHOEc7TCPWN9QDO+IBR+E0zxAdV9wWDAB1DzTVQ4RzhHOEeN+4AmfEFYyQcs4guGiDndBxwzwjnCOcI5wjnCOcI5MuwDFvMFoWEfkO0LhsgDyM1IySvN5PqEc4RzhHNk3Acs4gtCoz6gmC+4Az4qLGDMvMUAHxWfwpI/99k1YKiYb8IW3QPfFZ7+mLnFCLsKB/CIIQbgqeDmny2+Y5wALwU2/wqcYpSzzB79CZxjnAvga8bmx2suG/IBWb5gm9gZxrkPDfqALF+wq1TxQ2MjuzOkVnwzBxDTGeZUfFMHMNUZ5lZ8cwfwV2eIrfimi+AhN78X7DP/5i7lA5r4HqHT6dA0P32R2X1NXS7nAAAAAElFTkSuQmCC") no-repeat center center;
background-size: 32px; background-color: #fff; cursor: pointer;
box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: all 0.2s;
}
#sBtn:hover { opacity: 1; transform: scale(1.05); }
.theme-dark #sBtn { background-color: #2c2c2c; }
#tg-sort-panel {
position: fixed; top: 0; right: -420px; width: 350px; height: 100vh;
background: #ffffff; z-index: 10000; box-shadow: -5px 0 15px rgba(0,0,0,0.1);
transition: right 0.3s ease; display: flex; flex-direction: column;
}
#tg-sort-panel.open { right: 0; }
.theme-dark #tg-sort-panel { background: #212121; color: #fff; border-left: 1px solid #333; }
.tg-panel-header {
padding: 15px 20px; background: #3390ec; color: white; font-weight: bold;
display: flex; justify-content: space-between; align-items: center; font-size: 16px;
}
.tg-panel-actions span { cursor: pointer; font-size: 18px; margin-left: 12px; opacity: 0.8; transition: opacity 0.2s; }
.tg-panel-actions span:hover { opacity: 1; }
.tg-panel-content { flex: 1; overflow-y: auto; padding: 15px 10px; background: #f4f4f5; }
.theme-dark .tg-panel-content { background: #0f0f0f; }
.tg-sorted-msg-wrap {
margin-bottom: 12px; background: #fff; border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); padding: 10px; position: relative;
cursor: pointer; transition: background 0.2s ease; border: 1px solid transparent;
overflow: hidden;
}
.theme-dark .tg-sorted-msg-wrap { background: #181818; }
.tg-sorted-msg-wrap:hover { background: #f0f8ff; border-color: #3390ec; }
.theme-dark .tg-sorted-msg-wrap:hover { background: #2c3e50; }
.tg-rank-badge {
display: inline-block; background: #ff4500; color: #fff;
font-size: 12px; font-weight: bold; padding: 2px 6px;
border-radius: 6px; margin-bottom: 4px;
}
/* 暴力隐藏多媒体及干扰项 */
.tg-sorted-msg-wrap .attachment,
.tg-sorted-msg-wrap .album-item,
.tg-sorted-msg-wrap .media-container,
.tg-sorted-msg-wrap img:not(.media-sticker),
.tg-sorted-msg-wrap video,
.tg-sorted-msg-wrap .media-wrap,
.tg-sorted-msg-wrap .message-media,
.tg-sorted-msg-wrap .ReplyInfo,
.tg-sorted-msg-wrap .stacked-avatars,
.tg-sorted-msg-wrap .bubble-beside-button,
.tg-sorted-msg-wrap .bubble-tail {
display: none !important;
}
/* 文字折叠 */
.tg-sorted-msg-wrap .message,
.tg-sorted-msg-wrap .text-content,
.tg-sorted-msg-wrap .MessageText,
.tg-sorted-msg-wrap .translatable-message {
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
font-size: 13px !important;
color: #555;
line-height: 1.4;
white-space: normal !important;
margin-top: 2px;
}
.theme-dark .tg-sorted-msg-wrap .message,
.theme-dark .tg-sorted-msg-wrap .text-content,
.theme-dark .tg-sorted-msg-wrap .MessageText,
.theme-dark .tg-sorted-msg-wrap .translatable-message { color: #aaa; }
/* 反应按钮缩放 */
.tg-sorted-msg-wrap reaction-element,
.tg-sorted-msg-wrap .reaction {
transform: scale(0.85);
transform-origin: left top;
margin-top: 5px;
}
/* 闪烁高亮动画 */
@keyframes tg-flash {
0% { background-color: rgba(51, 144, 236, 0.4); }
100% { background-color: transparent; }
}
.tg-highlight-msg { animation: tg-flash 2s ease-out; }
`;
$('body').prepend('<style>' + style + '</style>');
// ==========================================
// 2. 构建 UI DOM (新增清空按钮)
// ==========================================
$('body').append("<div id='sBtn' title='Scan and Sort Reactions'></div>");
$('body').append(`
<div id="tg-sort-panel">
<div class="tg-panel-header">
<span>🏆 Top Reactions</span>
<div class="tg-panel-actions">
<span class="tg-panel-clear" title="清空列表">🗑️</span>
<span class="tg-panel-close" title="关闭面板">✖</span>
</div>
</div>
<div class="tg-panel-content">
<div style="padding:20px; text-align:center; color:#888;">暂无数据。请点击左侧按钮进行扫描。</div>
</div>
</div>
`);
// 绑定关闭按钮
$('.tg-panel-close').click(() => $('#tg-sort-panel').removeClass('open'));
// 绑定手动清空按钮
$('.tg-panel-clear').click(function() {
$('.tg-panel-content').html('<div style="padding:20px; text-align:center; color:#888;">已清空。请点击左侧按钮重新扫描当前频道。</div>');
});
// ==========================================
// 3. 智能检测频道切换 (Auto-clear on chat switch)
// ==========================================
let currentChatUrl = window.location.href;
setInterval(() => {
if (window.location.href !== currentChatUrl) {
currentChatUrl = window.location.href;
// 当网址发生变化(切换了频道/群组),自动收起面板并清空数据
if ($('#tg-sort-panel').hasClass('open')) {
$('#tg-sort-panel').removeClass('open');
}
$('.tg-panel-content').html('<div style="padding:20px; text-align:center; color:#888;">检测到频道切换,数据已自动重置。请重新扫描。</div>');
}
}, 1000); // 每秒轻量级检测一次网址
// ==========================================
// 4. 数字转换函数
// ==========================================
$.fn.text2qty = function () {
let counterSpan = $(this).find('.reaction-counter');
let textToParse = counterSpan.length > 0 ? counterSpan.text() : $(this).text();
if (!textToParse) return 0;
let qty = parseFloat(textToParse.replace(/[^0-9.,]/g, '').replace(/[,]/g, '.')) || 0;
if (textToParse.match(/K$/i)) qty *= 1000;
if (textToParse.match(/M$/i)) qty *= 1000000;
if (qty > 0) return qty;
return 0;
};
// ==========================================
// 5. 扫描、提取与跳转逻辑
// ==========================================
$('#sBtn').click(function() {
let msgArray = [];
let messages = $('.bubble, .message-list-item, .Message');
messages.each(function() {
let item = $(this);
let reactSum = 0;
if (item.hasClass('bubble') && item.find('.bubble-content').length === 0) return;
let possibleId1 = item.attr('id');
let possibleId2 = item.attr('data-message-id');
let possibleId3 = item.attr('data-mid');
item.find('reaction-element, .reaction, .Reactions .Button').each(function() {
reactSum += $(this).text2qty();
});
if (reactSum > 0) {
let cloneNode = item.clone().removeAttr('id');
cloneNode.find('reaction-element, .reaction, .Reactions .Button, .replies').css('pointer-events', 'none');
msgArray.push({
score: reactSum,
id1: possibleId1,
id2: possibleId2,
id3: possibleId3,
dom: cloneNode
});
}
});
// 降序排序
msgArray.sort((a, b) => b.score - a.score);
let contentContainer = $('.tg-panel-content');
contentContainer.empty();
if (msgArray.length === 0) {
contentContainer.append('<div style="padding:20px; text-align:center; color:#888;">当前可视区域未扫描到包含互动的消息。<br><br>💡 提示:请先在聊天窗口往上滚动一段距离加载历史记录,然后再点击扫描。</div>');
} else {
msgArray.forEach((msgObj, index) => {
let wrapper = $('<div class="tg-sorted-msg-wrap"></div>');
let badge = $(`<div class="tg-rank-badge">#${index + 1} (♥ ${msgObj.score})</div>`);
wrapper.append(badge);
wrapper.append(msgObj.dom);
wrapper.click(function() {
let targetElement = null;
if (msgObj.id3) targetElement = $(`[data-mid="${msgObj.id3}"]`);
if (!targetElement || !targetElement.length) {
if (msgObj.id1) targetElement = $('#' + $.escapeSelector(msgObj.id1));
}
if (!targetElement || !targetElement.length) {
if (msgObj.id2) targetElement = $(`[data-message-id="${msgObj.id2}"]`);
}
if (targetElement && targetElement.length > 0) {
try {
targetElement.get(0).scrollIntoView({ behavior: 'smooth', block: 'center' });
targetElement.removeClass('tg-highlight-msg');
setTimeout(() => {
targetElement.addClass('tg-highlight-msg');
}, 10);
} catch(e) {
console.error("Jump failed:", e);
}
} else {
alert('⚠️ 消息已被内存清理,请在聊天窗口向上滚动一段距离后重试。');
}
});
contentContainer.append(wrapper);
});
}
$('#tg-sort-panel').addClass('open');
});
})();