музыка с саундклауда
// ==UserScript==
// @name @music
// @namespace нету
// @version 0
// @description музыка с саундклауда
// @author жди
// @match *://lolz.live/*
// @match *://zelenka.guru/*
// @match *://lolz.guru/*
// @match *://lolz.market/*
// @match *://zelenka.market/*
// @match *://lzt.market/*
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=soundcloud.com
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant unsafeWindow
// @connect api-v2.soundcloud.com
// ==/UserScript==
(function () {
'use strict';
const AUTOSEND_SETTING_KEY = "music_autosend_enabled";
const ANON_ID_KEY = "soundcloud_anon_id";
const SOUNDCLOUD_CLIENT_ID = 'ROEsbH8bpjaGsqvLX2uk7VjkqxNVlinH';
const SOUNDCLOUD_APP_VERSION = '1779782131';
const SOUNDCLOUD_APP_LOCALE = 'en';
let autoSendEnabled = GM_getValue(AUTOSEND_SETTING_KEY, false);
let lastQuery = '';
let debounceTimer = null;
let suppressObserver = false;
let cmdId = null;
init();
function initObserver() {
const observer = new MutationObserver(() => {
if (suppressObserver) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText.trim();
if (!text.startsWith("@music" + ' ')) {
removeResults();
lastQuery = '';
return;
}
const query = text.slice(("@music" + ' ').length).trim();
if (!query) {
removeResults();
lastQuery = '';
return;
}
if (query === lastQuery && document.querySelector('.music-results-renderer')) {
return;
}
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const currentEditor = document.querySelector('.tiptap.ProseMirror');
if (!currentEditor) return;
const currentText = currentEditor.innerText.trim();
if (!currentText.startsWith("@music" + ' ')) {
removeResults();
lastQuery = '';
return;
}
const currentQuery = currentText.slice(("@music" + ' ').length).trim();
if (!currentQuery) {
removeResults();
lastQuery = '';
return;
}
if (currentQuery === lastQuery && document.querySelector('.music-results-renderer')) {
return;
}
try {
const tracks = await getTracks(currentQuery);
lastQuery = currentQuery;
renderTracks(tracks);
} catch (err) {
console.error('[music] req failed:', err);
removeResults();
}
}, 400);
});
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
function addStyles() {
GM_addStyle(`
.music-results-renderer {
position: absolute;
left: 60px;
bottom: calc(100% + 8px);
z-index: 9999;
pointer-events: none;
}
.music-popup {
pointer-events: auto;
display: flex;
flex-direction: column;
gap: 6px;
width: min(440px, calc(100vw - 80px));
max-height: 700px;
overflow-y: auto;
padding: 8px;
border-radius: 8px;
background: #2b2b2b;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.music-result {
width: 100%;
min-height: 64px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: rgba(255,255,255,0.04);
display: flex;
align-items: center;
gap: 10px;
padding: 6px;
box-sizing: border-box;
}
.music-result img {
width: 54px;
height: 54px;
min-width: 54px;
object-fit: cover;
display: block;
border-radius: 6px;
background: rgba(255,255,255,0.06);
}
.music-info {
min-width: 0;
flex: 1;
}
.music-title {
color: #fff;
font-size: 13px;
font-weight: 700;
line-height: 1.25;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.music-plays {
color: rgba(255,255,255,0.65);
font-size: 12px;
line-height: 1.25;
margin-top: 4px;
}
.music-result:hover {
outline: 2px solid rgba(255,255,255,0.35);
}
`);
}
function registerSetting() {
if (cmdId !== null) {
GM_unregisterMenuCommand(cmdId);
}
const commandTitle = (autoSendEnabled ? "Выключить" : "Включить") + " автоотправку";
cmdId = GM_registerMenuCommand(commandTitle, () => {
autoSendEnabled = !autoSendEnabled;
GM_setValue(AUTOSEND_SETTING_KEY, autoSendEnabled);
registerSetting();
});
}
function removeResults() {
suppressObserver = true;
document.querySelectorAll('.music-results-renderer').forEach(el => el.remove());
queueMicrotask(() => {
suppressObserver = false;
});
}
function renderTracks(tracks) {
removeResults();
if (!tracks || !tracks.length) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const wrapper = document.querySelector('.editor-box-wrapper') || editor.parentElement;
if (!wrapper) return;
if (getComputedStyle(wrapper).position === 'static') {
wrapper.style.position = 'relative';
}
const renderer = document.createElement('div');
renderer.className = 'music-results-renderer';
const popup = document.createElement('div');
popup.className = 'music-popup';
tracks.forEach(track => {
const item = document.createElement('div');
item.className = 'music-result';
const img = document.createElement('img');
img.src = track.artwork;
img.alt = `${track.artist} - ${track.title}`;
img.title = `${track.artist} - ${track.title}`;
const info = document.createElement('div');
info.className = 'music-info';
const title = document.createElement('div');
title.className = 'music-title';
title.textContent = `${track.artist} - ${track.title}`;
const plays = document.createElement('div');
plays.className = 'music-plays';
plays.textContent = track.plays;
info.appendChild(title);
info.appendChild(plays);
item.appendChild(img);
item.appendChild(info);
item.addEventListener('click', () => {
insertTrack(track.url);
removeResults();
});
popup.appendChild(item);
});
renderer.appendChild(popup);
suppressObserver = true;
wrapper.appendChild(renderer);
queueMicrotask(() => {
suppressObserver = false;
});
}
function insertTrack(url) {
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText;
const newText = text.replace(/^@music(?:\s+.*)?$/m, `[MEDIA=soundcloud]${url.replace("https://", "")}[/MEDIA]`);
editor.focus();
editor.innerText = newText;
editor.dispatchEvent(new Event('input', { bubbles: true }));
if (autoSendEnabled) {
setTimeout(() => unsafeWindow.$('[aria-label="send-message"]').trigger('click'), 200);
}
}
function requestJson(url) {
return new Promise((resolve, reject) => {
toggleProgress('PseudoAjaxStart');
GM_xmlhttpRequest({
method: 'GET',
url,
headers: {
'Accept': 'application/json, text/plain, */*'
},
onload: res => {
toggleProgress('PseudoAjaxStop');
if (res.status < 200 || res.status >= 300) {
reject(new Error(`HTTP ${res.status}: ${res.responseText?.slice?.(0, 200) || ''}`));
return;
}
try {
resolve(JSON.parse(res.responseText));
} catch (err) {
reject(err);
}
},
onerror: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
ontimeout: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
onabort: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
}
});
});
}
async function getTracks(query) {
const q = encodeURIComponent(query);
const userId = getAnonId();
const url =
`https://api-v2.soundcloud.com/search` +
`?q=${q}` +
`&facet=model` +
`&user_id=${encodeURIComponent(userId)}` +
`&client_id=${encodeURIComponent(SOUNDCLOUD_CLIENT_ID)}` +
`&limit=20` +
`&offset=0` +
`&linked_partitioning=1` +
`&app_version=${encodeURIComponent(SOUNDCLOUD_APP_VERSION)}` +
`&app_locale=${encodeURIComponent(SOUNDCLOUD_APP_LOCALE)}`;
const data = await requestJson(url);
return parseTracks(data).slice(0, 20);
}
function parseTracks(data) {
const tracks = [];
const seen = new Set();
const collection = Array.isArray(data?.collection) ? data.collection : [];
for (const item of collection) {
const track = normalizeTrack(item);
if (!track) continue;
if (seen.has(track.id || track.url)) continue;
seen.add(track.id || track.url);
tracks.push(track);
}
return tracks;
}
function normalizeTrack(item) {
const t = item?.kind === 'track'
? item
: item?.track?.kind === 'track'
? item.track
: item?.model?.kind === 'track'
? item.model
: null;
if (!t) return null;
const title = cleanText(t.title);
const artist = cleanText(t.user?.username || t.publisher_metadata?.artist || 'Unknown artist');
const url = t.permalink_url;
if (!title || !url) return null;
return {
id: t.id,
title,
artist,
url,
artwork: getArtwork(t),
plays: formatPlays(t.playback_count)
};
}
function getArtwork(track) {
let url =
track.artwork_url ||
track.user?.avatar_url ||
'';
if (!url) {
return 'https://a-v2.sndcdn.com/assets/images/sc-icons/favicon-2cadd14bdb.ico';
}
url = url
.replace('-large.', '-t300x300.')
.replace('-t120x120.', '-t300x300.')
.replace('-small.', '-t300x300.');
if (url.startsWith('//')) url = 'https:' + url;
return url;
}
function formatPlays(count) {
const num = Number(count);
if (!Number.isFinite(num) || num <= 0) {
return '0 прослушиваний';
}
if (num >= 1000000000) {
return `${trimNumber(num / 1000000000)}B прослушиваний`;
}
if (num >= 1000000) {
return `${trimNumber(num / 1000000)}M прослушиваний`;
}
if (num >= 1000) {
return `${trimNumber(num / 1000)}K прослушиваний`;
}
return `${num} прослушиваний`;
}
function trimNumber(value) {
return value.toFixed(value >= 10 ? 0 : 1).replace('.0', '');
}
function cleanText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function getAnonId() {
let id = GM_getValue(ANON_ID_KEY, '');
if (!id) {
id = genAID();
GM_setValue(ANON_ID_KEY, id);
}
return id;
}
function genAID() {
return [
randomDigits(6),
randomDigits(6),
randomDigits(6),
randomDigits(6)
].join('-');
}
function randomDigits(length) {
let out = '';
for (let i = 0; i < length; i++) {
out += Math.floor(Math.random() * 10);
}
return out;
}
function toggleProgress(eventName) {
const $ = unsafeWindow.jQuery || unsafeWindow.$;
if ($) {
$(unsafeWindow.document).trigger(eventName);
}
}
function init() {
initObserver();
addStyles();
registerSetting();
}
})();