You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

122 lines
3.4 KiB

/**
* Recognize well-known RSS / Atom feed URL shapes (Spotifeed, YouTube, FeedBurner, etc.)
* for friendlier labels in the UI. Not exhaustive — unknown URLs return null.
*/
export type StandardRssFeedIcon = 'music' | 'youtube' | 'feedburner' | 'reddit' | 'substack' | 'medium' | 'rss'
export type StandardRssFeedProfile = {
icon: StandardRssFeedIcon
/** i18n key under translation */
labelKey: string
/** English fallback if catalog missing */
defaultLabel: string
/** Short secondary line (e.g. truncated id) */
detail?: string
}
function truncateId(id: string, max = 14): string {
const t = id.trim()
if (t.length <= max) return t
return `${t.slice(0, max)}`
}
/**
* Returns a profile when `feedUrl` matches a known pattern; otherwise null.
*/
export function getStandardRssFeedProfile(feedUrl: string): StandardRssFeedProfile | null {
let u: URL
try {
u = new URL(feedUrl.trim())
} catch {
return null
}
const host = u.hostname.replace(/^www\./, '').toLowerCase()
if (host === 'spotifeed.timdorr.com') {
const id = u.pathname.replace(/^\//, '').split('/').filter(Boolean)[0] ?? ''
return {
icon: 'music',
labelKey: 'standardRssFeed_spotifeed',
defaultLabel: 'Spotify playlist (Spotifeed)',
detail: id ? truncateId(id, 22) : undefined
}
}
if (host === 'youtube.com' || host === 'm.youtube.com' || host === 'music.youtube.com') {
if (u.pathname.includes('/feeds/videos.xml')) {
const ch = u.searchParams.get('channel_id')
const pl = u.searchParams.get('playlist_id')
if (ch) {
return {
icon: 'youtube',
labelKey: 'standardRssFeed_youtubeChannel',
defaultLabel: 'YouTube channel feed',
detail: truncateId(ch, 18)
}
}
if (pl) {
return {
icon: 'youtube',
labelKey: 'standardRssFeed_youtubePlaylist',
defaultLabel: 'YouTube playlist feed',
detail: truncateId(pl, 18)
}
}
return {
icon: 'youtube',
labelKey: 'standardRssFeed_youtube',
defaultLabel: 'YouTube feed'
}
}
}
if (host.endsWith('feedburner.com') || host.endsWith('feedburner.google.com')) {
return {
icon: 'feedburner',
labelKey: 'standardRssFeed_feedburner',
defaultLabel: 'FeedBurner'
}
}
if (host === 'reddit.com' || host.endsWith('.reddit.com')) {
const p = u.pathname
if (p.endsWith('.rss') || p.includes('/.rss') || p.endsWith('/rss') || p.includes('/rss/')) {
return {
icon: 'reddit',
labelKey: 'standardRssFeed_reddit',
defaultLabel: 'Reddit RSS'
}
}
}
if (host.endsWith('.substack.com') && u.pathname.startsWith('/feed')) {
return {
icon: 'substack',
labelKey: 'standardRssFeed_substack',
defaultLabel: 'Substack'
}
}
if (host === 'medium.com' || host.endsWith('.medium.com')) {
if (u.pathname.startsWith('/feed') || u.pathname.startsWith('/@')) {
return {
icon: 'medium',
labelKey: 'standardRssFeed_medium',
defaultLabel: 'Medium'
}
}
}
return null
}
/** Hostname for display when there is no known profile. */
export function getRssFeedUrlHostname(feedUrl: string): string {
try {
return new URL(feedUrl.trim()).hostname.replace(/^www\./, '')
} catch {
return feedUrl.trim().slice(0, 80)
}
}