Skip to content

Commit

Permalink
Merge branch 'development' of github.com:FreeTubeApp/FreeTube into fe…
Browse files Browse the repository at this point in the history
…at/keyword-search-history
  • Loading branch information
kommunarr committed Dec 27, 2024
2 parents 2caa250 + 6693654 commit 2dc2326
Show file tree
Hide file tree
Showing 26 changed files with 753 additions and 135 deletions.
15 changes: 14 additions & 1 deletion _scripts/dev-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ const web = process.argv.indexOf('--web') !== -1

let mainConfig
let rendererConfig
let botGuardScriptConfig
let webConfig
let SHAKA_LOCALES_TO_BE_BUNDLED

if (!web) {
mainConfig = require('./webpack.main.config')
rendererConfig = require('./webpack.renderer.config')
botGuardScriptConfig = require('./webpack.botGuardScript.config')

SHAKA_LOCALES_TO_BE_BUNDLED = rendererConfig.SHAKA_LOCALES_TO_BE_BUNDLED
delete rendererConfig.SHAKA_LOCALES_TO_BE_BUNDLED
Expand Down Expand Up @@ -98,6 +100,14 @@ function setupNotifyLocaleUpdate(compiler, devServer) {
})
}

function startBotGuardScript() {
webpack(botGuardScriptConfig, (err) => {
if (err) console.error(err)

console.log(`\nCompiled ${botGuardScriptConfig.name} script!`)
})
}

function startMain() {
const compiler = webpack(mainConfig)
const { name } = compiler
Expand Down Expand Up @@ -196,7 +206,10 @@ function startWeb () {
})
}
if (!web) {
startRenderer(startMain)
startRenderer(() => {
startBotGuardScript()
startMain()
})
} else {
startWeb()
}
2 changes: 2 additions & 0 deletions _scripts/injectAllowedPaths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const paths = readdirSync(distDirectory, {
// disallow the renderer process/browser windows to read the main.js file
dirent.name !== 'main.js' &&
dirent.name !== 'main.js.LICENSE.txt' &&
// disallow the renderer process/browser windows to read the botGuardScript.js file
dirent.name !== 'botGuardScript.js' &&
// filter out any web build files, in case the dist directory contains a web build
!dirent.parentPath.startsWith(webDirectory)
})
Expand Down
23 changes: 23 additions & 0 deletions _scripts/webpack.botGuardScript.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require('path')

