-
Notifications
You must be signed in to change notification settings - Fork 12
/
oembed.go
235 lines (187 loc) · 5.12 KB
/
oembed.go
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package oembed
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
)
// replacements to convert patterns to regexes
var (
su2re1 = regexp.MustCompile("^(https?://[^/]*?)\\*(.+)$")
su2re2 = regexp.MustCompile("^(https?://[^/]*?/.*?)\\*(.+)$")
su2re3 = regexp.MustCompile("^(https?://.*?)\\*$")
su2re4 = regexp.MustCompile("^http://")
)
// Oembed contains list of available oembed items (official endpoints)
type Oembed struct {
items []*Item
}
// Endpoint contains single endpoint to check against
type Endpoint struct {
URL string `json:"url"`
Discovery bool `json:"discovery,omitempty"`
Schemes []string `json:"schemes,omitempty"`
}
// Provider contains a single provider which can have multiple endpoints
type Provider struct {
Name string `json:"provider_name"`
URL string `json:"provider_url"`
Endpoints []Endpoint `json:"endpoints"`
}
// Item contains data for a schema
type Item struct {
IsEndpointURLComplete bool
EndpointURL string
ProviderName string
ProviderURL string
regex *regexp.Regexp
}
// Options contains options to pass to FetchOembed method
type Options struct {
Client *http.Client
URL string
MaxWidth int
MaxHeight int
AcceptLanguage string
ExtraOpts url.Values
}
// ComposeURL returns url of oembed resource ready to be queried
func (item *Item) ComposeURL(u string) string {
if item.IsEndpointURLComplete {
return item.EndpointURL
}
return item.EndpointURL + url.QueryEscape(u)
}
func (item *Item) parseOembed(u string, resp *http.Response) (*Info, error) {
var err error
if resp.StatusCode > 200 {
return &Info{Status: resp.StatusCode}, nil
}
reader := io.LimitReader(resp.Body, 40000) // 40 KB max
info := NewInfo()
err = info.FillFromJSON(reader)
if err != nil {
return nil, err
}
if len(info.URL) == 0 {
info.URL = u
}
if len(info.ProviderURL) == 0 {
info.ProviderURL = item.ProviderURL
}
if len(info.ProviderName) == 0 {
info.ProviderName = item.ProviderName
}
return info, nil
}
// FetchOembed return oembed info from an url containing it
func (item *Item) FetchOembed(opts Options) (*Info, error) {
resURL := item.ComposeURL(opts.URL)
params := url.Values{}
if opts.MaxWidth > 0 {
params.Add("maxwidth", fmt.Sprintf("%d", opts.MaxWidth))
}
if opts.MaxHeight > 0 {
params.Add("maxheight", fmt.Sprintf("%d", opts.MaxHeight))
}
if len(params) > 0 {
resURL = fmt.Sprintf("%s&%s", resURL, params.Encode())
}
if len(opts.ExtraOpts) > 0 {
resURL = fmt.Sprintf("%s&%s", resURL, opts.ExtraOpts.Encode())
}
req, err := http.NewRequest("GET", resURL, nil)
if err != nil {
return nil, err
}
if len(opts.AcceptLanguage) > 0 {
req.Header.Add("Accept-Language", opts.AcceptLanguage)
}
var resp *http.Response
if opts.Client != nil {
resp, err = opts.Client.Do(req)
} else {
client := &http.Client{}
resp, err = client.Do(req)
}
if err != nil {
return nil, err
}
defer resp.Body.Close()
return item.parseOembed(opts.URL, resp)
}
// MatchURL tests if given url applies to the endpoint
func (item *Item) MatchURL(url string) bool {
return item.regex.MatchString(strings.Trim(url, "\r\n"))
}
// NewOembed creates Oembed instance
func NewOembed() *Oembed {
return &Oembed{}
}
// ParseProviders build oembed endpoint list based on provided json stream
func (o *Oembed) ParseProviders(buf io.Reader) error {
var providers []Provider
data, err := ioutil.ReadAll(buf)
if err != nil {
return err
}
err = json.Unmarshal(data, &providers)
if err != nil {
return err
}
var items []*Item
for _, provider := range providers {
for _, endpoint := range provider.Endpoints {
if len(endpoint.Schemes) == 0 {
endpoint.Schemes = append(endpoint.Schemes, strings.TrimRight(provider.URL, "/")+"/*")
}
for _, schema := range endpoint.Schemes {
or := &Item{ProviderName: provider.Name, ProviderURL: provider.URL}
or.EndpointURL = o.prepareEndpointURL(endpoint.URL)
or.regex = o.convertSchemaURL2Regexp(schema)
items = append(items, or)
}
}
}
o.items = items
return nil
}
// FindItem returns Oembed item based on provided url
func (o *Oembed) FindItem(url string) *Item {
for _, or := range o.items {
if or.MatchURL(url) {
return or
}
}
return nil
}
// TODO: add more intelligent parameters parsing
func (o *Oembed) prepareEndpointURL(url string) string {
url = strings.Replace(url, "{format}", "json", -1)
url = strings.Replace(url, "/*", "", -1) // hack for Ora TV.. wtf they put in?
if strings.IndexRune(url, '?') == -1 {
url += "?format=json&url="
} else {
url += "&format=json&url="
}
return url
}
func (o *Oembed) convertSchemaURL2Regexp(url string) *regexp.Regexp {
// domain replacements
url = strings.Replace(url, "?", "\\?", -1)
url = su2re1.ReplaceAllString(url, "${1}[^/]%?${2}")
url = su2re2.ReplaceAllString(url, "${1}.%?${2}")
url = su2re3.ReplaceAllString(url, "${1}.%")
url = su2re4.ReplaceAllString(url, "https?://")
url = strings.Replace(url, "%", "*", -1)
////
res, err := regexp.Compile("^" + url + "$")
if err != nil {
panic(err)
}
return res
}