// ==UserScript==
// @name Telegram Web Sort Panel + Chart (Зроблено в Україні)
// @namespace https://example.com
// @version 3.2
// @description Панель сортировки, цветной график топ-100 сообщений. Поддержка /a/ и /k/, drag&drop, фильтры, экспорт CSV. 🇺🇦 Зроблено в Україні.
// @match https://web.telegram.org/a/*
// @match https://web.telegram.org/k/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// === Chart.js ===
const chartScript = document.createElement('script');
chartScript.src = 'https://cdn.jsdelivr.net/npm/chart.js';
document.head.appendChild(chartScript);
GM_addStyle(`
#ua-panel {
position: fixed;
top: 100px; right: 20px;
width: 280px;
background: var(--tg-theme-bg-color, #fff);
color: var(--tg-theme-text-color, #000);
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
padding: 8px;
z-index: 99999;
font-family: Arial,sans-serif;
}
#ua-panel-header {
display:flex;align-items:center;justify-content:space-between;
font-weight:bold;
cursor:move;
}
#ua-buttons button {
margin:2px;padding:3px 5px;border:none;border-radius:4px;
background:#0d6efd;color:#fff;cursor:pointer;font-size:12px;
}
#ua-buttons button:hover {opacity:0.8;}
#ua-chart {width:100%;height:200px;}
#ua-hide {
position:absolute;top:2px;right:2px;cursor:pointer;font-weight:bold;
}
`);
const panel = document.createElement('div');
panel.id = 'ua-panel';
panel.innerHTML = `
<div id="ua-panel-header">
<span>🇺🇦 Зроблено в Україні</span>
<span id="ua-hide">✖</span>
</div>
<div id="ua-buttons">
<button data-sort="reactions">По реакциям</button>
<button data-sort="date_desc">Дата ↓</button>
<button data-sort="date_asc">Дата ↑</button>
<button data-sort="media">По медиа</button>
<button data-sort="length">По длине</button>
<button data-action="24h">За 24ч</button>
<button data-action="refresh">Обновить</button>
<button data-action="csv">Экспорт CSV</button>
</div>
<canvas id="ua-chart"></canvas>
`;
document.body.appendChild(panel);
// === drag & drop + сохранение позиции ===
let offsetX, offsetY, isDragging=false;
const savedPos = JSON.parse(localStorage.getItem('uaPanelPos')||'{}');
if(savedPos.left && savedPos.top){
panel.style.left = savedPos.left+'px';
panel.style.top = savedPos.top+'px';
panel.style.right = 'unset';
}
panel.querySelector('#ua-panel-header').addEventListener('mousedown',e=>{
isDragging=true;offsetX=e.clientX-panel.offsetLeft;offsetY=e.clientY-panel.offsetTop;
});
document.addEventListener('mouseup',()=>isDragging=false);
document.addEventListener('mousemove',e=>{
if(isDragging){
panel.style.left=(e.clientX-offsetX)+'px';
panel.style.top=(e.clientY-offsetY)+'px';
panel.style.right='unset';
}
});
window.addEventListener('beforeunload',()=>{
localStorage.setItem('uaPanelPos',JSON.stringify({left:panel.offsetLeft,top:panel.offsetTop}));
});
// === скрыть ===
panel.querySelector('#ua-hide').addEventListener('click',()=>{
panel.style.display='none';
localStorage.setItem('uaPanelHidden','true');
});
if(localStorage.getItem('uaPanelHidden')==='true') panel.style.display='none';
// === Chart.js загрузился ===
chartScript.onload = ()=>init();
function init(){
const ctx=document.getElementById('ua-chart').getContext('2d');
let chart=new Chart(ctx,{type:'bar',data:{labels:[],datasets:[{label:'ТОП-10',data:[],backgroundColor:[]}]}});
function randomColor(){
return `hsl(${Math.floor(Math.random()*360)},70%,50%)`;
}
function collectMessages(){
const msgs=document.querySelectorAll('[class*="message"]');
let arr=[];
msgs.forEach(m=>{
const text=m.innerText||'';
const date=m.querySelector('time')?.getAttribute('datetime')||'';
const reactions=m.querySelectorAll('[class*="reaction"]').length;
const media=m.querySelectorAll('img,video').length;
arr.push({el:m,text,date,reactions,media});
});
return arr;
}
function updateChart(data){
chart.data.labels=data.slice(0,10).map((_,i)=>i+1);
chart.data.datasets[0].data=data.slice(0,10).map(d=>d.value);
chart.data.datasets[0].backgroundColor=data.slice(0,10).map(()=>randomColor());
chart.update();
}
function sortData(type){
let msgs=collectMessages();
let now=Date.now();
if(type==='24h') msgs=msgs.filter(m=>now-(new Date(m.date).getTime())<86400000);
let data=[];
switch(type){
case 'reactions':
data=msgs.map(m=>({value:m.reactions,text:m.text})).sort((a,b)=>b.value-a.value);
break;
case 'date_desc':
data=msgs.map(m=>({value:new Date(m.date).getTime(),text:m.text})).sort((a,b)=>b.value-a.value);
break;
case 'date_asc':
data=msgs.map(m=>({value:new Date(m.date).getTime(),text:m.text})).sort((a,b)=>a.value-b.value);
break;
case 'media':
data=msgs.map(m=>({value:m.media,text:m.text})).sort((a,b)=>b.value-a.value);
break;
case 'length':
data=msgs.map(m=>({value:m.text.length,text:m.text})).sort((a,b)=>b.value-a.value);
break;
default:
data=msgs.map(m=>({value:m.text.length,text:m.text}));
}
updateChart(data);
}
document.querySelectorAll('#ua-buttons button').forEach(btn=>{
btn.addEventListener('click',()=>{
const sort=btn.dataset.sort;
const action=btn.dataset.action;
if(sort) sortData(sort);
if(action==='24h') sortData('24h');
if(action==='refresh') sortData('reactions');
if(action==='csv'){
const msgs=collectMessages();
let csv='Текст;Реакции;Медиа;Дата\n';
msgs.forEach(m=>csv+=`"${m.text.replace(/"/g,'""')}";${m.reactions};${m.media};${m.date}\n`);
const blob=new Blob([csv],{type:'text/csv'});
const a=document.createElement('a');
a.href=URL.createObjectURL(blob);
a.download='telegram_export.csv';
a.click();
}
});
});
const chatContainer=document.querySelector('#column-center')||document.body;
if(chatContainer){
const observer=new MutationObserver(()=>sortData('reactions'));
observer.observe(chatContainer,{childList:true,subtree:true});
}
setTimeout(()=>sortData('reactions'),3000);
}
})();