@aidarkezio/main-func
Version:
📦 It has my funcs
168 lines (162 loc) • 5.87 kB
text/typescript
import cheerio from 'cheerio'
import got from 'got'
// eslint-disable-next-line import/extensions
import { YoutubeSearch } from './types'
type Ithumbnails = { url: string; width: number; height: number };
export default async function youtubeSearch (
query: string
): Promise<YoutubeSearch> {
const body = await got('https://www.youtube.com/results', {
headers: {
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
},
searchParams: {
search_query: query
}
}).text()
const $ = cheerio.load(body)
let sc: { [Key: string]: any }
$('script').map(function () {
const el = $(this).html()
let regex: RegExpExecArray | null
if ((regex = /var ytInitialData = /gi.exec(el || ''))) {
sc = JSON.parse(
regex.input.replace(/^var ytInitialData = /i, '').replace(/;$/, '')
)
}
return regex && sc
})
const results: YoutubeSearch = { video: [], channel: [], playlist: [] };
(
sc!.contents.twoColumnSearchResultsRenderer.primaryContents
.sectionListRenderer.contents[0].itemSectionRenderer.contents as any[]
).forEach((v: { [Key: string]: any }) => {
const typeName = Object.keys(v)[0]
const result = v[typeName]
if (['horizontalCardListRenderer', 'shelfRenderer'].includes(typeName)) { return } // Todo: add this result as results
const isChannel = typeName === 'channelRenderer'
const isVideo = typeName === 'videoRenderer'
const isMix = typeName === 'radioRenderer'
if (isVideo) {
const view: string =
result.viewCountText?.simpleText ||
result.shortViewCountText?.simpleText ||
result.shortViewCountText?.accessibility?.accessibilityData.label
const _duration = result.thumbnailOverlays?.find(
(v: { [Key: string]: any }) =>
Object.keys(v)[0] === 'thumbnailOverlayTimeStatusRenderer'
)?.thumbnailOverlayTimeStatusRenderer.text
const videoId: string = result.videoId
const duration: string =
result.lengthText?.simpleText || _duration?.simpleText
let durationS: number = 0;
(
duration?.split('.').length && duration.indexOf(':') === -1
? duration.split('.')
: duration?.split(':')
)?.forEach(
(v, i, arr) =>
(durationS +=
durationMultipliers[arr.length]['' + i] * parseInt(v))
)
results.video.push({
authorName: (result.ownerText?.runs ||
result.longBylineText?.runs ||
[])[0]?.text,
authorAvatar:
result.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer.thumbnail.thumbnails
?.filter(({ url }: Ithumbnails) => url)
?.pop().url,
videoId,
url: encodeURI('https://www.youtube.com/watch?v=' + videoId),
thumbnail: result.thumbnail.thumbnails.pop().url,
title: (
result.title?.runs.find((v: { [Key: string]: any }) => v.text)?.text ||
result.title?.accessibility.accessibilityData.label
)?.trim(),
description: result.detailedMetadataSnippets?.[0]?.snippetText.runs
?.filter(({ text }: { text: string }) => text)
?.map(({ text }: { text: string }) => text)
?.join(''),
publishedTime: result.publishedTimeText?.simpleText,
durationH:
result.lengthText?.accessibility.accessibilityData.label ||
_duration?.accessibility.accessibilityData.label,
durationS,
duration,
viewH: view,
view: (
(view?.indexOf('x') === -1
? view?.split(' ')[0]
: view?.split('x')[0]) || view
)?.trim(),
type: typeName.replace(/Renderer/i, '') as 'video'
})
}
if (isChannel) {
const channelId: string = result.channelId
const _subscriber: string =
result.subscriberCountText?.accessibility.accessibilityData.label ||
result.subscriberCountText?.simpleText
results.channel.push({
channelId,
url: encodeURI('https://www.youtube.com/channel/' + channelId),
channelName:
result.title.simpleText ||
result.shortBylineText?.runs.find(
(v: { [Key: string]: any }) => v.text
)?.text,
avatar:
'https:' +
result.thumbnail.thumbnails
.filter(({ url }: Ithumbnails) => url)
?.pop().url,
isVerified:
result.ownerBadges?.pop().metadataBadgeRenderer.style ===
'BADGE_STYLE_TYPE_VERIFIED',
subscriberH: _subscriber?.trim(),
subscriber: _subscriber?.split(' ')[0],
videoCount: parseInt(result.videoCountText?.runs[0]?.text),
description: result.descriptionSnippet?.runs
?.filter(({ text }: { text: string }) => text)
?.map(({ text }: { text: string }) => text)
?.join(''),
type: typeName.replace(/Renderer/i, '') as 'channel'
})
}
if (isMix) {
results.playlist.push({
playlistId: result.playlistId,
title: result.title.simpleText,
thumbnail: result.thumbnail.thumbnails.pop().url,
video: result.videos.map(({ childVideoRenderer }: { [Key: string]: any }) => {
return {
videoId: childVideoRenderer.videoId,
title: childVideoRenderer.title.simpleText,
durationH:
childVideoRenderer.lengthText.accessibility
.accessibilityData.label,
duration: childVideoRenderer.lengthText.simpleText
}
}),
type: 'mix'
})
}
})
return results
}
const durationMultipliers: { [key: string]: { [key: string]: number } } = {
1: {
0: 1
},
2: {
0: 60,
1: 1
},
3: {
0: 3600,
1: 60,
2: 1
}
}