/** @type {import('webpack').Configuration} */
module.exports = {
name: 'botGuardScript',
// Always use production mode, as we use the output as a function body and the debug output doesn't work for that
mode: 'production',
devtool: false,
target: 'web',
entry: {
botGuardScript: path.join(__dirname, '../src/botGuardScript.js'),
},
output: {
filename: '[name].js',
path: path.join(__dirname, '../dist'),
library: {
type: 'modern-module'
}
},
experiments: {
outputModule: true
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
"lint-style": "stylelint \"**/*.{css,scss}\"",
"lint-style-fix": "stylelint --fix \"**/*.{css,scss}\"",
"lint-yml": "eslint --config eslint.config.mjs \"**/*.yml\" \"**/*.yaml\"",
"pack": "run-p pack:main pack:renderer && node _scripts/injectAllowedPaths.mjs",
"pack": "run-p pack:main pack:renderer pack:botGuardScript && node _scripts/injectAllowedPaths.mjs",
"pack:main": "webpack --mode=production --node-env=production --config _scripts/webpack.main.config.js",
"pack:renderer": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.config.js",
"pack:web": "webpack --mode=production --node-env=production --config _scripts/webpack.web.config.js",
"pack:botGuardScript": "webpack --config _scripts/webpack.botGuardScript.config.js",
"postinstall": "run-s --silent rebuild:electron patch-shaka",
"prettier": "prettier --write \"{src,_scripts}/**/*.{js,vue}\"",
"rebuild:electron": "electron-builder install-app-deps",
Expand All @@ -61,6 +62,7 @@
"@fortawesome/vue-fontawesome": "^2.0.10",
"@seald-io/nedb": "^4.0.4",
"autolinker": "^4.0.1",
"bgutils-js": "^3.1.0",
"electron-context-menu": "^4.0.4",
"lodash.debounce": "^4.0.8",
"marked": "^15.0.4",
Expand Down
37 changes: 37 additions & 0 deletions src/botGuardScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BG } from 'bgutils-js'

// This script has it's own webpack config, as it gets passed as a string to Electron's evaluateJavaScript function
// in src/main/poTokenGenerator.js
export default async function(visitorData) {
const requestKey = 'O43z0dpjhgX20SCx4KAo'

const bgConfig = {
fetch: (input, init) => fetch(input, init),
requestKey,
globalObj: window,
identifier: visitorData
}

const challenge = await BG.Challenge.create(bgConfig)

if (!challenge) {
throw new Error('Could not get challenge')
}

const interpreterJavascript = challenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue

if (interpreterJavascript) {
// eslint-disable-next-line no-new-func
new Function(interpreterJavascript)()
} else {
console.warn('Unable to load VM.')
}

const poTokenResult = await BG.PoToken.generate({
program: challenge.program,
globalName: challenge.globalName,
bgConfig
})

return poTokenResult.poToken
}
33 changes: 27 additions & 6 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ const IpcChannels = {
PLAYER_CACHE_GET: 'player-cache-get',
PLAYER_CACHE_SET: 'player-cache-set',

SET_INVIDIOUS_AUTHORIZATION: 'set-invidious-authorization'
SET_INVIDIOUS_AUTHORIZATION: 'set-invidious-authorization',

GENERATE_PO_TOKEN: 'generate-po-token',
}

const DBActions = {
Expand Down Expand Up @@ -116,19 +118,37 @@ const SyncEvents = {
},
}

// note: the multi-key shortcut values are currently just for display use in action titles
/*
DEV NOTE: Duplicate any and all changes made here to our [official documentation site here](https://github.com/FreeTubeApp/FreeTube-Docs/blob/master/usage/keyboard-shortcuts.md)
to have them reflect on the [keyboard shortcut reference webpage](https://docs.freetubeapp.io/usage/keyboard-shortcuts).
Please also update the [keyboard shortcut modal](src/renderer/components/FtKeyboardShortcutPrompt/FtKeyboardShortcutPrompt.vue)
*/
const KeyboardShortcuts = {
APP: {
GENERAL: {
SHOW_SHORTCUTS: 'shift+?',
HISTORY_BACKWARD: 'alt+arrowleft',
HISTORY_FORWARD: 'alt+arrowright',
NEW_WINDOW: 'ctrl+N',
FULLSCREEN: 'f11',
NAVIGATE_TO_SETTINGS: 'ctrl+,',
NAVIGATE_TO_HISTORY: 'ctrl+H',
NAVIGATE_TO_HISTORY_MAC: 'cmd+Y',
NEW_WINDOW: 'ctrl+N',
MINIMIZE_WINDOW: 'ctrl+M',
CLOSE_WINDOW: 'ctrl+W',
RESTART_WINDOW: 'ctrl+R',
FORCE_RESTART_WINDOW: 'ctrl+shift+R',
TOGGLE_DEVTOOLS: 'ctrl+shift+I',
FOCUS_SEARCH: 'alt+D',
SEARCH_IN_NEW_WINDOW: 'shift+enter',
RESET_ZOOM: 'ctrl+0',
ZOOM_IN: 'ctrl+plus',
ZOOM_OUT: 'ctrl+-'

},
SITUATIONAL: {
REFRESH: 'r'
REFRESH: 'r',
FOCUS_SECONDARY_SEARCH: 'ctrl+F'
},
},
VIDEO_PLAYER: {
Expand All @@ -152,10 +172,11 @@ const KeyboardShortcuts = {
SMALL_FAST_FORWARD: 'arrowright',
DECREASE_VIDEO_SPEED: 'o',
INCREASE_VIDEO_SPEED: 'p',
LAST_FRAME: ',',
NEXT_FRAME: '.',
SKIP_N_TENTHS: '0..9',
LAST_CHAPTER: 'ctrl+arrowleft',
NEXT_CHAPTER: 'ctrl+arrowright',
LAST_FRAME: ',',
NEXT_FRAME: '.',
}
},
}
Expand Down
26 changes: 8 additions & 18 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { brotliDecompress } from 'zlib'
import contextMenu from 'electron-context-menu'

import packageDetails from '../../package.json'
import { generatePoToken } from './poTokenGenerator'

const brotliDecompressAsync = promisify(brotliDecompress)

Expand Down Expand Up @@ -427,24 +428,9 @@ function runApp() {
requestHeaders.Referer = 'https://www.youtube.com/'
requestHeaders.Origin = 'https://www.youtube.com'

// Make iOS requests work and look more realistic
if (requestHeaders['x-youtube-client-name'] === '5') {
delete requestHeaders.Referer
delete requestHeaders.Origin
delete requestHeaders['Sec-Fetch-Site']
delete requestHeaders['Sec-Fetch-Mode']
delete requestHeaders['Sec-Fetch-Dest']
delete requestHeaders['sec-ch-ua']
delete requestHeaders['sec-ch-ua-mobile']
delete requestHeaders['sec-ch-ua-platform']

requestHeaders['User-Agent'] = requestHeaders['x-user-agent']
delete requestHeaders['x-user-agent']
} else {
requestHeaders['Sec-Fetch-Site'] = 'same-origin'
requestHeaders['Sec-Fetch-Mode'] = 'same-origin'
requestHeaders['X-Youtube-Bootstrap-Logged-In'] = 'false'
}
requestHeaders['Sec-Fetch-Site'] = 'same-origin'
requestHeaders['Sec-Fetch-Mode'] = 'same-origin'
requestHeaders['X-Youtube-Bootstrap-Logged-In'] = 'false'
} else if (urlObj.origin.endsWith('.googlevideo.com') && urlObj.pathname === '/videoplayback') {
requestHeaders.Referer = 'https://www.youtube.com/'
requestHeaders.Origin = 'https://www.youtube.com'
Expand Down Expand Up @@ -884,6 +870,10 @@ function runApp() {
})
})

