Skip to content

Commit

Permalink
feat: add media item favicons
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmaddock committed Jul 8, 2020
1 parent 38e31c5 commit 299ca88
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 10 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions packages/metastream-app/src/components/media/Media.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
justify-content: space-between;
}

/* .top {
.favicon {
align-self: center;
margin-right: 0.5rem;
width: 1.25rem;
}

.bottom {
} */

.title {
composes: single-line from '~styles/text.css';
flex: 1 1 100%;
Expand Down
19 changes: 19 additions & 0 deletions packages/metastream-app/src/components/media/MediaItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import styles from './Media.css'
import { formatMs } from 'utils/time'

import { IconButton } from '../common/button'
import { assetUrl } from 'utils/appUrl'

const DEFAULT_FAVICON = assetUrl('icons/favicon-default.svg')

interface IProps {
media: IMediaItem
Expand All @@ -24,8 +27,24 @@ export class MediaItem extends Component<IProps, IState> {
render(): JSX.Element | null {
const { media } = this.props

let hostname

try {
hostname = new URL(media.url).hostname
} catch {}

return (
<figure className={styles.container}>
<img
src={media.favicon || DEFAULT_FAVICON}
className={styles.favicon}
title={hostname}
onError={e => {
if (e.target && (e.target as any).tagName === 'IMG') {
;(e.target as any).src = DEFAULT_FAVICON
}
}}
/>
<figcaption className={styles.media}>
<div className={styles.title} title={media.title}>
{media.title}
Expand Down
3 changes: 2 additions & 1 deletion packages/metastream-app/src/lobby/actions/media-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ const requestMedia = (opts: MediaRequestOptions): RpcThunk<Promise<MediaRequestR
ownerId: userId,
ownerName: getUserName(getState(), userId),
hasMore: res.hasMore,
startTime: opts.time && res.duration && opts.time < res.duration ? opts.time : undefined
startTime: opts.time && res.duration && opts.time < res.duration ? opts.time : undefined,
favicon: res.favicon
}

if (res.state) {
Expand Down
3 changes: 3 additions & 0 deletions packages/metastream-app/src/lobby/reducers/mediaPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export interface IMediaItem {

/** Time to start playback at. */
startTime?: number

/** Website favicon URL. */
favicon?: string
}

export interface PendingMedia {
Expand Down
52 changes: 49 additions & 3 deletions packages/metastream-app/src/media/middleware/html.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
import { load } from 'cheerio'
import { IMediaMiddleware } from '../types'
import { IMediaMiddleware, IMediaContext } from '../types'
import { fetchText } from 'utils/http'
import { MEDIA_USER_AGENT } from 'constants/http'

const parseTitle = ($: CheerioStatic) =>
$('title')
.text()
.trim()

/** Prefer non-ico icons. */
const sortIcons = (a: CheerioElement, b: CheerioElement) => {
const aIco = a.attribs.href.includes('.ico')
const bIco = b.attribs.href.includes('.ico')

if (aIco && !bIco) return 1
if (!aIco && bIco) return -1
return 0
}

const parseFavicon = (ctx: IMediaContext, $: CheerioStatic) => {
const icons = Array.from($('head link')).filter(icon => {
const rel = new Set((icon.attribs.rel || '').split(' '))
if (!rel.has('icon')) return false
return true
})

if (icons.length === 0) return

icons.sort(sortIcons)

const icon = icons[0]
let url

try {
url = new URL(icon.attribs.href).href
} catch {}

if (!url) {
try {
url = new URL(`${ctx.req.url.origin}${icon.attribs.href}`).href
} catch {}
}

return url
}

const mware: IMediaMiddleware = {
match({ protocol }) {
return protocol === 'http:' || protocol === 'https:'
Expand Down Expand Up @@ -35,8 +77,12 @@ const mware: IMediaMiddleware = {
ctx.state.body = text
const $ = (ctx.state.$ = load(text))

// prettier-ignore
ctx.res.title = $('title').text().trim() || ctx.res.title
try {
ctx.res.title = parseTitle($) || ctx.res.title
ctx.res.favicon = parseFavicon(ctx, $)
} catch (e) {
console.error(e)
}

return next()
}
Expand Down
3 changes: 3 additions & 0 deletions packages/metastream-app/src/media/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export interface IMediaResponse {

/** Milliseconds */
startTime?: number

/** Website favicon URL. */
favicon?: string
}

export interface IMediaContext {
Expand Down

0 comments on commit 299ca88

Please sign in to comment.