-
Notifications
You must be signed in to change notification settings - Fork 2
/
Message.js
135 lines (113 loc) · 3.75 KB
/
Message.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router'
import { Html5Entities } from 'html-entities'
import ExternalLink from './ExternalLink'
import Segmenter from '../Segmenter'
import './Message.css'
import SessionContext from './SessionContext'
import useQuery from '../useQuery'
const https = require('https')
const shortLinkCache = new Map()
async function resolveShortLink(href) {
href = href.replace('http://', 'https://')
const cached = shortLinkCache.get(href)
if (cached) {
return cached
}
console.log('resolveShortLink', href)
const result = await new Promise((resolve, reject) => {
https.get(href, (response) => {
const result = response.headers.location || null
console.log('resolveShortLink result', result, response)
shortLinkCache.set(href, result)
resolve(result)
}).on('error', (error) => {
console.log('resolveShortLink error', error)
})
})
shortLinkCache.set(href, result)
return result
}
// This only exists because DMs don't include entity information.
function TwitterShortLink(props) {
const [ realUrl, setRealUrl ] = useState(null)
resolveShortLink(props.href).then((realUrl) => {
setRealUrl(realUrl)
})
const stickerPattern = /^https:\/\/twitter\.com\/i\/stickers\/image\/([0-9]+)$/
if (realUrl && stickerPattern.test(realUrl)) {
return (
<img src={realUrl} alt="Sticker" />
)
}
const mediaPattern = /^https:\/\/twitter\.com\/messages\/media\/([0-9]+)$/
if (realUrl && mediaPattern.test(realUrl)) {
return (
<img src={realUrl} alt="Media" />
)
}
return (
<ExternalLink href={props.href}>{realUrl || props.href}</ExternalLink>
)
}
function MessageMedia(props) {
const [ dataUrl, setDataUrl ] = useState(null)
const { session } = useContext(SessionContext)
const query = useQuery()
const history = useHistory()
const mediaProvider = session.direct_message_media
const url = mediaProvider.getDirectMessageMediaUrl(props.url)
const openMedia = () => {
query.set('media', url)
history.push(history.location.pathname + '?' + query.toString())
}
mediaProvider.fetchMedia(url).then((dataUrl) => {
setDataUrl(dataUrl)
})
return (
<div className="Message-media" onClick={openMedia}>
<img alt="" src={dataUrl} />
</div>
)
}
export default function Message(props) {
const { session: { account: { accountId }}} = useContext(SessionContext)
const message = props.message
const isSelf = message.senderId === accountId
const date = message.createdDate
if (message.mediaUrls.length > 0) {
console.log('has media urls', message)
return (
<div className="Message-container" data-isself={isSelf} data-ismedia="true">
{message.mediaUrls.map((url, index) => <MessageMedia key={index} url={url} />)}
<div className="Message-date">{date.toLocaleTimeString()}</div>
</div>
)
}
const text = Html5Entities.decode(message.text)
const segmenter = new Segmenter(text.length, { style: 'normal' })
const linkPattern = /https?:\/\/t\.co\/[^\s]*/g
for (const match of text.matchAll(linkPattern)) {
segmenter.setSpan(match.index, match.index + match[0].length - 1, {
style: 'link',
href: match[0],
})
}
const segments = segmenter.array.map(({ start, end, value }, index) => {
const slice = text.slice(start, end+1)
switch (value.style) {
case 'normal':
return <span key={index}>{slice}</span>
case 'link':
return <TwitterShortLink key={index} href={value.href} />
default:
return null
}
})
return (
<div className="Message-container" data-isself={isSelf}>
<p>{segments}</p>
<div className="Message-date">{date.toLocaleTimeString()}</div>
</div>
)
}