diff --git a/blueprints/nodelink/docker-compose.yml b/blueprints/nodelink/docker-compose.yml new file mode 100644 index 000000000..9961fd4d1 --- /dev/null +++ b/blueprints/nodelink/docker-compose.yml @@ -0,0 +1,17 @@ +services: + nodelink: + image: performanc/nodelink:latest + volumes: + - ../files/config.js:/app/config.js + ports: + - "3000:3000" + restart: unless-stopped + environment: + NODELINK_SERVER_PASSWORD: "${NODELINK_SERVER_PASSWORD:-youshallnotpass}" + NODELINK_SERVER_PORT: "${NODELINK_SERVER_PORT:-3000}" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:2333/version"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s \ No newline at end of file diff --git a/blueprints/nodelink/nodelink.png b/blueprints/nodelink/nodelink.png new file mode 100644 index 000000000..8d3aea8a7 Binary files /dev/null and b/blueprints/nodelink/nodelink.png differ diff --git a/blueprints/nodelink/template.toml b/blueprints/nodelink/template.toml new file mode 100644 index 000000000..e8559d420 --- /dev/null +++ b/blueprints/nodelink/template.toml @@ -0,0 +1,379 @@ +[variables] +main_domain = "${domain}" +server_port = "3000" +nodelink_server_password = "${password}" + +[config] +[[config.mounts]] +filePath = "./config.js" +content = """ +export default { + server: { + host: '0.0.0.0', + port: 3000, + password: 'youshallnotpass', + useBunServer: false // set to true to use Bun.serve websocket (experimental) + }, + cluster: { + enabled: true, // active cluster (or use env CLUSTER_ENABLED) + workers: 0, // 0 => uses os.cpus().length, or specify a number (1 = 2 processes total: master + 1 worker) + minWorkers: 1, // Minimum workers to keep alive (improves availability during bursts) + specializedSourceWorker: { + enabled: true, // If true, source loading (search, lyrics, etc.) is delegated to dedicated workers to prevent voice worker lag + count: 1, // Number of separate process clusters for source operations + microWorkers: 2, // Number of worker threads per process cluster + tasksPerWorker: 32, // Number of parallel tasks each micro-worker can handle before queuing + silentLogs: true // If true, micro-workers will only log warnings and errors + }, + commandTimeout: 6000, // Timeout for heavy operations like loadTracks (6s) + fastCommandTimeout: 4000, // Timeout for player commands like play/pause (4s) + maxRetries: 2, // Number of retry attempts on timeout or worker failure + hibernation: { + enabled: true, + timeoutMs: 1200000 + }, + scaling: { + //scaling configurations + maxPlayersPerWorker: 20, // Reference capacity for utilization calculation + targetUtilization: 0.7, // Target utilization for scaling up/down + scaleUpThreshold: 0.75, // Utilization threshold to scale up + scaleDownThreshold: 0.3, // Utilization threshold to scale down + checkIntervalMs: 5000, // Interval to check for scaling needs + idleWorkerTimeoutMs: 60000, // Time in ms an idle worker should wait before being removed + queueLengthScaleUpFactor: 5, // How many commands in queue per active worker trigger scale up + lagPenaltyLimit: 60, // Event loop lag threshold (ms) to penalize worker cost + cpuPenaltyLimit: 0.85 // CPU usage threshold (85% of a core) to force scale up + }, + endpoint: { + patchEnabled: true, + allowExternalPatch: false, + code: 'CAPYBARA' + } + }, + logging: { + level: 'debug', + file: { + enabled: false, + path: 'logs', + rotation: 'daily', + ttlDays: 7 + }, + debug: { + all: false, + request: true, + session: true, + player: true, + filters: true, + sources: true, + lyrics: true, + youtube: true, + 'youtube-cipher': true + } + }, + connection: { + logAllChecks: false, + interval: 300000, // 5 minutes + timeout: 10000, // 10 seconds + thresholds: { + bad: 1, // Mbps + average: 5 // Mbps + } + }, + maxSearchResults: 10, + maxAlbumPlaylistLength: 100, + playerUpdateInterval: 2000, + statsUpdateInterval: 30000, + trackStuckThresholdMs: 10000, + zombieThresholdMs: 60000, + enableHoloTracks: false, + enableTrackStreamEndpoint: false, + enableLoadStreamEndpoint: false, + resolveExternalLinks: false, + fetchChannelInfo: false, + filters: { + enabled: { + tremolo: true, + vibrato: true, + lowpass: true, + highpass: true, + rotation: true, + karaoke: true, + distortion: true, + channelMix: true, + equalizer: true, + chorus: true, + compressor: true, + echo: true, + phaser: true, + timescale: true + } + }, + defaultSearchSource: ['youtube', 'soundcloud'], + unifiedSearchSources: ['youtube', 'soundcloud'], + sources: { + vkmusic: { + enabled: true, + userToken: '', // (optional) get from vk in browser devtools -> reqs POST /?act=web_token HTTP/2 - headers -> response -> access_token + userCookie: '' // (required without userToken) get from vk in browser devtools -> reqs POST /?act=web_token HTTP/2 - headers -> request -> cookie (copy full cookie header) + }, + amazonmusic: { + enabled: true + }, + mixcloud: { + enabled: true + }, + audiomack: { + enabled: true + }, + deezer: { + // arl: '', + // decryptionKey: '', + enabled: true + }, + bandcamp: { + enabled: true + }, + soundcloud: { + enabled: true, + // clientId: "" + }, + local: { + enabled: true, + basePath: './local-music/' + }, + http: { + enabled: true + }, + vimeo: { + // Note: not 100% of the songs are currently working (but most should.), because i need to code a different extractor for every year (2010, 2011, etc. not all are done) + enabled: true, + }, + telegram: { + enabled: true + }, + shazam: { + enabled: true, + allowExplicit: true + }, + bilibili: { + enabled: true, + sessdata: '' // Optional, improves access to some videos (premium and 4k+) + }, + genius: { + enabled: true + }, + pinterest: { + enabled: true + }, + flowery: { + enabled: true, + voice: 'Salli', + translate: false, + silence: 0, + speed: 1.0, + enforceConfig: false + }, + jiosaavn: { + enabled: true, + playlistLoadLimit: 50, + artistLoadLimit: 20 + // "secretKey": "38346591" // Optional, defaults to standard key + }, + gaana: { + enabled: true, + apiUrl: 'https://gaana.1lucas1apk.fun/api', // if you want to host your server https://github.com/notdeltaxd/Gaana-API + streamQuality: 'high', + playlistLoadLimit: 100, + albumLoadLimit: 100, + artistLoadLimit: 100 + }, + "google-tts": { + enabled: true, + language: 'en-US' + }, + youtube: { + enabled: true, + allowItag: [], // additional itags for audio streams, e.g., [140, 141] + targetItag: null, // force a specific itag for audio streams, overriding the quality option + getOAuthToken: false, + hl: 'en', + gl: 'US', + clients: { + search: ['Android'], // Clients used for searching tracks + playback: ['AndroidVR', 'TV', 'TVEmbedded', 'IOS'], // Clients used for playback/streaming + resolve: ['AndroidVR', 'TV', 'TVEmbedded', 'IOS', 'Web'], // Clients used for resolving detailed track information (channel, external links, etc.) + settings: { + TV: { + refreshToken: [""] // You can use a string "token" or an array ["token1", "token2"] for rotation/fallback + } + } + }, + cipher: { + url: 'https://cipher.kikkia.dev/api', + token: null + } + }, + instagram: { + enabled: true + }, + kwai: { + enabled: true + }, + twitch: { + enabled: true + }, + spotify: { + enabled: true, + clientId: '', + clientSecret: '', + externalAuthUrl: 'http://get.1lucas1apk.fun/spotify/gettoken', // URL to external token provider (e.g. http://localhost:8080/api/token - use https://github.com/topi314/spotify-tokener or https://github.com/1Lucas1apk/gettoken) + market: 'US', + playlistLoadLimit: 1, // 0 means no limit (loads all tracks), 1 = 100 tracks, 2 = 100 and so on! + playlistPageLoadConcurrency: 10, // How many pages to load simultaneously + albumLoadLimit: 1, // 0 means no limit (loads all tracks), 1 = 50 tracks, 2 = 100 tracks, etc. + albumPageLoadConcurrency: 5, // How many pages to load simultaneously + allowExplicit: true // If true plays the explicit version of the song, If false plays the Non-Explicit version of the song. Normal songs are not affected. + }, + applemusic: { + enabled: true, + mediaApiToken: 'token_here', //manually | or "token_here" to get a token automatically + market: 'US', + playlistLoadLimit: 0, + albumLoadLimit: 0, + playlistPageLoadConcurrency: 5, + albumPageLoadConcurrency: 5, + allowExplicit: true + }, + tidal: { + enabled: true, + token: 'token_here', //manually | or "token_here" to get a token automatically, get from tidal web player devtools; using login google account + countryCode: 'US', + playlistLoadLimit: 2, // 0 = no limit, 1 = 50 tracks, 2 = 100 tracks, etc. + playlistPageLoadConcurrency: 5 // How many pages to load simultaneously + }, + pandora: { + enabled: true, + // Optional, setting this manually can help unblocking countries (since pandora is US only.). May need to be updated periodically. + // fetching manually: use a vpn connected to US, go on pandora.com, open devtools, Network tab, first request to appear and copy the 2nd csrfToken= value. + // csrfToken: '', + remoteTokenUrl: 'https://get.1lucas1apk.fun/pandora/gettoken' // URL to a remote provider that returns { success: true, authToken: "...", csrfToken: "...", expires_in_seconds: ... } //https://github.com/1Lucas1apk/gettoken + }, + nicovideo: { + enabled: true + }, + reddit: { + enabled: true + }, + lastfm: { + enabled: true + } + }, + lyrics: { + fallbackSource: 'genius', + youtube: { + enabled: true + }, + genius: { + enabled: true + }, + musixmatch: { + enabled: true + // signatureSecret: '' + }, + lrclib: { + enabled: true + }, + bilibili: { + enabled: true + }, + applemusic: { + enabled: true, + advanceSearch: true // Uses YTMusic to fetch the correct title and artists instead of relying on messy YouTube video titles, improving lyrics accuracy + } + }, + audio: { + quality: 'high', // high, medium, low, lowest + encryption: 'aead_aes256_gcm_rtpsize', + resamplingQuality: 'best' // best, medium, fastest, zero order holder, linear + }, + voiceReceive: { + enabled: false, + format: 'opus' // pcm_s16le, opus + }, + routePlanner: { + strategy: 'RotateOnBan', // RotateOnBan, RoundRobin, LoadBalance + bannedIpCooldown: 600000, // 10 minutes + ipBlocks: [] + }, + rateLimit: { + enabled: true, + global: { + maxRequests: 1000, + timeWindowMs: 60000 // 1 minute + }, + perIp: { + maxRequests: 100, + timeWindowMs: 10000 // 10 seconds + }, + perUserId: { + maxRequests: 50, + timeWindowMs: 5000 // 5 seconds + }, + perGuildId: { + maxRequests: 20, + timeWindowMs: 5000 // 5 seconds + }, + ignorePaths: [], + ignore: { + userIds: [], + guildIds: [], + ips: [] + } + }, + dosProtection: { + enabled: true, + thresholds: { + burstRequests: 50, + timeWindowMs: 10000 // 10 seconds + }, + mitigation: { + delayMs: 500, + blockDurationMs: 300000 // 5 minutes + }, + ignore: { + userIds: [], + guildIds: [], + ips: [] + } + }, + metrics: { + enabled: true, + authorization: { + type: 'Bearer', // Bearer or Basic. + password: '' // If empty, uses server.password + } + }, + mix: { + enabled: true, + defaultVolume: 0.8, + maxLayersMix: 5, + autoCleanup: true + }, + plugins: [ + /* { + name: 'nodelink-sample-plugin', + source: 'local' + } */ + ], + pluginConfig: {} +} +""" + +[[config.domains]] +serviceName = "nodelink" +port = 3_000 +domain = "${main_domain}" + +[config.env] +NODELINK_SERVER_PASSWORD = "${nodelink_server_password}" +NODELINK_SERVER_PORT = "${server_port}" \ No newline at end of file diff --git a/meta.json b/meta.json index 12173567a..a527d7db1 100644 --- a/meta.json +++ b/meta.json @@ -1307,8 +1307,8 @@ "logo": "cloudflared.svg", "links": { "github": "https://github.com/cloudflare/cloudflared", - "website": "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/", - "docs": "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/" + "website": "https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/", + "docs": "https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/" }, "tags": [ "cloud", @@ -4306,6 +4306,23 @@ "nocode" ] }, + { + "id": "nodelink", + "name": "NodeLink", + "version": "latest", + "description": "NodeLink is a modern Lavalink alternative built entirely in Node.js.", + "links": { + "github": "https://github.com/PreformanC/NodeLink", + "website": "https://nodelink.js.org", + "docs": "https://nodelink.js.org/docs" + }, + "logo": "nodelink.png", + "tags": [ + "music", + "discord", + "audio" + ] + }, { "id": "notifuse", "name": "Notifuse",