Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 299ca88

Browse files
committedJul 8, 2020
feat: add media item favicons
1 parent 38e31c5 commit 299ca88

File tree

7 files changed

+81
-10
lines changed

7 files changed

+81
-10
lines changed
 
Loading

‎packages/metastream-app/src/components/media/Media.css

+4-6
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
justify-content: space-between;
1717
}
1818

19-
/* .top {
20-
19+
.favicon {
20+
align-self: center;
21+
margin-right: 0.5rem;
22+
width: 1.25rem;
2123
}
2224

23-
.bottom {
24-
25-
} */
26-
2725
.title {
2826
composes: single-line from '~styles/text.css';
2927
flex: 1 1 100%;

‎packages/metastream-app/src/components/media/MediaItem.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import styles from './Media.css'
44
import { formatMs } from 'utils/time'
55

66
import { IconButton } from '../common/button'
7+
import { assetUrl } from 'utils/appUrl'
8+
9+
const DEFAULT_FAVICON = assetUrl('icons/favicon-default.svg')
710

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

30+
let hostname
31+
32+
try {
33+
hostname = new URL(media.url).hostname
34+
} catch {}
35+
2736
return (
2837
<figure className={styles.container}>
38+
<img
39+
src={media.favicon || DEFAULT_FAVICON}
40+
className={styles.favicon}
41+
title={hostname}
42+
onError={e => {
43+
if (e.target && (e.target as any).tagName === 'IMG') {
44+
;(e.target as any).src = DEFAULT_FAVICON
45+
}
46+
}}
47+
/>
2948
<figcaption className={styles.media}>
3049
<div className={styles.title} title={media.title}>
3150
{media.title}

‎packages/metastream-app/src/lobby/actions/media-request.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ const requestMedia = (opts: MediaRequestOptions): RpcThunk<Promise<MediaRequestR
140140
ownerId: userId,
141141
ownerName: getUserName(getState(), userId),
142142
hasMore: res.hasMore,
143-
startTime: opts.time && res.duration && opts.time < res.duration ? opts.time : undefined
143+
startTime: opts.time && res.duration && opts.time < res.duration ? opts.time : undefined,
144+
favicon: res.favicon
144145
}
145146

146147
if (res.state) {

‎packages/metastream-app/src/lobby/reducers/mediaPlayer.ts

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export interface IMediaItem {
6464

6565
/** Time to start playback at. */
6666
startTime?: number
67+
68+
/** Website favicon URL. */
69+
favicon?: string
6770
}
6871

6972
export interface PendingMedia {

‎packages/metastream-app/src/media/middleware/html.ts

+49-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,50 @@
11
import { load } from 'cheerio'
2-
import { IMediaMiddleware } from '../types'
2+
import { IMediaMiddleware, IMediaContext } from '../types'
33
import { fetchText } from 'utils/http'
44
import { MEDIA_USER_AGENT } from 'constants/http'
55

6+
const parseTitle = ($: CheerioStatic) =>
7+
$('title')
8+
.text()
9+
.trim()
10+
11+
/** Prefer non-ico icons. */
12+
const sortIcons = (a: CheerioElement, b: CheerioElement) => {
13+
const aIco = a.attribs.href.includes('.ico')
14+
const bIco = b.attribs.href.includes('.ico')
15+
16+
if (aIco && !bIco) return 1
17+
if (!aIco && bIco) return -1
18+
return 0
19+
}
20+
21+
const parseFavicon = (ctx: IMediaContext, $: CheerioStatic) => {
22+
const icons = Array.from($('head link')).filter(icon => {
23+
const rel = new Set((icon.attribs.rel || '').split(' '))
24+
if (!rel.has('icon')) return false
25+
return true
26+
})
27+
28+
if (icons.length === 0) return
29+
30+
icons.sort(sortIcons)
31+
32+
const icon = icons[0]
33+
let url
34+
35+
try {
36+
url = new URL(icon.attribs.href).href
37+
} catch {}
38+
39+
if (!url) {
40+
try {
41+
url = new URL(`${ctx.req.url.origin}${icon.attribs.href}`).href
42+
} catch {}
43+
}
44+
45+
return url
46+
}
47+
648
const mware: IMediaMiddleware = {
749
match({ protocol }) {
850
return protocol === 'http:' || protocol === 'https:'
@@ -35,8 +77,12 @@ const mware: IMediaMiddleware = {
3577
ctx.state.body = text
3678
const $ = (ctx.state.$ = load(text))
3779

38-
// prettier-ignore
39-
ctx.res.title = $('title').text().trim() || ctx.res.title
80+
try {
81+
ctx.res.title = parseTitle($) || ctx.res.title
82+
ctx.res.favicon = parseFavicon(ctx, $)
83+
} catch (e) {
84+
console.error(e)
85+
}
4086

4187
return next()
4288
}

‎packages/metastream-app/src/media/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export interface IMediaResponse {
5656

5757
/** Milliseconds */
5858
startTime?: number
59+
60+
/** Website favicon URL. */
61+
favicon?: string
5962
}
6063

6164
export interface IMediaContext {

0 commit comments

Comments
 (0)
Please sign in to comment.