// ==UserScript==
// @name Telegram Web Sort Panel + Chart + Lang (Зроблено в Україні)
// @namespace https://example.com
// @version 3.3
// @description Панель сортировки, график топ-100 сообщений + языковая панель. 🇺🇦 Зроблено в Україні.
// @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: 300px;
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, #ua-lang select {
margin:2px;padding:3px 5px;border:none;border-radius:4px;
background:#0d6efd;color:#fff;cursor:pointer;font-size:12px;
}
#ua-buttons button:hover, #ua-lang select:hover {opacity:0.8;}
#ua-chart {width:100%;height:200px;}
#ua-hide {
position:absolute;top:2px;right:2px;cursor:pointer;font-weight:bold;
}
#ua-lang {
margin-top:5px;
}
`);
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>
<div id="ua-lang">
<select>
<option value="ua">Українська</option>
<option value="ru">Русский</option>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="it">Italiano</option>
<option value="pt">Português</option>
<option value="pl">Polski</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ko">한국어</option>
</select>
</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 langSelect = panel.querySelector('#ua-lang select');
langSelect.addEventListener('change',()=>{
const lang = langSelect.value;
localStorage.setItem('uaPanelLang', lang);
applyLang(lang);
});
const savedLang = localStorage.getItem('uaPanelLang') || 'ua';
langSelect.value = savedLang;
applyLang(savedLang);
function applyLang(lang){
const translations = {
ua: { reactions:'За реакціями', date_desc:'Дата ↓', date_asc:'Дата ↑', media:'По медіа', length:'По довжині', '24h':'За 24г', refresh:'Оновити', csv:'Експорт CSV' },
ru: { reactions:'По реакциям', date_desc:'Дата ↓', date_asc:'Дата ↑', media:'По медиа', length:'По длине', '24h':'За 24ч', refresh:'Обновить', csv:'Экспорт CSV' },
en: { reactions:'By reactions', date_desc:'Date ↓', date_asc:'Date ↑', media:'By media', length:'By length', '24h':'Last 24h', refresh:'Refresh', csv:'Export CSV' },
de: { reactions:'Nach Reaktionen', date_desc:'Datum ↓', date_asc:'Datum ↑', media:'Nach Medien', length:'Nach Länge', '24h':'Letzte 24h', refresh:'Aktualisieren', csv:'CSV exportieren' },
fr: { reactions:'Par réactions', date_desc:'Date ↓', date_asc:'Date ↑', media:'Par médias', length:'Par longueur', '24h':'24h', refresh:'Rafraîchir', csv:'Exporter CSV' },
es: { reactions:'Por reacciones', date_desc:'Fecha ↓', date_asc:'Fecha ↑', media:'Por medios', length:'Por longitud', '24h':'Últimas 24h', refresh:'Actualizar', csv:'Exportar CSV' },
it: { reactions:'Per reazioni', date_desc:'Data ↓', date_asc:'Data ↑', media:'Per media', length:'Per lunghezza', '24h':'Ultime 24h', refresh:'Aggiorna', csv:'Esporta CSV' },
pt: { reactions:'Por reações', date_desc:'Data ↓', date_asc:'Data ↑', media:'Por mídia', length:'Por comprimento', '24h':'Últimas 24h', refresh:'Atualizar', csv:'Exportar CSV' },
pl: { reactions:'Według reakcji', date_desc:'Data ↓', date_asc:'Data ↑', media:'Według mediów', length:'Według długości', '24h':'Ostatnie 24h', refresh:'Odśwież', csv:'Eksport CSV' },
zh: { reactions:'按反应', date_desc:'日期 ↓', date_asc:'日期 ↑', media:'按媒体', length:'按长度', '24h':'24小时内', refresh:'刷新', csv:'导出 CSV' },
ja: { reactions:'リアクション順', date_desc:'日付 ↓', date_asc:'日付 ↑', media:'メディア順', length:'長さ順', '24h':'過去24時間', refresh:'更新', csv:'CSVエクスポート' },
ko: { reactions:'반응순', date_desc:'날짜 ↓', date_asc:'날짜 ↑', media:'미디어순', length:'길이순', '24h':'최근 24시간', refresh:'새로고침', csv:'CSV 내보내기' }
};
document.querySelectorAll('#ua-buttons button').forEach(btn=>{
const key = btn.dataset.sort || btn.dataset.action;
if(translations[lang][key]) btn.textContent = translations[lang][key];
});
}
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);
}
})();