ipcMain.handle(IpcChannels.GENERATE_PO_TOKEN, (_, visitorData) => {
return generatePoToken(visitorData)
})

ipcMain.on(IpcChannels.ENABLE_PROXY, (_, url) => {
session.defaultSession.setProxy({
proxyRules: url
Expand Down
140 changes: 140 additions & 0 deletions src/main/poTokenGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { session, WebContentsView } from 'electron'
import { readFile } from 'fs/promises'
import { join } from 'path'

/**
* Generates a poToken (proof of origin token) using `bgutils-js`.
* The script to generate it is `src/botGuardScript.js`
*
* This is intentionally split out into it's own thing, with it's own temporary in-memory session,
* as the BotGuard stuff accesses the global `document` and `window` objects and also requires making some requests.
* So we definitely don't want it running in the same places as the rest of the FreeTube code with the user data.
* @param {string} visitorData
* @returns {Promise<string>}
*/
export async function generatePoToken(visitorData) {
const sessionUuid = crypto.randomUUID()

const theSession = session.fromPartition(`potoken-${sessionUuid}`, { cache: false })

theSession.setPermissionCheckHandler(() => false)
// eslint-disable-next-line n/no-callback-literal
theSession.setPermissionRequestHandler((webContents, permission, callback) => callback(false))

theSession.setUserAgent(
theSession.getUserAgent()
.split(' ')
.filter(part => !part.includes('Electron'))
.join(' ')
)

const webContentsView = new WebContentsView({
webPreferences: {
backgroundThrottling: false,
safeDialogs: true,
sandbox: true,
v8CacheOptions: 'none',
session: theSession,
offscreen: true
}
})

webContentsView.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))

webContentsView.webContents.setAudioMuted(true)
webContentsView.setBounds({
x: 0,
y: 0,
width: 1920,
height: 1080
})

webContentsView.webContents.debugger.attach()

await webContentsView.webContents.loadURL('data:text/html,', {
baseURLForDataURL: 'https://www.youtube.com'
})

await webContentsView.webContents.debugger.sendCommand('Emulation.setUserAgentOverride', {
userAgent: theSession.getUserAgent(),
acceptLanguage: 'en-US',
platform: 'Win32',
userAgentMetadata: {
brands: [
{
brand: 'Not/A)Brand',
version: '99'
},
{
brand: 'Chromium',
version: process.versions.chrome.split('.')[0]
}
],
fullVersionList: [
{
brand: 'Not/A)Brand',
version: '99.0.0.0'
},
{
brand: 'Chromium',
version: process.versions.chrome
}
],
platform: 'Windows',
platformVersion: '10.0.0',
architecture: 'x86',
model: '',
mobile: false,
bitness: '64',
wow64: false
}
})

await webContentsView.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
mobile: false,
screenWidth: 1920,
screenHeight: 1080,
positionX: 0,
positionY: 0,
screenOrientation: {
type: 'landscapePrimary',
angle: 0
}
})

const script = await getScript(visitorData)

const response = await webContentsView.webContents.executeJavaScript(script)

webContentsView.webContents.close({ waitForBeforeUnload: false })
await theSession.closeAllConnections()

return response
}

let cachedScript

/**
* @param {string} visitorData
*/
async function getScript(visitorData) {
if (!cachedScript) {
const pathToScript = process.env.NODE_ENV === 'development'
? join(__dirname, '../../dist/botGuardScript.js')
/* eslint-disable-next-line n/no-path-concat */
: `${__dirname}/botGuardScript.js`

const content = await readFile(pathToScript, 'utf-8')

const match = content.match(/export{(\w+) as default};/)

const functionName = match[1]

cachedScript = content.replace(match[0], `;${functionName}("FT_VISITOR_DATA")`)
}

return cachedScript.replace('FT_VISITOR_DATA', visitorData)
}
Loading

0 comments on commit 2dc2326

Please sign in to comment.