├── .gitignore ├── src ├── RPC │ ├── index.js │ └── Client.js ├── util │ └── scripts │ │ ├── index.js │ │ ├── infos.js │ │ └── content_script.js ├── Electron │ ├── index.js │ └── BrowserWindow.js └── index.js ├── assets ├── icon.png └── iconmac.png ├── .travis.yml ├── SECURITY.md ├── package.json ├── .all-contributorsrc ├── .github └── workflows │ └── codeql-analysis.yml ├── README.md ├── .eslintrc └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.vscode 3 | /dist -------------------------------------------------------------------------------- /src/RPC/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Client: require('./Client') 3 | } 4 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V0l-D/Discord-Netflix/HEAD/assets/icon.png -------------------------------------------------------------------------------- /src/util/scripts/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | infos: require('./infos') 3 | } 4 | -------------------------------------------------------------------------------- /assets/iconmac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V0l-D/Discord-Netflix/HEAD/assets/iconmac.png -------------------------------------------------------------------------------- /src/Electron/index.js: -------------------------------------------------------------------------------- 1 | const { app, Notification } = require('electron') 2 | const BrowserWindow = require('./BrowserWindow') 3 | 4 | module.exports = { app, BrowserWindow, Notification } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | env: 5 | global: 6 | - ELECTRON_CACHE=$HOME/.cache/electron 7 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 8 | os: 9 | - linux 10 | - osx 11 | cache: 12 | directories: 13 | - node_modules 14 | - $HOME/.cache/electron 15 | - $HOME/.cache/electron-builder 16 | - $HOME/.npm/_prebuilds 17 | script: 18 | - npm install 19 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm run buildlin; 20 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then npm run buildwin; 21 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm run buildmac; -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # 🔐 Security Policy 2 | 3 | ## ✅ Supported Versions 4 | 5 | Only the following versions are actively maintained and receive security updates and critical fixes: 6 | 7 | | Version | Status | 8 | |---------|----------------------| 9 | | 1.2.x | ✅ Supported | 10 | | 1.1.x | ❌ No longer supported | 11 | | 1.0.x | ❌ No longer supported | 12 | 13 | --- 14 | 15 | ## 🐛 Reporting a Vulnerability 16 | 17 | If you discover a security vulnerability, please report it responsibly by contacting a developer through our [Discord server](https://discord.gg/kbf8EjpxbU). 18 | 19 | We typically review and address reports within a few hours to a few days. If the issue stems from an external dependency (e.g., an npm package), we will update it as soon as possible. 20 | 21 | Thank you for helping keep the project secure! 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Netflix", 3 | "version": "1.2.18", 4 | "description": "Discord-Netflix", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "macbuild": "electron-builder -m", 9 | "linbuild": "electron-builder -l", 10 | "winbuild": "electron-builder -w" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+ssh://git@github.com/V0l-D/Discord-Netflix.git" 15 | }, 16 | "author": "V0l-D", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/V0l-D/Discord-Netflix/issues" 20 | }, 21 | "build": { 22 | "appId": "Discord-Netflix", 23 | "win": { 24 | "target": "nsis-web", 25 | "icon": "./assets/icon.png" 26 | }, 27 | "linux": { 28 | "target": "AppImage", 29 | "icon": "./assets/icon.png" 30 | }, 31 | "mac": { 32 | "target": "dmg", 33 | "icon": "./assets/iconmac.png" 34 | }, 35 | "artifactName": "${productName}_Setup_${version}.${ext}", 36 | "electronVersion": "37.1.0+wvcus", 37 | "electronDownload": { 38 | "version": "37.1.0+wvcus", 39 | "mirror": "https://github.com/castlabs/electron-releases/releases/download/v" 40 | } 41 | }, 42 | "homepage": "https://github.com/V0l-D/Discord-Netflix#readme", 43 | "dependencies": { 44 | "@xhayper/discord-rpc": "^1.3.0", 45 | "axios": "^1.12.0", 46 | "electron-discord-register": "1.0.0", 47 | "jquery": "^3.7.1", 48 | "moment": "2.30.1", 49 | "setimmediate": "^1.0.5", 50 | "socket.io-client": "^4.8.1" 51 | }, 52 | "devDependencies": { 53 | "electron-builder": "^25.0.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, Notification } = require('./Electron') 2 | const { Client } = require('./RPC') 3 | const path = require('path') 4 | const { components, nativeImage } = require('electron') 5 | 6 | app.setAppUserModelId('Discord-Netflix') 7 | 8 | const icons = { 9 | win32: nativeImage.createFromPath(path.join(__dirname, `../assets/icon.png`)), 10 | linux: nativeImage.createFromPath(path.join(__dirname, `../assets/icon.png`)), 11 | darwin: nativeImage.createFromPath(path.join(__dirname, `../assets/iconmac.png`)) 12 | } 13 | const icon = process.platform === 'win32' ? icons.win32 : process.platform === 'darwin' ? icons.darwin : icons.linux 14 | const clientId = '868487355114323968' 15 | 16 | let mainWindow 17 | const rpc = new Client({ transport: 'ipc', clientId }) 18 | 19 | //|| Fix for Linux systems ||\\ 20 | if (process.platform === 'linux') { 21 | app.commandLine.appendSwitch('--no-sandbox'); 22 | } 23 | 24 | rpc.on('ready', () => { 25 | mainWindow.checkNetflix() 26 | setInterval(mainWindow.checkNetflix.bind(mainWindow), 15E3) 27 | }) 28 | 29 | app.on('ready', () => { 30 | mainWindow = new BrowserWindow({ 31 | rpc, 32 | icon 33 | }) 34 | 35 | app.whenReady().then(() => { 36 | app.emit('rpc') 37 | }) 38 | mainWindow.maximize() 39 | mainWindow.loadURL('https://netflix.com/browse'); 40 | }) 41 | 42 | components.whenReady(); 43 | 44 | app.on('window-all-closed', () => { 45 | app.quit() 46 | }) 47 | 48 | app.on('rpc', () => { 49 | rpc.start().then(() => { 50 | }).catch(e => { 51 | let notification = new Notification({ 52 | title: 'Could not connect to Discord', 53 | body: 'Click here to try again', 54 | icon 55 | }) 56 | notification.show() 57 | notification.on('click', () => app.emit('rpc')) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "discord-netflix", 3 | "projectOwner": "V0l-D", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "types": { 12 | "help": { 13 | "symbol": "💡", 14 | "description": "Support", 15 | "link": "" 16 | } 17 | }, 18 | "contributors": [ 19 | { 20 | "login": "V0l-D", 21 | "name": "V0l-D", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/35117713?v=4", 23 | "profile": "https://github.com/V0l-D", 24 | "contributions": [ 25 | "code", 26 | "design" 27 | ] 28 | }, 29 | { 30 | "login": "nirewen", 31 | "name": "Eduardo Londero", 32 | "avatar_url": "https://avatars1.githubusercontent.com/u/8761479?v=4", 33 | "profile": "https://github.com/nirewen", 34 | "contributions": [ 35 | "code", 36 | "design" 37 | ] 38 | }, 39 | { 40 | "login": "Wist9063", 41 | "name": "Wist9063", 42 | "avatar_url": "https://avatars0.githubusercontent.com/u/22089269?v=4", 43 | "profile": "https://hexaplexsoftware.ga/", 44 | "contributions": [ 45 | "code", 46 | "design" 47 | ] 48 | }, 49 | { 50 | "login": "Keyygan", 51 | "name": "Keegan", 52 | "avatar_url": "https://avatars1.githubusercontent.com/u/27071605?v=4", 53 | "profile": "https://keyygan.me", 54 | "contributions": [ 55 | "code" 56 | ] 57 | }, 58 | { 59 | "login": "dmfj", 60 | "name": "Dominic Fitch-Jones", 61 | "avatar_url": "https://avatars2.githubusercontent.com/u/13137236?v=4", 62 | "profile": "https://github.com/dmfj", 63 | "contributions": [ 64 | "help" 65 | ] 66 | }, 67 | { 68 | "login": "NovusTheory", 69 | "name": "NovusTheory", 70 | "avatar_url": "https://avatars0.githubusercontent.com/u/3434404?v=4", 71 | "profile": "https://modulobot.xyz", 72 | "contributions": [ 73 | "code" 74 | ] 75 | }, 76 | { 77 | "login": "Maik", 78 | "name": "Maik", 79 | "avatar_url": "https://cdn.discordapp.com/avatars/177405097129672704/eb201db337fc6cfd343a8c90d979e8cd.png?size=1024", 80 | "profile": "#", 81 | "contributions": [ 82 | "video", 83 | "tutorial" 84 | ] 85 | } 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '26 4 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /src/RPC/Client.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('@xhayper/discord-rpc'); 2 | const util = require('util'); 3 | const sleep = util.promisify(setTimeout); 4 | const { ActivityType } = require('discord-api-types/v10'); 5 | 6 | module.exports = class RPCClient extends Client { 7 | constructor({ clientId }) { 8 | super({ clientId, transport: 'ipc' }); 9 | 10 | this.clientId = clientId; 11 | this.currentState = null; 12 | this.ready = false; 13 | 14 | this.on('connected', () => console.log('[RPC] Connected to Discord')); 15 | this.on('ready', () => { 16 | console.log('[RPC] RPC Ready'); 17 | this.ready = true; 18 | }); 19 | this.on('disconnected', () => { 20 | console.log('[RPC] Disconnected from Discord'); 21 | this.ready = false; 22 | }); 23 | } 24 | 25 | /** 26 | * Attempts to start the RPC connection, retrying up to 3 times. 27 | */ 28 | async start(tries = 0) { 29 | if (tries >= 3) throw new Error('Too many tries to connect to Discord'); 30 | 31 | try { 32 | await this.login(); 33 | } catch (error) { 34 | console.error(`[RPC] Login failed (attempt ${tries + 1}):`, error.message); 35 | await sleep(10000); 36 | return this.start(tries + 1); 37 | } 38 | } 39 | 40 | /** 41 | * Sets the Discord Rich Presence activity to "Watching". 42 | * Handles paused state and media duration timestamps. 43 | */ 44 | async setWatchingActivity({ 45 | title, 46 | state, 47 | avatar, 48 | userName, 49 | paused, 50 | elapsedMs, 51 | durationMs, 52 | buttons, 53 | }) { 54 | if (!this.ready || !this.user) { 55 | console.warn('[RPC] Cannot set activity: not ready or user unavailable'); 56 | return; 57 | } 58 | 59 | const now = Date.now(); 60 | 61 | const activity = { 62 | type: ActivityType.Watching, 63 | details: title || 'Watching Netflix', 64 | state: paused 65 | ? `Paused${state ? ` • ${state}` : ''}` 66 | : state || '', 67 | largeImageKey: 'netflix', 68 | largeImageText: `${title || 'Netflix'} on Netflix`, 69 | smallImageKey: avatar || undefined, 70 | smallImageText: userName || undefined, 71 | instance: false, 72 | buttons: buttons?.length ? buttons : undefined, 73 | }; 74 | 75 | if (!paused && typeof elapsedMs === 'number') { 76 | const start = now - elapsedMs; 77 | activity.startTimestamp = new Date(start); 78 | 79 | if (typeof durationMs === 'number') { 80 | activity.endTimestamp = new Date(start + durationMs); 81 | } 82 | } 83 | 84 | this.currentState = activity; 85 | 86 | try { 87 | await this.user.setActivity(activity); 88 | } catch (err) { 89 | console.error('[RPC] Failed to set activity:', err); 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/Electron/BrowserWindow.js: -------------------------------------------------------------------------------- 1 | const Electron = require('electron'); 2 | const scripts = require('../util/scripts'); 3 | const path = require('path'); 4 | const crypto = require('crypto'); 5 | 6 | function normalizeTime(time) { 7 | if (time === undefined || time === null || isNaN(time)) return 0; 8 | return time > 100000 ? time / 1000 : time; 9 | } 10 | 11 | function md5(string) { 12 | return crypto.createHash('md5').update(string).digest('hex'); 13 | } 14 | 15 | module.exports = class BrowserWindow extends Electron.BrowserWindow { 16 | constructor({ title, icon, rpc }) { 17 | super({ 18 | backgroundColor: '#141414', 19 | useContentSize: false, 20 | autoHideMenuBar: true, 21 | resizable: true, 22 | center: true, 23 | fullscreenable: true, 24 | alwaysOnTop: false, 25 | title, 26 | icon, 27 | webPreferences: { 28 | nodeIntegration: false, 29 | contextIsolation: true, 30 | sandbox: false, 31 | plugins: true, 32 | preload: path.join(__dirname, '../util/scripts/content_script.js'), 33 | }, 34 | }); 35 | 36 | this.rpc = rpc; 37 | this.browsingStart = null; 38 | } 39 | 40 | eval(code) { 41 | return this.webContents.executeJavaScript(code); 42 | } 43 | 44 | getInfos() { 45 | return this.eval(`(${scripts.infos})()`); 46 | } 47 | 48 | async checkNetflix() { 49 | try { 50 | const infos = await this.getInfos(); 51 | if (!infos) return; 52 | 53 | console.log('Buttons payload:', infos.buttons); 54 | 55 | const now = Date.now(); 56 | let elapsedMs = 0; 57 | let startTimestamp; 58 | let endTimestamp; 59 | 60 | const isBrowsing = infos.name === 'Browsing'; 61 | const isPaused = infos.paused; 62 | 63 | if (isBrowsing) { 64 | if (!this.browsingStart) this.browsingStart = now; 65 | elapsedMs = now - this.browsingStart; 66 | 67 | if (!isPaused) { 68 | startTimestamp = new Date(this.browsingStart); 69 | } 70 | } else if (infos.duration && infos.currentTime) { 71 | this.browsingStart = null; 72 | 73 | const currentTimeSec = normalizeTime(infos.currentTime); 74 | const durationSec = normalizeTime(infos.duration); 75 | 76 | elapsedMs = currentTimeSec * 1000; 77 | 78 | if (!isPaused) { 79 | startTimestamp = new Date(now - elapsedMs); 80 | endTimestamp = new Date(startTimestamp.getTime() + durationSec * 1000); 81 | } 82 | } else { 83 | this.browsingStart = null; 84 | } 85 | 86 | console.log('[RPC] Sending elapsedMs:', elapsedMs); 87 | if (startTimestamp) console.log('[RPC] startTimestamp:', startTimestamp); 88 | if (endTimestamp) console.log('[RPC] endTimestamp:', endTimestamp); 89 | 90 | await this.rpc.setWatchingActivity({ 91 | title: infos.title || 'Watching Netflix', 92 | state: infos.state || '', 93 | avatar: infos.avatar ? md5(infos.avatar) : '', 94 | userName: infos.userName || '', 95 | paused: isPaused || false, 96 | elapsedMs, 97 | durationMs: isBrowsing ? undefined : (infos.duration ? normalizeTime(infos.duration) * 1000 : undefined), 98 | // Only include timestamps if not paused 99 | ...(isPaused ? {} : { 100 | startTimestamp, 101 | endTimestamp, 102 | }), 103 | buttons: infos.buttons?.length ? infos.buttons : undefined, 104 | }); 105 | 106 | console.log('[checkNetflix] Sending title:', infos.title); 107 | console.log('[checkNetflix] Sending state:', infos.state); 108 | 109 | } catch (err) { 110 | console.error('[checkNetflix] error:', err); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/util/scripts/infos.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | const pathname = document.location.pathname; 3 | let avatar = ''; 4 | let userName = ''; 5 | 6 | // Try to get Netflix user profile avatar and name 7 | if (typeof netflix !== 'undefined') { 8 | try { 9 | const { userGuid, name } = netflix.reactContext.models.userInfo.data; 10 | avatar = netflix.falcorCache.profiles[userGuid].summary.value.avatarName.split('R|').pop().split('|')[0]; 11 | userName = name; 12 | } catch { 13 | console.warn('[Netflix RPC] User info not found'); 14 | } 15 | } 16 | 17 | // === Browsing === 18 | if (pathname.includes('/browse')) { 19 | return { 20 | name: 'Browsing', 21 | title: 'Browsing', 22 | state: 'In the Catalogs', 23 | avatar, 24 | userName, 25 | }; 26 | } 27 | 28 | // === Title Page === 29 | if (pathname.includes('/title')) { 30 | const title = 31 | document.querySelector('h1.title-title')?.textContent?.trim() || 32 | document.querySelector('.title-info h3')?.textContent?.trim() || 33 | document.title.replace(' - Netflix', '').trim(); 34 | 35 | const subtitle = document.querySelector('.episodeTitle')?.textContent?.trim() || ''; 36 | 37 | return { 38 | name: 'Checking a title', 39 | title, 40 | state: subtitle, 41 | avatar, 42 | userName, 43 | buttons: [], 44 | }; 45 | } 46 | 47 | // === Watching Page === 48 | if (pathname.includes('/watch')) { 49 | try { 50 | const videoEl = document.querySelector(".VideoContainer video") || 51 | document.querySelector(".watch-video--player-view video"); 52 | if (!videoEl) return; 53 | 54 | const { duration, currentTime, paused } = videoEl; 55 | 56 | const titleContainer = document.querySelector('[data-uia="video-title"]'); 57 | if (!titleContainer) return; 58 | 59 | const spans = Array.from(titleContainer.querySelectorAll('span')); 60 | const h4s = Array.from(titleContainer.querySelectorAll('h4')); 61 | 62 | let episodeNumber = ''; 63 | let episodeTitle = ''; 64 | let seriesTitle = ''; 65 | 66 | // Parse episode and title info based on Netflix's DOM layout 67 | if (spans.length >= 3) { 68 | // Typical episode: S1:E1, Title, Show Name 69 | seriesTitle = h4s[0]?.textContent?.trim() || ''; 70 | episodeNumber = spans[0]?.textContent?.trim() || ''; 71 | episodeTitle = spans[1]?.textContent?.trim() || ''; 72 | } else if (spans.length === 2) { 73 | // Fallback for episodes with fewer spans 74 | episodeNumber = spans[0]?.textContent?.trim() || ''; 75 | episodeTitle = spans[1]?.textContent?.trim() || ''; 76 | seriesTitle = h4s[0]?.textContent?.trim() || ''; 77 | } else { 78 | // Movie fallback 79 | const movieName = titleContainer.textContent?.trim() || ''; 80 | seriesTitle = 'Netflix'; 81 | episodeTitle = movieName; 82 | } 83 | 84 | const fullState = (episodeNumber && episodeTitle) 85 | ? `${episodeNumber}: ${episodeTitle}` 86 | : episodeTitle || episodeNumber || ''; 87 | 88 | const idMatch = pathname.match(/\/watch\/(\d+)/); 89 | const id = idMatch ? idMatch[1] : ''; 90 | 91 | return { 92 | title: seriesTitle || document.title.replace(' - Netflix', '').trim(), 93 | state: fullState, 94 | duration, 95 | currentTime, 96 | paused, 97 | avatar, 98 | userName, 99 | buttons: id ? [{ 100 | label: 'Watch on Netflix', 101 | url: `https://www.netflix.com/watch/${id}` 102 | }] : [], 103 | }; 104 | } catch (e) { 105 | console.error('[Netflix RPC] Failed to parse watching data:', e); 106 | } 107 | } 108 | 109 | return null; 110 | }; 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 🎬 Discord-Netflix 4 | **Rich Presence for Netflix in Discord** 5 | _An actively maintained fork of [nirewen's](https://github.com/nirewen) original project._ 6 | 7 | [![Join our Discord](https://img.shields.io/discord/868546947953356860?color=5865F2&logo=discord&logoColor=white)](https://discord.gg/kbf8EjpxbU) 8 | [![Known Vulnerabilities](https://snyk.io/test/github/Terroriser1/Discord-Netflix/badge.svg)](https://snyk.io/test/github/Terroriser1/Discord-Netflix) 9 | [![Downloads](https://img.shields.io/github/downloads/Terroriser1/Discord-Netflix/total?color=green&logo=github)](https://github.com/Terroriser1/Discord-Netflix/releases) 10 | [![License](https://img.shields.io/github/license/Terroriser1/Discord-Netflix?color=blue)](LICENSE) 11 | 12 |
13 | 14 | --- 15 | 16 | ## 🌟 Features 17 | 18 | - 🖥️ 1080p streaming support 19 | - 📌 Picture-in-picture mode 20 | - 🧭 Smooth UI scrolling 21 | - 🎵 Max bitrate playback 22 | - 🧠 Smart Discord Rich Presence 23 | - 👤 Custom user avatars ([Wiki](https://github.com/Terroriser1/Discord-Netflix/wiki/Avatars)) 24 | - 🔒 Transparent [Privacy](https://github.com/Terroriser1/Discord-Netflix/wiki/Privacy) practices 25 | - 🎞️ IMDB cover support ([fork by 0xGingi](https://github.com/0xGingi/Discord-Netflix/tree/IMDB-Cover)) 26 | - 🗺️ [Development Roadmap](https://github.com/Terroriser1/Discord-Netflix/wiki/Roadmap) 27 | 28 | --- 29 | 30 | ## 📥 Download 31 | 32 | You can download a **prebuilt installer** directly from the [releases page](https://github.com/Terroriser1/Discord-Netflix/releases). 33 | 34 | --- 35 | 36 | ## 🛠️ Building It Yourself 37 | 38 | ### Prerequisites 39 | 40 | Make sure you have the following installed: 41 | 42 | - [Node.js](https://nodejs.org/en/) > 7.0.0 43 | - [Git](https://git-scm.com/) (modern version) 44 | - [Python](https://www.python.org/downloads/) > 3.9.0 45 | - [7-Zip](https://www.7-zip.org/) > 19.00 46 | 47 | > ⚠️ **Note:** Python is required to handle the **ECS certificate** for streaming. You must use the **CastLabs Electron fork** for DRM support. 48 | 49 | ### ⚙️ Supported Platforms 50 | 51 | - ✅ Windows 52 | - ✅ Linux 53 | - ✅ macOS 54 | 55 | > 📚 See full instructions in the [Wiki](https://github.com/Terroriser1/Discord-Netflix/wiki) 56 | 57 | --- 58 | 59 | ## 🚧 Roadmap 60 | 61 | - ✅ Fix RPC buttons 62 | - ✅ Fix elapsed time display 63 | - ✅ Clean up legacy code 64 | - ✅ Create official website 65 | - ✅ Fix profile icons in RPC 66 | - ✅ Build macOS executable 67 | - 🟡 Add 4K support 68 | - 🟡 Improve auto-updater 69 | - 🟡 Personalize Rich Presence 70 | - 🟡 Launch Discord bot 71 | - 🟡 Modernize Discord community 72 | 73 | --- 74 | 75 | ## 🖼️ Preview 76 | 77 |
78 | Rich Presence Preview 1 79 | Rich Presence Preview 2 80 |
81 | 82 | 83 | --- 84 | 85 | ## 👥 Contributors 86 | 87 | Thanks to all these awesome contributors: 88 | 89 | 90 | | [
V0l-D](https://github.com/V0l-D) | [
Nirewen](https://github.com/nirewen) | [
Wist9063](https://hexaplexsoftware.ga/) | [
Keyygan](https://keyygan.me) | 91 | | :---: | :---: | :---: | :---: | 92 | | [
Dominic F-J](https://github.com/dmfj) | [
NovusTheory](https://modulobot.xyz) | [
Maik](#) | [
Monochromish](https://github.com/Monochromish) | 93 | | [
0xGingi](https://github.com/0xGingi) | 94 | 95 | 96 | --- 97 | 98 | ## 🙌 Credits 99 | 100 | Shout-out to [@nirewen](https://github.com/nirewen) for the original project. This fork is a love letter to the Netflix community—brought up to date and improved with ❤️ by fans. 101 | 102 | --- 103 | 104 | ## 💬 Join the Community 105 | 106 | Stay up to date, get support, or contribute ideas in our community Discord: 107 | 👉 [Join Here](https://discord.gg/mJYxxeZygw) 108 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2018, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "accessor-pairs": "error", 12 | "arrow-spacing": ["error", { "before": true, "after": true }], 13 | "block-spacing": ["error", "always"], 14 | "brace-style": [0, "1tbs", { "allowSingleLine": true }], 15 | "camelcase": ["error", { "properties": "never" }], 16 | "comma-dangle": ["error", { 17 | "arrays": "never", 18 | "objects": "never", 19 | "imports": "never", 20 | "exports": "never", 21 | "functions": "never" 22 | }], 23 | "comma-spacing": ["error", { "before": false, "after": true }], 24 | "comma-style": ["error", "last"], 25 | "constructor-super": "error", 26 | "dot-location": ["error", "property"], 27 | "eol-last": "error", 28 | "eqeqeq": ["error", "always", { "null": "ignore" }], 29 | "func-call-spacing": ["error", "never"], 30 | "generator-star-spacing": ["error", { "before": true, "after": true }], 31 | "handle-callback-err": ["error", "^(err|error)$" ], 32 | "indent": ["error", 4, { 33 | "SwitchCase": 1, 34 | "VariableDeclarator": 1, 35 | "outerIIFEBody": 1, 36 | "MemberExpression": 1, 37 | "FunctionDeclaration": { "parameters": 1, "body": 1 }, 38 | "FunctionExpression": { "parameters": 1, "body": 1 }, 39 | "CallExpression": { "arguments": 1 }, 40 | "ArrayExpression": 1, 41 | "ObjectExpression": 1, 42 | "ImportDeclaration": 1, 43 | "flatTernaryExpressions": false, 44 | "ignoreComments": false 45 | }], 46 | "keyword-spacing": ["error", { "before": true, "after": true }], 47 | "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], 48 | "new-parens": "error", 49 | "no-array-constructor": "error", 50 | "no-caller": "error", 51 | "no-class-assign": "error", 52 | "no-compare-neg-zero": "error", 53 | "no-const-assign": "error", 54 | "no-constant-condition": ["error", { "checkLoops": false }], 55 | "no-control-regex": "error", 56 | "no-debugger": "error", 57 | "no-delete-var": "error", 58 | "no-dupe-args": "error", 59 | "no-dupe-class-members": "error", 60 | "no-dupe-keys": "error", 61 | "no-duplicate-case": "error", 62 | "no-empty-character-class": "error", 63 | "no-empty-pattern": "error", 64 | "no-ex-assign": "error", 65 | "no-extend-native": "error", 66 | "no-extra-bind": "error", 67 | "no-extra-boolean-cast": "error", 68 | "no-extra-parens": ["error", "functions"], 69 | "no-fallthrough": "error", 70 | "no-floating-decimal": "error", 71 | "no-func-assign": "error", 72 | "no-global-assign": "error", 73 | "no-implied-eval": "error", 74 | "no-inner-declarations": ["error", "functions"], 75 | "no-invalid-regexp": "error", 76 | "no-irregular-whitespace": "error", 77 | "no-iterator": "error", 78 | "no-label-var": "error", 79 | "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], 80 | "no-lone-blocks": "error", 81 | "no-mixed-operators": ["error", { 82 | "groups": [ 83 | ["==", "!=", "===", "!==", ">", ">=", "<", "<="], 84 | ["&&", "||"], 85 | ["in", "instanceof"] 86 | ], 87 | "allowSamePrecedence": true 88 | }], 89 | "no-mixed-spaces-and-tabs": "error", 90 | "no-multi-str": "error", 91 | "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], 92 | "no-negated-in-lhs": "error", 93 | "no-new": "error", 94 | "no-new-func": "error", 95 | "no-new-object": "error", 96 | "no-new-require": "error", 97 | "no-new-symbol": "error", 98 | "no-new-wrappers": "error", 99 | "no-obj-calls": "error", 100 | "no-octal": "error", 101 | "no-octal-escape": "error", 102 | "no-path-concat": "error", 103 | "no-proto": "error", 104 | "no-redeclare": "error", 105 | "no-regex-spaces": "error", 106 | "no-return-assign": ["error", "except-parens"], 107 | "no-return-await": "error", 108 | "no-self-assign": "error", 109 | "no-self-compare": "error", 110 | "no-sequences": "error", 111 | "no-shadow-restricted-names": "error", 112 | "no-sparse-arrays": "error", 113 | "no-tabs": "error", 114 | "no-template-curly-in-string": "error", 115 | "no-this-before-super": "error", 116 | "no-throw-literal": "error", 117 | "no-undef": "error", 118 | "no-undef-init": "error", 119 | "no-unexpected-multiline": "error", 120 | "no-unmodified-loop-condition": "error", 121 | "no-unneeded-ternary": ["error", { "defaultAssignment": false }], 122 | "no-unreachable": "error", 123 | "no-unsafe-finally": "error", 124 | "no-unsafe-negation": "error", 125 | "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], 126 | "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], 127 | "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], 128 | "no-useless-call": "error", 129 | "no-useless-computed-key": "error", 130 | "no-useless-constructor": "error", 131 | "no-useless-escape": "error", 132 | "no-useless-rename": "error", 133 | "no-useless-return": "error", 134 | "no-whitespace-before-property": "error", 135 | "no-with": "error", 136 | "object-curly-spacing": ["error", "always"], 137 | "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], 138 | "one-var": ["error", { "initialized": "never" }], 139 | "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }], 140 | "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], 141 | "prefer-promise-reject-errors": "error", 142 | "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], 143 | "rest-spread-spacing": ["error", "never"], 144 | "semi": ["error", "never"], 145 | "semi-spacing": ["error", { "before": false, "after": true }], 146 | "space-before-blocks": ["error", "always"], 147 | "space-before-function-paren": ["error", "always"], 148 | "space-in-parens": ["error", "never"], 149 | "space-infix-ops": "error", 150 | "space-unary-ops": ["error", { "words": true, "nonwords": false }], 151 | "spaced-comment": ["error", "always", { 152 | "line": { "markers": ["*package", "!", "/", ",", "="] }, 153 | "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } 154 | }], 155 | "symbol-description": "error", 156 | "template-curly-spacing": ["error", "never"], 157 | "template-tag-spacing": ["error", "never"], 158 | "unicode-bom": ["error", "never"], 159 | "use-isnan": "error", 160 | "valid-typeof": ["error", { "requireStringLiterals": true }], 161 | "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], 162 | "yield-star-spacing": ["error", "both"], 163 | "yoda": ["error", "never"] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /src/util/scripts/content_script.js: -------------------------------------------------------------------------------- 1 | //Smoothscroll is kool 2 | (function () { 3 | 4 | // Scroll Variables (tweakable) 5 | var defaultOptions = { 6 | 7 | // Scrolling Core 8 | frameRate : 200, // [Hz] 9 | animationTime : 400, // [ms] 10 | stepSize : 100, // [px] 11 | 12 | // Pulse (less tweakable) 13 | // ratio of "tail" to "acceleration" 14 | pulseAlgorithm : true, 15 | pulseScale : 4, 16 | pulseNormalize : 1, 17 | 18 | // Acceleration 19 | accelerationDelta : 50, // 50 20 | accelerationMax : 3, // 3 21 | 22 | // Keyboard Settings 23 | keyboardSupport : true, // option 24 | arrowScroll : 50, // [px] 25 | 26 | // Other 27 | fixedBackground : true, 28 | excluded : '' 29 | }; 30 | 31 | var options = defaultOptions; 32 | 33 | 34 | // Other Variables 35 | var isExcluded = false; 36 | var isFrame = false; 37 | var direction = { x: 0, y: 0 }; 38 | var initDone = false; 39 | var root = document.documentElement; 40 | var activeElement; 41 | var observer; 42 | var refreshSize; 43 | var deltaBuffer = []; 44 | var deltaBufferTimer; 45 | var isMac = /^Mac/.test(navigator.platform); 46 | 47 | var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 48 | pageup: 33, pagedown: 34, end: 35, home: 36 }; 49 | var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 }; 50 | 51 | /*********************************************** 52 | * INITIALIZE 53 | ***********************************************/ 54 | 55 | /** 56 | * Tests if smooth scrolling is allowed. Shuts down everything if not. 57 | */ 58 | function initTest() { 59 | if (options.keyboardSupport) { 60 | addEvent('keydown', keydown); 61 | } 62 | } 63 | 64 | /** 65 | * Sets up scrolls array, determines if frames are involved. 66 | */ 67 | function init() { 68 | 69 | if (initDone || !document.body) return; 70 | 71 | initDone = true; 72 | 73 | var body = document.body; 74 | var html = document.documentElement; 75 | var windowHeight = window.innerHeight; 76 | var scrollHeight = body.scrollHeight; 77 | 78 | // check compat mode for root element 79 | root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; 80 | activeElement = body; 81 | 82 | initTest(); 83 | 84 | // Checks if this script is running in a frame 85 | if (top != self) { 86 | isFrame = true; 87 | } 88 | 89 | /** 90 | * Safari 10 fixed it, Chrome fixed it in v45: 91 | * This fixes a bug where the areas left and right to 92 | * the content does not trigger the onmousewheel event 93 | * on some pages. e.g.: html, body { height: 100% } 94 | */ 95 | else if (isOldSafari && 96 | scrollHeight > windowHeight && 97 | (body.offsetHeight <= windowHeight || 98 | html.offsetHeight <= windowHeight)) { 99 | 100 | var fullPageElem = document.createElement('div'); 101 | fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' + 102 | 'top:0; left:0; right:0; height:' + 103 | root.scrollHeight + 'px'; 104 | document.body.appendChild(fullPageElem); 105 | 106 | // DOM changed (throttled) to fix height 107 | var pendingRefresh; 108 | refreshSize = function () { 109 | if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh); 110 | pendingRefresh = setTimeout(function () { 111 | if (isExcluded) return; // could be running after cleanup 112 | fullPageElem.style.height = '0'; 113 | fullPageElem.style.height = root.scrollHeight + 'px'; 114 | pendingRefresh = null; 115 | }, 500); // act rarely to stay fast 116 | }; 117 | 118 | setTimeout(refreshSize, 10); 119 | 120 | addEvent('resize', refreshSize); 121 | 122 | // TODO: attributeFilter? 123 | var config = { 124 | attributes: true, 125 | childList: true, 126 | characterData: false 127 | // subtree: true 128 | }; 129 | 130 | observer = new MutationObserver(refreshSize); 131 | observer.observe(body, config); 132 | 133 | if (root.offsetHeight <= windowHeight) { 134 | var clearfix = document.createElement('div'); 135 | clearfix.style.clear = 'both'; 136 | body.appendChild(clearfix); 137 | } 138 | } 139 | 140 | // disable fixed background 141 | if (!options.fixedBackground && !isExcluded) { 142 | body.style.backgroundAttachment = 'scroll'; 143 | html.style.backgroundAttachment = 'scroll'; 144 | } 145 | } 146 | 147 | /** 148 | * Removes event listeners and other traces left on the page. 149 | */ 150 | function cleanup() { 151 | observer && observer.disconnect(); 152 | removeEvent(wheelEvent, wheel); 153 | removeEvent('mousedown', mousedown); 154 | removeEvent('keydown', keydown); 155 | removeEvent('resize', refreshSize); 156 | removeEvent('load', init); 157 | } 158 | 159 | 160 | /************************************************ 161 | * SCROLLING 162 | ************************************************/ 163 | 164 | var que = []; 165 | var pending = false; 166 | var lastScroll = Date.now(); 167 | 168 | /** 169 | * Pushes scroll actions to the scrolling queue. 170 | */ 171 | function scrollArray(elem, left, top) { 172 | 173 | directionCheck(left, top); 174 | 175 | if (options.accelerationMax != 1) { 176 | var now = Date.now(); 177 | var elapsed = now - lastScroll; 178 | if (elapsed < options.accelerationDelta) { 179 | var factor = (1 + (50 / elapsed)) / 2; 180 | if (factor > 1) { 181 | factor = Math.min(factor, options.accelerationMax); 182 | left *= factor; 183 | top *= factor; 184 | } 185 | } 186 | lastScroll = Date.now(); 187 | } 188 | 189 | // push a scroll command 190 | que.push({ 191 | x: left, 192 | y: top, 193 | lastX: (left < 0) ? 0.99 : -0.99, 194 | lastY: (top < 0) ? 0.99 : -0.99, 195 | start: Date.now() 196 | }); 197 | 198 | // don't act if there's a pending queue 199 | if (pending) { 200 | return; 201 | } 202 | 203 | var scrollRoot = getScrollRoot(); 204 | var isWindowScroll = (elem === scrollRoot || elem === document.body); 205 | 206 | // if we haven't already fixed the behavior, 207 | // and it needs fixing for this sesh 208 | if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) { 209 | elem.$scrollBehavior = elem.style.scrollBehavior; 210 | elem.style.scrollBehavior = 'auto'; 211 | } 212 | 213 | var step = function (time) { 214 | 215 | var now = Date.now(); 216 | var scrollX = 0; 217 | var scrollY = 0; 218 | 219 | for (var i = 0; i < que.length; i++) { 220 | 221 | var item = que[i]; 222 | var elapsed = now - item.start; 223 | var finished = (elapsed >= options.animationTime); 224 | 225 | // scroll position: [0, 1] 226 | var position = (finished) ? 1 : elapsed / options.animationTime; 227 | 228 | // easing [optional] 229 | if (options.pulseAlgorithm) { 230 | position = pulse(position); 231 | } 232 | 233 | // only need the difference 234 | var x = (item.x * position - item.lastX) >> 0; 235 | var y = (item.y * position - item.lastY) >> 0; 236 | 237 | // add this to the total scrolling 238 | scrollX += x; 239 | scrollY += y; 240 | 241 | // update last values 242 | item.lastX += x; 243 | item.lastY += y; 244 | 245 | // delete and step back if it's over 246 | if (finished) { 247 | que.splice(i, 1); i--; 248 | } 249 | } 250 | 251 | // scroll left and top 252 | if (isWindowScroll) { 253 | window.scrollBy(scrollX, scrollY); 254 | } 255 | else { 256 | if (scrollX) elem.scrollLeft += scrollX; 257 | if (scrollY) elem.scrollTop += scrollY; 258 | } 259 | 260 | // clean up if there's nothing left to do 261 | if (!left && !top) { 262 | que = []; 263 | } 264 | 265 | if (que.length) { 266 | requestFrame(step, elem, (1000 / options.frameRate + 1)); 267 | } else { 268 | pending = false; 269 | // restore default behavior at the end of scrolling sesh 270 | if (elem.$scrollBehavior != null) { 271 | elem.style.scrollBehavior = elem.$scrollBehavior; 272 | elem.$scrollBehavior = null; 273 | } 274 | } 275 | }; 276 | 277 | // start a new queue of actions 278 | requestFrame(step, elem, 0); 279 | pending = true; 280 | } 281 | 282 | 283 | /*********************************************** 284 | * EVENTS 285 | ***********************************************/ 286 | 287 | /** 288 | * Mouse wheel handler. 289 | * @param {Object} event 290 | */ 291 | function wheel(event) { 292 | 293 | if (!initDone) { 294 | init(); 295 | } 296 | 297 | var target = event.target; 298 | 299 | // leave early if default action is prevented 300 | // or it's a zooming event with CTRL 301 | if (event.defaultPrevented || event.ctrlKey) { 302 | return true; 303 | } 304 | 305 | // leave embedded content alone (flash & pdf) 306 | if (isNodeName(activeElement, 'embed') || 307 | (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) || 308 | isNodeName(activeElement, 'object') || 309 | target.shadowRoot) { 310 | return true; 311 | } 312 | 313 | var deltaX = -event.wheelDeltaX || event.deltaX || 0; 314 | var deltaY = -event.wheelDeltaY || event.deltaY || 0; 315 | 316 | if (isMac) { 317 | if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) { 318 | deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX)); 319 | } 320 | if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) { 321 | deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY)); 322 | } 323 | } 324 | 325 | // use wheelDelta if deltaX/Y is not available 326 | if (!deltaX && !deltaY) { 327 | deltaY = -event.wheelDelta || 0; 328 | } 329 | 330 | // line based scrolling (Firefox mostly) 331 | if (event.deltaMode === 1) { 332 | deltaX *= 40; 333 | deltaY *= 40; 334 | } 335 | 336 | var overflowing = overflowingAncestor(target); 337 | 338 | // nothing to do if there's no element that's scrollable 339 | if (!overflowing) { 340 | // except Chrome iframes seem to eat wheel events, which we need to 341 | // propagate up, if the iframe has nothing overflowing to scroll 342 | if (isFrame && isChrome) { 343 | // change target to iframe element itself for the parent frame 344 | Object.defineProperty(event, "target", {value: window.frameElement}); 345 | return parent.wheel(event); 346 | } 347 | return true; 348 | } 349 | 350 | // check if it's a touchpad scroll that should be ignored 351 | if (isTouchpad(deltaY)) { 352 | return true; 353 | } 354 | 355 | // scale by step size 356 | // delta is 120 most of the time 357 | // synaptics seems to send 1 sometimes 358 | if (Math.abs(deltaX) > 1.2) { 359 | deltaX *= options.stepSize / 120; 360 | } 361 | if (Math.abs(deltaY) > 1.2) { 362 | deltaY *= options.stepSize / 120; 363 | } 364 | 365 | scrollArray(overflowing, deltaX, deltaY); 366 | event.preventDefault(); 367 | scheduleClearCache(); 368 | } 369 | 370 | /** 371 | * Keydown event handler. 372 | * @param {Object} event 373 | */ 374 | function keydown(event) { 375 | 376 | var target = event.target; 377 | var modifier = event.ctrlKey || event.altKey || event.metaKey || 378 | (event.shiftKey && event.keyCode !== key.spacebar); 379 | 380 | // our own tracked active element could've been removed from the DOM 381 | if (!document.body.contains(activeElement)) { 382 | activeElement = document.activeElement; 383 | } 384 | 385 | // do nothing if user is editing text 386 | // or using a modifier key (except shift) 387 | // or in a dropdown 388 | // or inside interactive elements 389 | var inputNodeNames = /^(textarea|select|embed|object)$/i; 390 | var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i; 391 | if ( event.defaultPrevented || 392 | inputNodeNames.test(target.nodeName) || 393 | isNodeName(target, 'input') && !buttonTypes.test(target.type) || 394 | isNodeName(activeElement, 'video') || 395 | isInsideNetflixVideo(event) || 396 | target.isContentEditable || 397 | modifier ) { 398 | return true; 399 | } 400 | 401 | // [spacebar] should trigger button press, leave it alone 402 | if ((isNodeName(target, 'button') || 403 | isNodeName(target, 'input') && buttonTypes.test(target.type)) && 404 | event.keyCode === key.spacebar) { 405 | return true; 406 | } 407 | 408 | // [arrwow keys] on radio buttons should be left alone 409 | if (isNodeName(target, 'input') && target.type == 'radio' && 410 | arrowKeys[event.keyCode]) { 411 | return true; 412 | } 413 | 414 | var shift, x = 0, y = 0; 415 | var overflowing = overflowingAncestor(activeElement); 416 | 417 | if (!overflowing) { 418 | // Chrome iframes seem to eat key events, which we need to 419 | // propagate up, if the iframe has nothing overflowing to scroll 420 | return (isFrame && isChrome) ? parent.keydown(event) : true; 421 | } 422 | 423 | var clientHeight = overflowing.clientHeight; 424 | 425 | if (overflowing == document.body) { 426 | clientHeight = window.innerHeight; 427 | } 428 | 429 | switch (event.keyCode) { 430 | case key.up: 431 | y = -options.arrowScroll; 432 | break; 433 | case key.down: 434 | y = options.arrowScroll; 435 | break; 436 | case key.spacebar: // (+ shift) 437 | shift = event.shiftKey ? 1 : -1; 438 | y = -shift * clientHeight * 0.9; 439 | break; 440 | case key.pageup: 441 | y = -clientHeight * 0.9; 442 | break; 443 | case key.pagedown: 444 | y = clientHeight * 0.9; 445 | break; 446 | case key.home: 447 | if (overflowing == document.body && document.scrollingElement) 448 | overflowing = document.scrollingElement; 449 | y = -overflowing.scrollTop; 450 | break; 451 | case key.end: 452 | var scroll = overflowing.scrollHeight - overflowing.scrollTop; 453 | var scrollRemaining = scroll - clientHeight; 454 | y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0; 455 | break; 456 | case key.left: 457 | x = -options.arrowScroll; 458 | break; 459 | case key.right: 460 | x = options.arrowScroll; 461 | break; 462 | default: 463 | return true; // a key we don't care about 464 | } 465 | 466 | scrollArray(overflowing, x, y); 467 | event.preventDefault(); 468 | scheduleClearCache(); 469 | } 470 | 471 | /** 472 | * Mousedown event only for updating activeElement 473 | */ 474 | function mousedown(event) { 475 | activeElement = event.target; 476 | } 477 | 478 | 479 | /*********************************************** 480 | * OVERFLOW 481 | ***********************************************/ 482 | 483 | var uniqueID = (function () { 484 | var i = 0; 485 | return function (el) { 486 | return el.uniqueID || (el.uniqueID = i++); 487 | }; 488 | })(); 489 | 490 | var cacheX = {}; // cleared out after a scrolling session 491 | var cacheY = {}; // cleared out after a scrolling session 492 | var clearCacheTimer; 493 | var smoothBehaviorForElement = {}; 494 | 495 | //setInterval(function () { cache = {}; }, 10 * 1000); 496 | 497 | function scheduleClearCache() { 498 | clearTimeout(clearCacheTimer); 499 | clearCacheTimer = setInterval(function () { 500 | cacheX = cacheY = smoothBehaviorForElement = {}; 501 | }, 1*1000); 502 | } 503 | 504 | function setCache(elems, overflowing, x) { 505 | var cache = x ? cacheX : cacheY; 506 | for (var i = elems.length; i--;) 507 | cache[uniqueID(elems[i])] = overflowing; 508 | return overflowing; 509 | } 510 | 511 | function getCache(el, x) { 512 | return (x ? cacheX : cacheY)[uniqueID(el)]; 513 | } 514 | 515 | // (body) (root) 516 | // | hidden | visible | scroll | auto | 517 | // hidden | no | no | YES | YES | 518 | // visible | no | YES | YES | YES | 519 | // scroll | no | YES | YES | YES | 520 | // auto | no | YES | YES | YES | 521 | 522 | function overflowingAncestor(el) { 523 | var elems = []; 524 | var body = document.body; 525 | var rootScrollHeight = root.scrollHeight; 526 | do { 527 | var cached = getCache(el, false); 528 | if (cached) { 529 | return setCache(elems, cached); 530 | } 531 | elems.push(el); 532 | if (rootScrollHeight === el.scrollHeight) { 533 | var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body); 534 | var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root); 535 | if (isFrame && isContentOverflowing(root) || 536 | !isFrame && isOverflowCSS) { 537 | return setCache(elems, getScrollRoot()); 538 | } 539 | } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) { 540 | return setCache(elems, el); 541 | } 542 | } while ((el = el.parentElement)); 543 | } 544 | 545 | function isContentOverflowing(el) { 546 | return (el.clientHeight + 10 < el.scrollHeight); 547 | } 548 | 549 | // typically for and 550 | function overflowNotHidden(el) { 551 | var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 552 | return (overflow !== 'hidden'); 553 | } 554 | 555 | // for all other elements 556 | function overflowAutoOrScroll(el) { 557 | var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 558 | return (overflow === 'scroll' || overflow === 'auto'); 559 | } 560 | 561 | // for all other elements 562 | function isScrollBehaviorSmooth(el) { 563 | var id = uniqueID(el); 564 | if (smoothBehaviorForElement[id] == null) { 565 | var scrollBehavior = getComputedStyle(el, '')['scroll-behavior']; 566 | smoothBehaviorForElement[id] = ('smooth' == scrollBehavior); 567 | } 568 | return smoothBehaviorForElement[id]; 569 | } 570 | 571 | 572 | /*********************************************** 573 | * HELPERS 574 | ***********************************************/ 575 | 576 | function addEvent(type, fn, arg) { 577 | window.addEventListener(type, fn, arg || false); 578 | } 579 | 580 | function removeEvent(type, fn, arg) { 581 | window.removeEventListener(type, fn, arg || false); 582 | } 583 | 584 | function isNodeName(el, tag) { 585 | return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase(); 586 | } 587 | 588 | function directionCheck(x, y) { 589 | x = (x > 0) ? 1 : -1; 590 | y = (y > 0) ? 1 : -1; 591 | if (direction.x !== x || direction.y !== y) { 592 | direction.x = x; 593 | direction.y = y; 594 | que = []; 595 | lastScroll = 0; 596 | } 597 | } 598 | 599 | if (window.localStorage && localStorage.SS_deltaBuffer) { 600 | try { // #46 Safari throws in private browsing for localStorage 601 | deltaBuffer = localStorage.SS_deltaBuffer.split(','); 602 | } catch (e) { } 603 | } 604 | 605 | function isTouchpad(deltaY) { 606 | if (!deltaY) return; 607 | if (!deltaBuffer.length) { 608 | deltaBuffer = [deltaY, deltaY, deltaY]; 609 | } 610 | deltaY = Math.abs(deltaY); 611 | deltaBuffer.push(deltaY); 612 | deltaBuffer.shift(); 613 | clearTimeout(deltaBufferTimer); 614 | deltaBufferTimer = setTimeout(function () { 615 | try { // #46 Safari throws in private browsing for localStorage 616 | localStorage.SS_deltaBuffer = deltaBuffer.join(','); 617 | } catch (e) { } 618 | }, 1000); 619 | var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64 620 | var tp = !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta; 621 | if (deltaY < 50) return true; 622 | return tp; 623 | } 624 | 625 | function isDivisible(n, divisor) { 626 | return (Math.floor(n / divisor) == n / divisor); 627 | } 628 | 629 | function allDeltasDivisableBy(divisor) { 630 | return (isDivisible(deltaBuffer[0], divisor) && 631 | isDivisible(deltaBuffer[1], divisor) && 632 | isDivisible(deltaBuffer[2], divisor)); 633 | } 634 | 635 | function isInsideNetflixVideo(event) { 636 | var elem = event.target; 637 | var isControl = false; 638 | if (document.URL.indexOf ('www.netflix.com/watch') != -1) { 639 | do { 640 | isControl = (elem.classList && 641 | elem.classList.contains('html5-video-controls')); 642 | if (isControl) break; 643 | } while ((elem = elem.parentNode)); 644 | } 645 | return isControl; 646 | } 647 | 648 | var requestFrame = (function () { 649 | return (window.requestAnimationFrame || 650 | window.webkitRequestAnimationFrame || 651 | window.mozRequestAnimationFrame || 652 | function (callback, element, delay) { 653 | window.setTimeout(callback, delay || (1000/60)); 654 | }); 655 | })(); 656 | 657 | var MutationObserver = (window.MutationObserver || 658 | window.WebKitMutationObserver || 659 | window.MozMutationObserver); 660 | 661 | var getScrollRoot = (function() { 662 | var SCROLL_ROOT = document.scrollingElement; 663 | return function() { 664 | if (!SCROLL_ROOT) { 665 | var dummy = document.createElement('div'); 666 | dummy.style.cssText = 'height:10000px;width:1px;'; 667 | document.body.appendChild(dummy); 668 | var bodyScrollTop = document.body.scrollTop; 669 | var docElScrollTop = document.documentElement.scrollTop; 670 | window.scrollBy(0, 3); 671 | if (document.body.scrollTop != bodyScrollTop) 672 | (SCROLL_ROOT = document.body); 673 | else 674 | (SCROLL_ROOT = document.documentElement); 675 | window.scrollBy(0, -3); 676 | document.body.removeChild(dummy); 677 | } 678 | return SCROLL_ROOT; 679 | }; 680 | })(); 681 | 682 | 683 | /*********************************************** 684 | * PULSE (by Michael Herf) 685 | ***********************************************/ 686 | 687 | /** 688 | * Viscous fluid with a pulse for part and decay for the rest. 689 | * - Applies a fixed force over an interval (a damped acceleration), and 690 | * - Lets the exponential bleed away the velocity over a longer interval 691 | * - Michael Herf, http://stereopsis.com/stopping/ 692 | */ 693 | function pulse_(x) { 694 | var val, start, expx; 695 | // test 696 | x = x * options.pulseScale; 697 | if (x < 1) { // acceleartion 698 | val = x - (1 - Math.exp(-x)); 699 | } else { // tail 700 | // the previous animation ended here: 701 | start = Math.exp(-1); 702 | // simple viscous drag 703 | x -= 1; 704 | expx = 1 - Math.exp(-x); 705 | val = start + (expx * (1 - start)); 706 | } 707 | return val * options.pulseNormalize; 708 | } 709 | 710 | function pulse(x) { 711 | if (x >= 1) return 1; 712 | if (x <= 0) return 0; 713 | 714 | if (options.pulseNormalize == 1) { 715 | options.pulseNormalize /= pulse_(1); 716 | } 717 | return pulse_(x); 718 | } 719 | 720 | 721 | /*********************************************** 722 | * FIRST RUN 723 | ***********************************************/ 724 | 725 | var userAgent = window.navigator.userAgent; 726 | var isEdge = /Edge/.test(userAgent); // thank you MS 727 | var isChrome = /chrome/i.test(userAgent) && !isEdge; 728 | var isSafari = /safari/i.test(userAgent) && !isEdge; 729 | var isMobile = /mobile/i.test(userAgent); 730 | var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent); 731 | var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent)); 732 | var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile; 733 | 734 | var supportsPassive = false; 735 | try { 736 | window.addEventListener("test", null, Object.defineProperty({}, 'passive', { 737 | get: function () { 738 | supportsPassive = true; 739 | } 740 | })); 741 | } catch(e) {} 742 | 743 | var wheelOpt = supportsPassive ? { passive: false } : false; 744 | var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; 745 | 746 | if (wheelEvent && isEnabledForBrowser) { 747 | addEvent(wheelEvent, wheel, wheelOpt); 748 | addEvent('mousedown', mousedown); 749 | addEvent('load', init); 750 | } 751 | 752 | 753 | /*********************************************** 754 | * PUBLIC INTERFACE 755 | ***********************************************/ 756 | 757 | function SmoothScroll(optionsToSet) { 758 | for (var key in optionsToSet) 759 | if (defaultOptions.hasOwnProperty(key)) 760 | options[key] = optionsToSet[key]; 761 | } 762 | SmoothScroll.destroy = cleanup; 763 | 764 | if (window.SmoothScrollOptions) // async API 765 | SmoothScroll(window.SmoothScrollOptions); 766 | 767 | if (typeof define === 'function' && define.amd) 768 | define(function() { 769 | return SmoothScroll; 770 | }); 771 | else if ('object' == typeof exports) 772 | module.exports = SmoothScroll; 773 | else 774 | window.SmoothScroll = SmoothScroll; 775 | 776 | })(); 777 | 778 | document.addEventListener('DOMContentLoaded', function (event) { 779 | if (!window.netflixPartyLoaded) { 780 | window.netflixPartyLoaded = true 781 | const setImmediate = require('setimmediate') // eslint-disable-line 782 | window.$ = window.jQuery = require('jquery') 783 | const { ipcRenderer, clipboard } = require('electron') 784 | const io = require('socket.io-client') 785 | 786 | var withChatInterval = null; 787 | 788 | (function ($) { 789 | $.isBlank = function (obj) { 790 | return (!obj || $.trim(obj) === '') 791 | } 792 | })(jQuery) 793 | 794 | /// /////////////////////////////////////////////////////////////////////// 795 | // Vendor libraries // 796 | /// /////////////////////////////////////////////////////////////////////// 797 | 798 | /* PNGLib.js v1.0 */ 799 | // eslint-disable-next-line 800 | !(function () { function i (i, t) { for (var s = 2; s < arguments.length; s++) for (var h = 0; h < arguments[s].length; h++)i[t++] = arguments[s].charAt(h) } function t (i) { return String.fromCharCode(i >> 8 & 255, 255 & i) } function s (i) { return String.fromCharCode(i >> 24 & 255, i >> 16 & 255, i >> 8 & 255, 255 & i) } function h (i) { return String.fromCharCode(255 & i, i >> 8 & 255) }window.PNGlib = function (f, e, r) { this.width = f, this.height = e, this.depth = r, this.pix_size = e * (f + 1), this.data_size = 2 + this.pix_size + 5 * Math.floor((65534 + this.pix_size) / 65535) + 4, this.ihdr_offs = 0, this.ihdr_size = 25, this.plte_offs = this.ihdr_offs + this.ihdr_size, this.plte_size = 8 + 3 * r + 4, this.trns_offs = this.plte_offs + this.plte_size, this.trns_size = 8 + r + 4, this.idat_offs = this.trns_offs + this.trns_size, this.idat_size = 8 + this.data_size + 4, this.iend_offs = this.idat_offs + this.idat_size, this.iend_size = 12, this.buffer_size = this.iend_offs + this.iend_size, this.buffer = new Array(), this.palette = new Object(), this.pindex = 0; for (var n = new Array(), o = 0; o < this.buffer_size; o++) this.buffer[o] = '\x00'; i(this.buffer, this.ihdr_offs, s(this.ihdr_size - 12), 'IHDR', s(f), s(e), '\b'), i(this.buffer, this.plte_offs, s(this.plte_size - 12), 'PLTE'), i(this.buffer, this.trns_offs, s(this.trns_size - 12), 'tRNS'), i(this.buffer, this.idat_offs, s(this.idat_size - 12), 'IDAT'), i(this.buffer, this.iend_offs, s(this.iend_size - 12), 'IEND'); var a = 30912; a += 31 - a % 31, i(this.buffer, this.idat_offs + 8, t(a)); for (var o = 0; (o << 16) - 1 < this.pix_size; o++) { var d, _; o + 65535 < this.pix_size ? (d = 65535, _ = '\x00') : (d = this.pix_size - (o << 16) - o, _ = ''), i(this.buffer, this.idat_offs + 8 + 2 + (o << 16) + (o << 2), _, h(d), h(~d)) } for (var o = 0; o < 256; o++) { for (var u = o, z = 0; z < 8; z++)u = 1 & u ? -306674912 ^ u >> 1 & 2147483647 : u >> 1 & 2147483647; n[o] = u } this.index = function (i, t) { var s = t * (this.width + 1) + i + 1; var h = this.idat_offs + 8 + 2 + 5 * Math.floor(s / 65535 + 1) + s; return h }, this.color = function (i, t, s, h) { h = h >= 0 ? h : 255; var f = ((h << 8 | i) << 8 | t) << 8 | s; if (typeof this.palette[f] === 'undefined') { if (this.pindex == this.depth) return '\x00'; var e = this.plte_offs + 8 + 3 * this.pindex; this.buffer[e + 0] = String.fromCharCode(i), this.buffer[e + 1] = String.fromCharCode(t), this.buffer[e + 2] = String.fromCharCode(s), this.buffer[this.trns_offs + 8 + this.pindex] = String.fromCharCode(h), this.palette[f] = String.fromCharCode(this.pindex++) } return this.palette[f] }, this.getBase64 = function () { var i; var t; var s; var h; var f; var e; var r; var n = this.getDump(); var o = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var a = n.length; var d = 0; var _ = ''; do i = n.charCodeAt(d), h = i >> 2, t = n.charCodeAt(d + 1), f = (3 & i) << 4 | t >> 4, s = n.charCodeAt(d + 2), e = d + 2 > a ? 64 : (15 & t) << 2 | s >> 6, r = d + 3 > a ? 64 : 63 & s, _ += o.charAt(h) + o.charAt(f) + o.charAt(e) + o.charAt(r); while ((d += 3) < a);return _ }, this.getDump = function () { function t (t, h, f) { for (var e = -1, r = 4; f - 4 > r; r += 1)e = n[255 & (e ^ t[h + r].charCodeAt(0))] ^ e >> 8 & 16777215; i(t, h + f - 4, s(-1 ^ e)) } for (var h = 65521, f = 5552, e = 1, r = 0, o = f, a = 0; a < this.height; a++) for (var d = -1; d < this.width; d++)e += this.buffer[this.index(d, a)].charCodeAt(0), r += e, (o -= 1) == 0 && (e %= h, r %= h, o = f); return e %= h, r %= h, i(this.buffer, this.idat_offs + this.idat_size - 8, s(r << 16 | e)), t(this.buffer, this.ihdr_offs, this.ihdr_size), t(this.buffer, this.plte_offs, this.plte_size), t(this.buffer, this.trns_offs, this.trns_size), t(this.buffer, this.idat_offs, this.idat_size), t(this.buffer, this.iend_offs, this.iend_size), '‰PNG\r\n\n' + this.buffer.join('') } } }()) 801 | 802 | /* Identicon.js v1.0 */ 803 | // eslint-disable-next-line 804 | !(function () { Identicon = function (n, t, r) { this.hash = n, this.size = t || 64, this.margin = r || 0 }, Identicon.prototype = { hash:null, size:null, margin:null, render:function () { var n; var t; var r = this.hash; var e = this.size; var i = Math.floor(e * this.margin); var s = Math.floor((e - 2 * i) / 5); var o = new PNGlib(e, e, 256); var h = o.color(0, 0, 0, 0); var a = this.hsl2rgb(parseInt(r.substr(-7), 16) / 268435455, 0.5, 0.7); var c = o.color(255 * a[0], 255 * a[1], 255 * a[2]); for (n = 0; n < 15; n++)t = parseInt(r.charAt(n), 16) % 2 ? h : c, n < 5 ? this.rectangle(2 * s + i, n * s + i, s, s, t, o) : n < 10 ? (this.rectangle(1 * s + i, (n - 5) * s + i, s, s, t, o), this.rectangle(3 * s + i, (n - 5) * s + i, s, s, t, o)) : n < 15 && (this.rectangle(0 * s + i, (n - 10) * s + i, s, s, t, o), this.rectangle(4 * s + i, (n - 10) * s + i, s, s, t, o)); return o }, rectangle:function (n, t, r, e, i, s) { var o, h; for (o = n; n + r > o; o++) for (h = t; t + e > h; h++)s.buffer[s.index(o, h)] = i }, hsl2rgb:function (n, t, r) { return n *= 6, t = [r += t *= r < 0.5 ? r : 1 - r, r - n % 1 * t * 2, r -= t *= 2, r, r + n % 1 * t, r + t], [t[~~n % 6], t[(16 | n) % 6], t[(8 | n) % 6]] }, toString:function () { return this.render().getBase64() } }, window.Identicon = Identicon }()) 805 | 806 | /* SHA256 (Chris Veness) */ 807 | // eslint-disable-next-line 808 | var Sha256 = {}; Sha256.hash = function (t) { t = t.utf8Encode(); var r = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298]; var e = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]; t += String.fromCharCode(128); for (var n = t.length / 4 + 2, o = Math.ceil(n / 16), a = new Array(o), h = 0; o > h; h++) { a[h] = new Array(16); for (var S = 0; S < 16; S++)a[h][S] = t.charCodeAt(64 * h + 4 * S) << 24 | t.charCodeAt(64 * h + 4 * S + 1) << 16 | t.charCodeAt(64 * h + 4 * S + 2) << 8 | t.charCodeAt(64 * h + 4 * S + 3) }a[o - 1][14] = 8 * (t.length - 1) / Math.pow(2, 32), a[o - 1][14] = Math.floor(a[o - 1][14]), a[o - 1][15] = 8 * (t.length - 1) & 4294967295; for (var u, f, c, i, d, R, p, y, x = new Array(64), h = 0; o > h; h++) { for (var O = 0; O < 16; O++)x[O] = a[h][O]; for (var O = 16; O < 64; O++)x[O] = Sha256.σ1(x[O - 2]) + x[O - 7] + Sha256.σ0(x[O - 15]) + x[O - 16] & 4294967295; u = e[0], f = e[1], c = e[2], i = e[3], d = e[4], R = e[5], p = e[6], y = e[7]; for (var O = 0; O < 64; O++) { var T = y + Sha256.Σ1(d) + Sha256.Ch(d, R, p) + r[O] + x[O]; var s = Sha256.Σ0(u) + Sha256.Maj(u, f, c); y = p, p = R, R = d, d = i + T & 4294967295, i = c, c = f, f = u, u = T + s & 4294967295 }e[0] = e[0] + u & 4294967295, e[1] = e[1] + f & 4294967295, e[2] = e[2] + c & 4294967295, e[3] = e[3] + i & 4294967295, e[4] = e[4] + d & 4294967295, e[5] = e[5] + R & 4294967295, e[6] = e[6] + p & 4294967295, e[7] = e[7] + y & 4294967295 } return Sha256.toHexStr(e[0]) + Sha256.toHexStr(e[1]) + Sha256.toHexStr(e[2]) + Sha256.toHexStr(e[3]) + Sha256.toHexStr(e[4]) + Sha256.toHexStr(e[5]) + Sha256.toHexStr(e[6]) + Sha256.toHexStr(e[7]) }, Sha256.ROTR = function (t, r) { return r >>> t | r << 32 - t }, Sha256.Σ0 = function (t) { return Sha256.ROTR(2, t) ^ Sha256.ROTR(13, t) ^ Sha256.ROTR(22, t) }, Sha256.Σ1 = function (t) { return Sha256.ROTR(6, t) ^ Sha256.ROTR(11, t) ^ Sha256.ROTR(25, t) }, Sha256.σ0 = function (t) { return Sha256.ROTR(7, t) ^ Sha256.ROTR(18, t) ^ t >>> 3 }, Sha256.σ1 = function (t) { return Sha256.ROTR(17, t) ^ Sha256.ROTR(19, t) ^ t >>> 10 }, Sha256.Ch = function (t, r, e) { return t & r ^ ~t & e }, Sha256.Maj = function (t, r, e) { return t & r ^ t & e ^ r & e }, Sha256.toHexStr = function (t) { for (var r, e = '', n = 7; n >= 0; n--)r = t >>> 4 * n & 15, e += r.toString(16); return e }, typeof String.prototype.utf8Encode === 'undefined' && (String.prototype.utf8Encode = function () { return unescape(encodeURIComponent(this)) }), typeof String.prototype.utf8Decode === 'undefined' && (String.prototype.utf8Decode = function () { try { return decodeURIComponent(escape(this)) } catch (t) { return this } }), typeof module !== 'undefined' && module.exports && (module.exports = Sha256), typeof define === 'function' && define.amd && define([], function () { return Sha256 }) 809 | 810 | /// /////////////////////////////////////////////////////////////////////// 811 | // Version // 812 | /// /////////////////////////////////////////////////////////////////////// 813 | 814 | var version = null 815 | 816 | /// /////////////////////////////////////////////////////////////////////// 817 | // Helpers // 818 | /// /////////////////////////////////////////////////////////////////////// 819 | 820 | // returns an action which delays for some time 821 | var delay = function (milliseconds) { 822 | return function (result) { 823 | return new Promise(function (resolve, reject) { 824 | setTimeout(function () { 825 | resolve(result) 826 | }, milliseconds) 827 | }) 828 | } 829 | } 830 | 831 | // returns an action which waits until the condition thunk returns true, 832 | // rejecting if maxDelay time is exceeded 833 | var delayUntil = function (condition, maxDelay) { 834 | return function (result) { 835 | var delayStep = 250 836 | var startTime = (new Date()).getTime() 837 | var checkForCondition = function () { 838 | if (condition()) { 839 | return Promise.resolve(result) 840 | } 841 | if (maxDelay !== null && (new Date()).getTime() - startTime > maxDelay) { 842 | return Promise.reject(Error('delayUntil timed out')) 843 | } 844 | return delay(delayStep)().then(checkForCondition) 845 | } 846 | return checkForCondition() 847 | } 848 | } 849 | 850 | // add value to the end of array, and remove items from the beginning 851 | // such that the length does not exceed limit 852 | var shove = function (array, value, limit) { 853 | array.push(value) 854 | if (array.length > limit) { 855 | array.splice(0, array.length - limit) 856 | } 857 | } 858 | 859 | // compute the mean of an array of numbers 860 | var mean = function (array) { 861 | return array.reduce(function (a, b) { return a + b }) / array.length 862 | } 863 | 864 | // compute the median of an array of numbers 865 | var median = function (array) { 866 | return array.concat().sort()[Math.floor(array.length / 2)] 867 | } 868 | 869 | // swallow any errors from an action 870 | // and log them to the console 871 | var swallow = function (action) { 872 | return function (result) { 873 | return action(result).catch(function (e) { 874 | //console.error(e) Only out purs annoying errors 875 | }) 876 | } 877 | } 878 | 879 | // promise.ensure(fn) method 880 | // note that this method will not swallow errors 881 | // eslint-disable-next-line 882 | Promise.prototype.ensure = function (fn) { 883 | return this.then(fn, function (e) { 884 | fn() 885 | throw e 886 | }) 887 | } 888 | 889 | console.log("SMOOTHSCROLL ENABLED | DISCORD-NETFLIX"); 890 | 891 | //MAX BITRATE// 892 | let getElementByXPath = function (xpath) { 893 | return document.evaluate( 894 | xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null 895 | ).singleNodeValue; 896 | }; 897 | 898 | let fn = function () { 899 | window.dispatchEvent(new KeyboardEvent('keydown', { 900 | keyCode: 83, 901 | ctrlKey: true, 902 | altKey: true, 903 | shiftKey: true, 904 | })); 905 | 906 | const VIDEO_SELECT = getElementByXPath("//div[text()='Video Bitrate']"); 907 | const AUDIO_SELECT = getElementByXPath("//div[text()='Audio Bitrate']"); 908 | const BUTTON = getElementByXPath("//button[text()='Override']"); 909 | 910 | if (!(VIDEO_SELECT && AUDIO_SELECT && BUTTON)){ 911 | return false; 912 | } 913 | 914 | [VIDEO_SELECT, AUDIO_SELECT].forEach(function (el) { 915 | let parent = el.parentElement; 916 | 917 | let options = parent.querySelectorAll('select > option'); 918 | 919 | for (var i = 0; i < options.length - 1; i++) { 920 | options[i].removeAttribute('selected'); 921 | } 922 | 923 | options[options.length - 1].setAttribute('selected', 'selected'); 924 | }); 925 | 926 | BUTTON.click(); 927 | 928 | return true; 929 | }; 930 | 931 | let run = function () { 932 | fn() || setTimeout(run, 100); 933 | }; 934 | 935 | const WATCH_REGEXP = /netflix.com\/watch\/.*/; 936 | 937 | let oldLocation; 938 | 939 | console.log("MAXBITRATE ENABLED | DISCORD-NETFLIX"); 940 | setInterval(function () { 941 | let newLocation = window.location.toString(); 942 | 943 | if (newLocation !== oldLocation) { 944 | oldLocation = newLocation; 945 | WATCH_REGEXP.test(newLocation) && run(); 946 | } 947 | }, 500); 948 | 949 | //Fixed PIP 950 | (function(){ 951 | console.log("connected") 952 | //Creates button parent element 953 | const btnParent = document.createElement("div"); 954 | //Styling button 955 | btnParent.style.position = "relative"; 956 | btnParent.style.zIndex = "1"; 957 | btnParent.style.textAlign = "center"; 958 | btnParent.style.opacity = "0"; 959 | btnParent.addEventListener("mouseover", () => { 960 | btnParent.style.opacity = "1"; 961 | setTimeout(() => { 962 | btnParent.style.opacity = "0"; 963 | },2000); 964 | }); 965 | 966 | //Button element 967 | btnParent.innerHTML = 968 | ` 969 | 975 | ` 976 | //Waits loading video element 977 | const observer = new MutationObserver((mutations, obs) => { 978 | const targetNode = document.querySelector('body'); 979 | 980 | //Parses url and gets id 981 | const url = window.location.href.toString().split('/'); 982 | if(url[4]){//Video Id 983 | const filmId = url[4].split('?')[0]; 984 | const videoNode = document.getElementById(filmId); 985 | 986 | if (videoNode) { 987 | targetNode.prepend(btnParent);//Adds miniflix button 988 | obs.disconnect(); 989 | return; 990 | } 991 | } 992 | }); 993 | 994 | //Which changes must listen in elements 995 | observer.observe(document, { 996 | childList: true, 997 | subtree: true 998 | }); 999 | 1000 | //Waits loading miniflix button 1001 | const observer2 = new MutationObserver((mutations, obs) => { 1002 | const pipBtn = document.getElementById('pipBtn'); 1003 | 1004 | //Parses url and gets id 1005 | const url = window.location.href.toString().split('/'); 1006 | if(url[4]){//Video Id 1007 | const filmId = url[4].split('?')[0]; 1008 | if (pipBtn) { 1009 | const videoNode = document.getElementById(filmId); 1010 | const video = videoNode.firstElementChild; 1011 | //console.log(video); 1012 | pipBtn.style.border = "none"; 1013 | if("pictureInPictureEnabled" in document){//If miniflix mode enabled adds event listener for disabling 1014 | pipBtn.addEventListener("click",() => { 1015 | if(document.pictureInPictureElement){ 1016 | document.exitPictureInPicture().catch(err=>{ 1017 | console.log(err); 1018 | }); 1019 | return; 1020 | } 1021 | video.requestPictureInPicture().catch(err=>{ 1022 | console.log(err); 1023 | }) 1024 | }) 1025 | } 1026 | obs.disconnect(); 1027 | return; 1028 | } 1029 | } 1030 | 1031 | }); 1032 | 1033 | //Which changes must listen in elements 1034 | observer2.observe(document, { 1035 | childList: true, 1036 | subtree: true 1037 | }); 1038 | 1039 | //Update notifier 1040 | const fs = require('fs'); 1041 | 1042 | let userChoice = null; 1043 | 1044 | function getVersionNumberFromPackageJson() { 1045 | try { 1046 | const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); 1047 | return packageJson.version; 1048 | } catch (error) { 1049 | 1050 | } 1051 | } 1052 | 1053 | function checkNumberOnWebsite(url, numberToCheck) { 1054 | const versionNumber = getVersionNumberFromPackageJson(); 1055 | if (!versionNumber) { 1056 | alert("Error: Couldn't retrieve version number from package.json"); 1057 | return; 1058 | } 1059 | 1060 | fetch(url) 1061 | .then(response => { 1062 | if (!response.ok) { 1063 | throw new Error('Network response was not ok'); 1064 | } 1065 | return response.text(); 1066 | }) 1067 | .then(html => { 1068 | if (html.includes(numberToCheck)) { 1069 | console.log("Latest version: " + versionNumber) 1070 | } else { 1071 | setTimeout(() => { 1072 | userChoice = confirm("There's an update available. Would you like to update to version: " + versionNumber); 1073 | }, 0); 1074 | setTimeout(() => { 1075 | if (userChoice === null) { 1076 | console.log("User did not make a choice."); 1077 | } else if (userChoice === true) { 1078 | console.log("User chose to update."); 1079 | shell.openExternal(updateURL); 1080 | } else { 1081 | console.log("User chose not to update."); 1082 | // Do nothing 1083 | } 1084 | }, 100); 1085 | } 1086 | }) 1087 | .catch(error => { 1088 | console.error('There was a problem with the fetch operation:', error); 1089 | alert("There was an error checking the website."); 1090 | }); 1091 | } 1092 | const { shell } = require('electron'); 1093 | const updateURL = "https://github.com/V0l-D/Discord-Netflix/releases"; 1094 | const websiteURL = "https://V0l-D.github.io"; 1095 | const numberToCheck = getVersionNumberFromPackageJson(); 1096 | if (numberToCheck) { 1097 | checkNumberOnWebsite(websiteURL, numberToCheck); 1098 | } 1099 | }) 1100 | } 1101 | }); 1102 | 1103 | 1104 | --------------------------------------------------------------------------------