2023-02-26 10:34:24 +01:00
const {
2022-11-18 10:42:25 +01:00
fetcher ,
core ,
wiki ,
musicInfo ,
modules ,
version ,
initlog ,
init ,
} = require ( "../libpoketube-initsys.js" ) ;
2023-08-21 23:11:46 +02:00
const { curly } = require ( "node-libcurl" ) ;
2022-11-09 17:52:57 +01:00
const {
IsJsonString ,
convert ,
getFirstLine ,
capitalizeFirstLetter ,
turntomins ,
getRandomInt ,
getRandomArbitrary ,
} = require ( "../ptutils/libpt-coreutils.js" ) ;
2022-12-18 16:30:24 +01:00
const sha384 = modules . hash ;
2022-12-09 18:13:14 +01:00
2023-11-16 07:15:20 +01:00
/ * *
* Parses a string to JSON , returns null if parsing fails .
* @ param { string } str - The input string to be parsed as JSON .
* @ returns { Object | null } - The parsed JSON object or null if parsing fails .
* /
2022-12-09 18:13:14 +01:00
function getJson ( str ) {
try {
return JSON . parse ( str ) ;
} catch {
return null ;
}
}
2023-11-16 07:15:20 +01:00
/ * *
* Object representing base64 - encoded protobuf values for channel tabs .
* @ typedef { Object } ChannelTabs
* @ property { string } community - Base64 - encoded value for the community tab .
* @ property { string } shorts - Base64 - encoded value for the shorts tab .
* @ property { string } videos - Base64 - encoded value for the videos tab .
* @ property { string } streams - Base64 - encoded value for the streams tab .
* /
2023-12-26 15:48:46 +01:00
// see https://developers.google.com/youtube/v3/docs/channels/
2023-09-09 18:49:38 +02:00
const ChannelTabs = {
2023-10-17 21:42:12 +02:00
community : "Y29tbXVuaXR5" ,
shorts : "c2hvcnRz" ,
videos : "dmlkZW9z" ,
2023-11-16 07:15:20 +01:00
streams : "c3RyZWFtcw==" , // or "live"
2023-12-26 15:48:46 +01:00
channels : "Y2hhbm5lbHM=" ,
store : "c3RvcmU=" ,
released : "cmVsZWFzZWQ=" ,
2024-02-10 10:06:57 +01:00
playlist : "cGxheWxpc3Rz" ,
2023-10-17 21:42:12 +02:00
} ;
2023-09-09 18:49:38 +02:00
2022-11-09 17:52:57 +01:00
module . exports = function ( app , config , renderTemplate ) {
2022-11-18 10:42:25 +01:00
app . get ( "/download" , async function ( req , res ) {
2023-03-26 14:30:07 +02:00
try {
2023-04-28 22:39:55 +02:00
var v = req . query . v ;
2022-11-18 10:42:25 +01:00
2023-04-28 22:39:55 +02:00
renderTemplate ( res , req , "download.ejs" , {
2023-05-16 20:14:11 +02:00
v ,
2023-04-28 22:39:55 +02:00
color : await modules
. getColors ( ` https://i.ytimg.com/vi/ ${ v } /maxresdefault.jpg ` )
. then ( ( colors ) => colors [ 0 ] . hex ( ) ) ,
2023-11-01 16:53:37 +01:00
isMobile : req . useragent . isMobile ,
2023-04-28 22:39:55 +02:00
} ) ;
2023-05-16 20:14:11 +02:00
} catch {
2023-04-28 22:39:55 +02:00
res . redirect ( "/" ) ;
2023-03-26 14:30:07 +02:00
}
2022-11-18 10:42:25 +01:00
} ) ;
2023-11-01 16:53:37 +01:00
2022-11-18 10:42:25 +01:00
app . get ( "/old/watch" , async function ( req , res ) {
var v = req . query . v ;
var e = req . query . e ;
if ( ! v ) res . redirect ( "/" ) ;
res . redirect ( ` /watch?v= ${ v } ` ) ;
2022-11-09 17:52:57 +01:00
} ) ;
2022-11-18 10:42:25 +01:00
2023-12-26 15:48:46 +01:00
app . get ( "/api/getchanneltabs" , async function ( req , res ) {
2023-11-16 07:15:20 +01:00
res . json ( ChannelTabs ) ;
} ) ;
2022-11-18 10:42:25 +01:00
app . get ( "/search" , async ( req , res ) => {
2024-08-22 23:24:02 +02:00
const query = req . query . query . replace ( "ohio" , "things to do in ohio" ) ;
2023-10-17 21:42:12 +02:00
const tab = req . query . tab ;
2023-11-01 16:53:37 +01:00
const { fetch } = await import ( "undici" ) ;
2023-10-17 21:42:12 +02:00
2024-01-05 17:26:58 +01:00
var media _proxy = config . media _proxy ;
2023-10-17 21:42:12 +02:00
2024-01-05 17:26:58 +01:00
if ( req . useragent . source . includes ( "Pardus" ) ) {
var media _proxy = "https://media-proxy.ashley0143.xyz" ;
}
2023-05-16 20:14:11 +02:00
var uaos = req . useragent . os ;
var IsOldWindows ;
if ( uaos == "Windows 7" && req . useragent . browser == "Firefox" ) {
IsOldWindows = true ;
} else if ( uaos == "Windows 8" && req . useragent . browser == "Firefox" ) {
IsOldWindows = true ;
} else {
IsOldWindows = false ;
}
const poketube _universe _value = "poketube_smart_search" ;
2024-04-29 23:11:50 +02:00
if ( query ) {
2024-05-04 07:31:35 +02:00
let redirectTo = null ;
2024-08-21 00:39:42 +02:00
let splitParam = ":" ;
2024-04-29 23:11:50 +02:00
2024-05-04 07:31:35 +02:00
if ( query . includes ( "youtube.com/watch?v=" ) ) {
2024-05-05 14:57:16 +02:00
redirectTo = "/watch" ;
splitParam = "?v=" ;
2024-08-21 00:39:42 +02:00
} else if ( query . includes ( "channel" ) ) {
2024-04-29 23:11:50 +02:00
redirectTo = "/channel?id=" ;
2024-08-21 00:39:42 +02:00
} else if ( query . includes ( "video" ) ) {
2024-04-29 23:11:50 +02:00
redirectTo = "/watch?v=" ;
2024-05-04 07:31:35 +02:00
}
2024-04-29 23:11:50 +02:00
2024-05-04 07:31:35 +02:00
if ( redirectTo ) {
2024-04-29 23:11:50 +02:00
try {
2024-05-05 14:57:16 +02:00
const id = query . split ( splitParam ) [ 1 ] ;
res . redirect ( ` ${ redirectTo } ${ splitParam } ${ id } ` ) ;
2024-04-29 23:11:50 +02:00
} catch {
2024-05-04 07:31:35 +02:00
return ;
2024-04-29 23:11:50 +02:00
}
2024-05-04 07:31:35 +02:00
}
2023-05-07 13:28:54 +02:00
}
2023-05-16 20:14:11 +02:00
2023-09-09 18:49:38 +02:00
if ( query && query . startsWith ( "!" ) && query . length > 2 ) {
res . redirect ( "https://lite.duckduckgo.com/lite/?q=" + query ) ;
2023-09-07 15:13:40 +02:00
}
2023-08-09 20:44:59 +02:00
2022-11-18 10:42:25 +01:00
if ( ! query ) {
return res . redirect ( "/" ) ;
}
2023-02-25 18:45:15 +01:00
let continuation = req . query . continuation || "" ;
2023-11-11 19:39:45 +01:00
let date = req . query . date || "" ;
2023-11-14 10:53:08 +01:00
let type = "video" ;
2023-11-11 19:39:45 +01:00
let duration = req . query . duration || "" ;
let sort = req . query . sort || "" ;
2023-12-26 15:48:46 +01:00
2023-02-25 18:45:15 +01:00
try {
2023-09-07 15:13:40 +02:00
const headers = { } ;
2023-08-21 23:11:46 +02:00
2023-12-26 15:48:46 +01:00
const xmlData = await fetch (
2024-03-27 17:14:28 +01:00
` ${ config . invapi } /search?q= ${ encodeURIComponent (
2023-12-26 15:48:46 +01:00
query
) } & page = $ { encodeURIComponent (
continuation
) } & date = $ { date } & type = $ { type } & duration = $ { duration } & sort = $ { sort } & hl = en + gb `
)
. then ( ( res ) => res . text ( ) )
. then ( ( txt ) => getJson ( txt ) ) ;
2023-10-18 20:20:07 +02:00
renderTemplate ( res , req , "search.ejs" , {
2023-11-11 19:39:45 +01:00
invresults : xmlData ,
2023-12-26 15:48:46 +01:00
turntomins ,
2023-11-11 19:39:45 +01:00
date ,
type ,
duration ,
sort ,
2023-10-18 20:20:07 +02:00
IsOldWindows ,
tab ,
continuation ,
2024-01-05 17:26:58 +01:00
media _proxy _url : media _proxy ,
2023-10-18 20:20:07 +02:00
results : "" ,
q : query ,
summary : "" ,
} ) ;
} catch ( error ) {
console . error ( ` Error while searching for ' ${ query } ': ` , error ) ;
res . redirect ( "/" ) ;
}
} ) ;
2023-12-26 16:23:23 +01:00
app . get ( "/im-feeling-lucky" , function ( req , res ) {
2024-04-01 17:47:51 +02:00
res . send ( "WIP" ) ;
2023-12-26 16:23:23 +01:00
} ) ;
app . get ( "/web" , async ( req , res ) => {
2024-07-15 22:21:59 +02:00
res . redirect ( "/" ) ;
2022-11-09 17:52:57 +01:00
} ) ;
2022-11-18 10:42:25 +01:00
2023-02-25 18:45:15 +01:00
app . get ( "/channel/" , async ( req , res ) => {
2023-08-24 21:22:16 +02:00
const { fetch } = await import ( "undici" ) ;
2022-12-21 09:25:16 +01:00
try {
2024-01-05 17:26:58 +01:00
var media _proxy = config . media _proxy ;
if ( req . useragent . source . includes ( "Pardus" ) ) {
var media _proxy = "https://media-proxy.ashley0143.xyz" ;
}
2024-01-04 16:51:08 +01:00
var ID = req . query . id ;
2024-01-05 17:26:58 +01:00
if ( ID . endsWith ( "@youtube.com" ) ) {
ID = ID . slice ( 0 , - "@youtube.com" . length ) ;
}
if ( ID . endsWith ( "@poketube.fun" ) ) {
ID = ID . slice ( 0 , - "@poketube.fun" . length ) ;
2024-01-04 16:51:08 +01:00
}
2024-07-15 22:21:59 +02:00
2024-01-05 17:26:58 +01:00
const tab = req . query . tab ;
2023-04-28 22:39:55 +02:00
const cache = { } ;
2024-07-15 19:55:56 +02:00
try {
// about
const bout = await fetch ( config . tubeApi + ` channel?id= ${ ID } &tab=about ` ) ;
const h = await bout . text ( ) ;
var boutJson = JSON . parse ( modules . toJson ( h ) ) ;
} catch {
boutJson = " " ;
}
2023-03-12 21:05:28 +01:00
const continuation = req . query . continuation
? ` &continuation= ${ req . query . continuation } `
: "" ;
const continuationl = req . query . continuationl
? ` &continuation= ${ req . query . continuationl } `
: "" ;
const continuations = req . query . continuations
? ` &continuation= ${ req . query . continuations } `
: "" ;
2023-02-25 18:45:15 +01:00
const sort _by = req . query . sort _by || "newest" ;
2024-07-15 22:00:18 +02:00
2024-07-15 22:09:54 +02:00
const getChannelData = async ( url ) => {
try {
return await fetch ( url )
. then ( ( res ) => res . text ( ) )
. then ( ( txt ) => getJson ( txt ) ) ;
} catch ( error ) {
return null ;
}
} ;
2024-07-15 22:04:43 +02:00
2024-07-15 22:09:54 +02:00
const apiUrl = config . invapi + "/channels/" ;
const channelUrl = ` ${ apiUrl } ${ atob (
ChannelTabs . videos
) } / $ { ID } / ? sort _by = $ { sort _by } $ { continuation } ` ;
const shortsUrl = ` ${ apiUrl } ${ ID } / ${ atob (
ChannelTabs . shorts
) } ? sort _by = $ { sort _by } $ { continuations } ` ;
const streamUrl = ` ${ apiUrl } ${ ID } / ${ atob (
ChannelTabs . streams
) } ? sort _by = $ { sort _by } $ { continuationl } ` ;
const communityUrl = ` ${ apiUrl } ${ atob (
ChannelTabs . community
) } / $ { ID } / ? hl = en - US ` ;
const PlaylistUrl = ` ${ apiUrl } ${ atob (
ChannelTabs . playlist
) } / $ { ID } / ? hl = en - US ` ;
const channelINVUrl = ` ${ apiUrl } ${ ID } / ` ;
2024-07-15 22:21:59 +02:00
const pronoun = "no pronouns :c" ;
2024-07-15 22:00:18 +02:00
2024-07-15 22:09:54 +02:00
var [ tj , shorts , playlist , stream , c , cinv ] = await Promise . all ( [
getChannelData ( channelUrl ) ,
getChannelData ( shortsUrl ) ,
getChannelData ( PlaylistUrl ) ,
getChannelData ( streamUrl ) ,
getChannelData ( communityUrl ) ,
getChannelData ( channelINVUrl ) ,
] ) ;
2023-09-07 15:13:40 +02:00
2024-08-17 20:40:21 +02:00
2024-09-03 01:13:16 +02:00
var bannedchannels = [ "UC1okSIA8UEY8OqvtjGHFvzA" , "UClsVg5LkK2COQRo1mVS4taA" , "UCIr4vkCsn0tdTW2xZ1jRG1g" ] ;
2024-08-17 21:13:24 +02:00
var bypassQuery = "cG9rZXR1YmVjaGFubmVsYnlwYXNzbG9scGVvcGxldGhpbmt0aGlzaXNjZW5zb3JzaGlwLTQ1OTBh" ;
var bypassExists = req . query . bypass === bypassQuery ;
var tabExists = 'tab' in req . query ;
var continuationExists = 'continuation' in req . query ;
if ( ID . includes ( bannedchannels ) && ! bypassExists && ! tabExists && ! continuationExists ) {
var cinv = {
2024-08-17 21:22:42 +02:00
error : ` this channel may include disinformation. If you still wanna view content <a href="/channel?id= ${ ID } &bypass= ${ bypassQuery } ">click here</a> to bypass this restriction. `
2024-08-17 21:16:43 +02:00
}
2024-08-17 21:13:24 +02:00
}
2024-08-17 20:40:21 +02:00
2023-12-26 15:48:46 +01:00
function getThumbnailUrl ( video ) {
const maxresDefaultThumbnail = video . videoThumbnails . find (
( thumbnail ) => thumbnail . quality === " maxresdefault "
) ;
if ( maxresDefaultThumbnail ) {
return ` https://vid.puffyan.us/vi/ ${ video . videoId } /maxresdefault.jpg ` ;
} else {
return ` https://vid.puffyan.us/vi/ ${ video . videoId } /hqdefault.jpg ` ;
}
}
2023-04-28 22:39:55 +02:00
cache [ ID ] = {
result : {
tj ,
shorts ,
stream ,
c ,
2023-08-18 18:27:48 +02:00
cinv ,
2024-07-15 19:55:56 +02:00
boutJson ,
2023-04-28 22:39:55 +02:00
} ,
timestamp : Date . now ( ) ,
} ;
if ( cache [ ID ] && Date . now ( ) - cache [ ID ] . timestamp < 3600000 ) {
2024-07-15 19:55:56 +02:00
var { tj , shorts , stream , c , boutJson } = cache [ ID ] . result ;
2023-04-28 22:39:55 +02:00
}
2023-09-07 15:13:40 +02:00
2024-07-23 21:39:38 +02:00
const subscribers = convert ( cinv ? . subCount ) ;
2024-07-15 19:55:56 +02:00
const about = boutJson ? . Channel ? . Contents ? . ItemSection ? . About ;
const description = about ? . Description . toString ( ) . replace (
/\n/g ,
" <br> "
) ;
const dnoreplace = about ? . Description . toString ( ) ;
2024-05-26 22:40:10 +02:00
2024-07-15 22:21:59 +02:00
if ( continuation ) {
const currentAuthorId = String ( cinv . authorId ) . trim ( ) ;
2024-07-26 00:22:01 +02:00
const firstVideoAuthorId = String ( tj ? . videos [ 0 ] . authorId ) . trim ( ) ;
2024-07-15 22:21:59 +02:00
if ( currentAuthorId . localeCompare ( firstVideoAuthorId ) !== 0 ) {
res . status ( 400 ) . send ( "Continuation does not match the channel :c" ) ;
}
}
2024-07-24 12:12:48 +02:00
let ChannelFirstVideoObject = {
2024-07-15 22:21:59 +02:00
subCountText : "0" ,
authorVerified : false ,
} ;
2024-07-15 16:53:00 +02:00
2023-02-25 18:45:15 +01:00
renderTemplate ( res , req , "channel.ejs" , {
ID ,
tab ,
shorts ,
2024-07-15 22:21:59 +02:00
firstVideo : ChannelFirstVideoObject ,
2024-07-15 19:55:56 +02:00
j : boutJson ,
2023-02-25 18:45:15 +01:00
sort : sort _by ,
stream ,
tj ,
c ,
2023-08-18 18:27:48 +02:00
cinv ,
2023-02-25 18:45:15 +01:00
convert ,
turntomins ,
2024-04-02 23:44:19 +02:00
pronoun ,
2024-01-05 17:26:58 +01:00
media _proxy _url : media _proxy ,
2023-02-25 18:45:15 +01:00
dnoreplace ,
2023-12-26 15:48:46 +01:00
getThumbnailUrl ,
2023-02-25 18:45:15 +01:00
continuation ,
2023-08-09 20:44:59 +02:00
wiki : "" ,
2023-02-25 18:45:15 +01:00
getFirstLine ,
isMobile : req . useragent . isMobile ,
about ,
2024-02-09 17:47:41 +01:00
playlist ,
2023-02-25 18:45:15 +01:00
subs :
typeof subscribers === "string"
? subscribers . replace ( "subscribers" , "" )
: "None" ,
desc : dnoreplace === "[object Object]" ? "" : description ,
} ) ;
} catch ( error ) {
console . error ( "Failed to render channel page:" , error ) ;
res . redirect ( "/" ) ;
}
} ) ;
2022-11-18 10:42:25 +01:00
} ;