Merge pull request #1992 from omnivore-app/feature/piped-video
Add handler for piped video
This commit is contained in:
@ -1,39 +1,40 @@
|
||||
import { AppleNewsHandler } from './websites/apple-news-handler'
|
||||
import { BloombergHandler } from './websites/bloomberg-handler'
|
||||
import { DerstandardHandler } from './websites/derstandard-handler'
|
||||
import { ImageHandler } from './websites/image-handler'
|
||||
import { MediumHandler } from './websites/medium-handler'
|
||||
import { PdfHandler } from './websites/pdf-handler'
|
||||
import { ScrapingBeeHandler } from './websites/scrapingBee-handler'
|
||||
import { TDotCoHandler } from './websites/t-dot-co-handler'
|
||||
import { TwitterHandler } from './websites/twitter-handler'
|
||||
import { YoutubeHandler } from './websites/youtube-handler'
|
||||
import { WikipediaHandler } from './websites/wikipedia-handler'
|
||||
import { GitHubHandler } from './websites/github-handler'
|
||||
import { parseHTML } from 'linkedom'
|
||||
import { Browser } from 'puppeteer-core'
|
||||
import {
|
||||
ContentHandler,
|
||||
NewsletterInput,
|
||||
NewsletterResult,
|
||||
PreHandleResult,
|
||||
} from './content-handler'
|
||||
import { SubstackHandler } from './newsletters/substack-handler'
|
||||
import { AxiosHandler } from './newsletters/axios-handler'
|
||||
import { GolangHandler } from './newsletters/golang-handler'
|
||||
import { MorningBrewHandler } from './newsletters/morning-brew-handler'
|
||||
import { BloombergNewsletterHandler } from './newsletters/bloomberg-newsletter-handler'
|
||||
import { BeehiivHandler } from './newsletters/beehiiv-handler'
|
||||
import { BloombergNewsletterHandler } from './newsletters/bloomberg-newsletter-handler'
|
||||
import { ConvertkitHandler } from './newsletters/convertkit-handler'
|
||||
import { RevueHandler } from './newsletters/revue-handler'
|
||||
import { GhostHandler } from './newsletters/ghost-handler'
|
||||
import { parseHTML } from 'linkedom'
|
||||
import { CooperPressHandler } from './newsletters/cooper-press-handler'
|
||||
import { HeyWorldHandler } from './newsletters/hey-world-handler'
|
||||
import { Browser } from 'puppeteer-core'
|
||||
import { StackOverflowHandler } from './websites/stack-overflow-handler'
|
||||
import { GenericHandler } from './newsletters/generic-handler'
|
||||
import { EveryIoHandler } from './newsletters/every-io-handler'
|
||||
import { EnergyWorldHandler } from './newsletters/energy-world'
|
||||
import { EveryIoHandler } from './newsletters/every-io-handler'
|
||||
import { GenericHandler } from './newsletters/generic-handler'
|
||||
import { GhostHandler } from './newsletters/ghost-handler'
|
||||
import { GolangHandler } from './newsletters/golang-handler'
|
||||
import { HeyWorldHandler } from './newsletters/hey-world-handler'
|
||||
import { IndiaTimesHandler } from './newsletters/india-times-handler'
|
||||
import { MorningBrewHandler } from './newsletters/morning-brew-handler'
|
||||
import { RevueHandler } from './newsletters/revue-handler'
|
||||
import { SubstackHandler } from './newsletters/substack-handler'
|
||||
import { AppleNewsHandler } from './websites/apple-news-handler'
|
||||
import { BloombergHandler } from './websites/bloomberg-handler'
|
||||
import { DerstandardHandler } from './websites/derstandard-handler'
|
||||
import { GitHubHandler } from './websites/github-handler'
|
||||
import { ImageHandler } from './websites/image-handler'
|
||||
import { MediumHandler } from './websites/medium-handler'
|
||||
import { PdfHandler } from './websites/pdf-handler'
|
||||
import { PipedVideoHandler } from './websites/piped-video-handler'
|
||||
import { ScrapingBeeHandler } from './websites/scrapingBee-handler'
|
||||
import { StackOverflowHandler } from './websites/stack-overflow-handler'
|
||||
import { TDotCoHandler } from './websites/t-dot-co-handler'
|
||||
import { TwitterHandler } from './websites/twitter-handler'
|
||||
import { WikipediaHandler } from './websites/wikipedia-handler'
|
||||
import { YoutubeHandler } from './websites/youtube-handler'
|
||||
|
||||
const validateUrlString = (url: string): boolean => {
|
||||
const u = new URL(url)
|
||||
@ -73,6 +74,7 @@ const contentHandlers: ContentHandler[] = [
|
||||
new SubstackHandler(),
|
||||
new StackOverflowHandler(),
|
||||
new EnergyWorldHandler(),
|
||||
new PipedVideoHandler(),
|
||||
]
|
||||
|
||||
const newsletterHandlers: ContentHandler[] = [
|
||||
|
||||
82
packages/content-handler/src/websites/piped-video-handler.ts
Normal file
82
packages/content-handler/src/websites/piped-video-handler.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import axios from 'axios'
|
||||
import _ from 'underscore'
|
||||
import { ContentHandler, PreHandleResult } from '../content-handler'
|
||||
|
||||
export class PipedVideoHandler extends ContentHandler {
|
||||
// https://piped.video/watch?v={videoId}
|
||||
PIPED_URL_MATCH = /^((?:https?:)?\/\/)?piped\.video\/watch\?v=[^&]+/
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.name = 'Piped-video'
|
||||
}
|
||||
|
||||
getYoutubeVideoId = (url: string) => {
|
||||
const u = new URL(url)
|
||||
return u.searchParams.get('v')
|
||||
}
|
||||
|
||||
escapeTitle = (title: string) => {
|
||||
return _.escape(title)
|
||||
}
|
||||
|
||||
shouldPreHandle(url: string): boolean {
|
||||
return this.PIPED_URL_MATCH.test(url.toString())
|
||||
}
|
||||
|
||||
async preHandle(url: string): Promise<PreHandleResult> {
|
||||
const videoId = this.getYoutubeVideoId(url)
|
||||
if (!videoId) {
|
||||
return {}
|
||||
}
|
||||
const baseUrl = 'https://api-piped.mha.fi'
|
||||
const apiUrl = `${baseUrl}/streams/${videoId}`
|
||||
const metadata = (await axios.get(apiUrl)).data as {
|
||||
title: string
|
||||
thumbnailUrl: string
|
||||
uploader: string
|
||||
uploaderUrl: string
|
||||
uploadDate: string
|
||||
description: string
|
||||
videoStreams: {
|
||||
width: number
|
||||
height: number
|
||||
url: string
|
||||
}[]
|
||||
}
|
||||
const videoStreams = metadata.videoStreams
|
||||
if (!videoStreams || videoStreams.length == 0) {
|
||||
return {}
|
||||
}
|
||||
const videoStream = videoStreams[0]
|
||||
const src = `https://piped.mha.fi/embed/${videoId}`
|
||||
// escape html entities in title
|
||||
const title = metadata.title
|
||||
const escapedTitle = this.escapeTitle(title)
|
||||
const ratio = videoStream.width / videoStream.height
|
||||
const thumbnail = metadata.thumbnailUrl
|
||||
const height = 350
|
||||
const width = height * ratio
|
||||
const authorName = _.escape(metadata.uploader)
|
||||
const content = `
|
||||
<html>
|
||||
<head>
|
||||
<title>${escapedTitle}</title>
|
||||
<meta property="og:image" content="${thumbnail}" />
|
||||
<meta property="og:image:secure_url" content="${thumbnail}" />
|
||||
<meta property="og:title" content="${escapedTitle}" />
|
||||
<meta property="og:description" content="${metadata.description}" />
|
||||
<meta property="og:article:author" content="${authorName}" />
|
||||
<meta property="og:site_name" content="Piped Video" />
|
||||
<meta property="article:published_time" content="${metadata.uploadDate}" />
|
||||
</head>
|
||||
<body>
|
||||
<iframe width="${width}" height="${height}" src="${src}" title="${escapedTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<p><a href="${url}" target="_blank">${escapedTitle}</a></p>
|
||||
<p itemscope="" itemprop="author" itemtype="http://schema.org/Person">By <a href="https://piped.video${metadata.uploaderUrl}" target="_blank">${authorName}</a></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return { content, title }
|
||||
}
|
||||
}
|
||||
@ -184,7 +184,7 @@ Readability.prototype = {
|
||||
publishedDate: /published|modified|created|updated/i,
|
||||
replaceFonts: /<(\/?)font[^>]*>/gi,
|
||||
normalize: /\s{2,}/g,
|
||||
videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq|cdnapisec\.kaltura)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,
|
||||
videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq|cdnapisec\.kaltura)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv|piped\.mha\.fi)/i,
|
||||
shareElements: /(\b|_)(share|sharedaddy|post-tags)(\b|_)/i,
|
||||
nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
|
||||
prevLink: /(prev|earl|old|new|<|«)/i,
|
||||
|
||||
@ -80,7 +80,7 @@ function handleBackendUrl(url) {
|
||||
const FORCE_CONTENT_FETCH_URLS = [
|
||||
// twitter status url regex
|
||||
/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)(?:\/.*)?/,
|
||||
/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/,
|
||||
/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu\.be|piped\.video))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/,
|
||||
]
|
||||
return FORCE_CONTENT_FETCH_URLS.some((regex) => regex.test(url))
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user