Skip to content

Commit ed907a4

Browse files
authored
Merge pull request #21 from otiai10/develop
v2
2 parents 7d11cc9 + 1a91acd commit ed907a4

23 files changed

+627
-467
lines changed

README.md

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Open Graph Parser for Golang
22

3+
Go implementation of https://ogp.me/
4+
35
[![Go](https://github.com/otiai10/opengraph/workflows/Go/badge.svg)](https://github.com/otiai10/opengraph/actions)
46
[![codecov](https://codecov.io/gh/otiai10/opengraph/branch/master/graph/badge.svg)](https://codecov.io/gh/otiai10/opengraph)
57
[![GoDoc](https://godoc.org/github.com/otiai10/opengraph?status.svg)](https://pkg.go.dev/github.com/otiai10/opengraph)
@@ -11,27 +13,57 @@ package main
1113

1214
import (
1315
"fmt"
14-
"github.com/otiai10/opengraph"
16+
"github.com/otiai10/opengraph/v2"
1517
)
1618

1719
func main() {
18-
og, err := opengraph.Fetch("https://www.youtube.com/watch?v=5blm22DeeHY")
19-
fmt.Printf("OpenGraph: %+v\nError: %v\n", og, err)
20+
ogp, err := opengraph.Fetch("https://github.com/")
21+
fmt.Println(ogp, err)
2022
}
2123
```
2224

25+
# Advanced usage
26+
27+
Set an option for fetching:
28+
```go
29+
intent := opengraph.Intent{
30+
Context: ctx,
31+
HTTPClient: client,
32+
Strict: true,
33+
TrustedTags: []string{"meta", "title"},
34+
}
35+
ogp, err := opengraph.Fetch("https://ogp.me", intent)
36+
```
37+
38+
Use any `io.Reader` as a data source:
39+
```go
40+
f, _ := os.Open("my_test.html")
41+
defer f.Close()
42+
ogp := &opengraph.OpenGraph{}
43+
err := ogp.Parse(f)
44+
```
45+
46+
of if you already have parsed `*html.Node`:
47+
48+
```go
49+
err := ogp.Walk(node)
50+
```
51+
52+
Do you wanna make absolute URLs?:
53+
```go
54+
ogp.Image[0].URL // /logo.png
55+
ogp.ToAbs()
56+
ogp.Image[0].URL // https://ogp.me/logo.png
57+
```
58+
2359
# CLI as a working example
2460

2561
```sh
2662
% go get github.com/otiai10/opengraph/ogp
2763
% ogp --help
64+
% ogp -A otiai10.com
2865
```
2966

30-
For more details, see [ogp/main.go](https://github.com/otiai10/opengraph/blob/master/ogp/main.go).
31-
32-
# Advanced
67+
# Issues
3368

34-
- [`og.Parse(body *io.Reader)`](https://godoc.org/github.com/otiai10/opengraph#OpenGraph.Parse) to re-use `*http.Response`
35-
- [`og.HTTPClient`](https://godoc.org/github.com/otiai10/opengraph#OpenGraph) to customize `*http.Client` for fetching
36-
- [`og.ToAbsURL()`](https://godoc.org/github.com/otiai10/opengraph#OpenGraph.ToAbsURL) to restore relative URL, e.g. `og.Favicon`
37-
- ~~[`og.Fulfill()`](https://godoc.org/github.com/otiai10/opengraph#OpenGraph.Fulfill) to fill empty fileds.~~ You ain't gonna need it
69+
- https://github.com/otiai10/opengraph/issues

all_test.go

Lines changed: 127 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,157 +2,180 @@ package opengraph
22

33
import (
44
"context"
5-
"fmt"
6-
"html/template"
75
"net/http"
86
"net/http/httptest"
7+
"net/url"
8+
"os"
9+
"strconv"
10+
"strings"
911
"testing"
1012
"time"
1113

12-
"github.com/otiai10/marmoset"
14+
mmst "github.com/otiai10/marmoset"
1315
. "github.com/otiai10/mint"
1416
"golang.org/x/net/html"
1517
)
1618

19+
var testserver *httptest.Server
20+
21+
func TestMain(m *testing.M) {
22+
testserver = createTestServer()
23+
code := m.Run()
24+
os.Exit(code)
25+
}
26+
1727
func TestNew(t *testing.T) {
18-
og := New(dummyServer(1).URL)
28+
og := New("https://github.com")
1929
Expect(t, og).TypeOf("*opengraph.OpenGraph")
20-
Expect(t, og.Error).ToBe(nil)
21-
22-
When(t, "invalid url is given", func(t *testing.T) {
23-
og := New(":invalid_url")
24-
Expect(t, og.Error).Not().ToBe(nil)
25-
})
2630
}
2731

2832
func TestFetch(t *testing.T) {
33+
og, err := Fetch(testserver.URL + "/case/01_hello")
34+
Expect(t, err).ToBe(nil)
35+
Expect(t, og).TypeOf("*opengraph.OpenGraph")
36+
Expect(t, og.Description).ToBe("This description should be preferred")
37+
38+
When(t, "invalid URL provided", func(t *testing.T) {
39+
_, err := Fetch(":SOME_INVALID_URL")
40+
Expect(t, err).Not().ToBe(nil)
41+
})
2942

30-
When(t, "invalid scheme is given", func(t *testing.T) {
31-
_, err := Fetch(":invalid_url")
43+
When(t, "og:image provided", func(t *testing.T) {
44+
og, err := Fetch(testserver.URL + "/case/03_image")
45+
Expect(t, err).ToBe(nil)
46+
Expect(t, og).TypeOf("*opengraph.OpenGraph")
47+
})
48+
When(t, "structured image properties provided", func(t *testing.T) {
49+
og, err := Fetch(testserver.URL + "/case/04_image_props")
50+
Expect(t, err).ToBe(nil)
51+
Expect(t, og).TypeOf("*opengraph.OpenGraph")
52+
})
53+
When(t, "content-type is NOT text/html", func(t *testing.T) {
54+
_, err := Fetch(testserver.URL + "/case/00_data.json")
3255
Expect(t, err).Not().ToBe(nil)
56+
Expect(t, err.Error()).ToBe("Content type must be text/html")
57+
})
58+
When(t, "strict flag is on", func(t *testing.T) {
59+
og, err := Fetch(testserver.URL+"/case/03_image", Intent{Strict: true})
60+
Expect(t, err).ToBe(nil)
61+
Expect(t, og.Title).ToBe("")
62+
og, err = Fetch(testserver.URL+"/case/03_image", Intent{Strict: false})
63+
Expect(t, err).ToBe(nil)
64+
Expect(t, og.Title).ToBe("TEST: 03_image (title tag)")
3365
})
34-
When(t, "invalid url is given", func(t *testing.T) {
35-
_, err := Fetch("htt://xxx/yyy")
66+
When(t, "context is specified", func(t *testing.T) {
67+
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
68+
intent := Intent{
69+
Context: ctx,
70+
}
71+
ogp, err := Fetch(testserver.URL+"/slow/100", intent)
72+
cancel()
73+
Expect(t, err).ToBe(nil)
74+
Expect(t, ogp.Title).ToBe("Hello! Open Graph!!")
75+
ctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)
76+
_, err = Fetch(testserver.URL+"/slow/300", intent)
77+
cancel()
3678
Expect(t, err).Not().ToBe(nil)
3779
})
38-
39-
s := dummyServer(1)
40-
og, err := Fetch(s.URL)
41-
42-
Expect(t, err).ToBe(nil)
43-
Expect(t, og.Title).ToBe("Hello! Open Graph!!")
44-
Expect(t, og.Type).ToBe("website")
45-
Expect(t, og.URL.Source).ToBe(s.URL)
46-
Expect(t, len(og.Image)).ToBe(1)
47-
48-
Expect(t, og.Image[0].URL).ToBe("/images/01.png")
49-
Expect(t, og.Favicon).ToBe("/images/01.favicon.png")
50-
og.ToAbsURL()
51-
Expect(t, og.Image[0].URL).ToBe(s.URL + "/images/01.png")
52-
Expect(t, og.Favicon).ToBe(s.URL + "/images/01.favicon.png")
5380
}
5481

55-
func TestFetch_02(t *testing.T) {
56-
s := dummyServer(2)
57-
og, err := Fetch(s.URL)
58-
59-
Expect(t, err).ToBe(nil)
60-
Expect(t, og.Title).ToBe("はいさいナイト")
61-
Expect(t, og.Description).ToBe("All Genre Music Party")
62-
Expect(t, og.URL.Value).ToBe("https://haisai.party/")
63-
64-
// b := bytes.NewBuffer(nil)
65-
// json.NewEncoder(b).Encode(og)
66-
// Expect(t, strings.Trim(b.String(), "\n")).ToBe(fmt.Sprintf(
67-
// `{"Policy":{"TrustedTags":["meta","link","title"]},"Title":"はいさいナイト","Type":"website","URL":{"Source":"%s","Scheme":"http","Opaque":"","User":null,"Host":"%s","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","Value":"%s"},"SiteName":"","Image":[],"Video":[],"Audio":[],"Description":"All Genre Music Party","Determiner":"","Locale":"","LocaleAlt":[],"Favicon":"/favicon.ico"}`,
68-
// s.URL,
69-
// strings.Replace(s.URL, "http://", "", -1),
70-
// og.URL.Value,
71-
// ))
72-
}
82+
func TestOpenGraph_Fetch(t *testing.T) {
83+
ogp := &OpenGraph{}
84+
err := ogp.Fetch()
85+
Expect(t, err.Error()).ToBe("no URL given yet")
7386

74-
func TestFetch_03(t *testing.T) {
75-
s := dummyServer(3)
76-
og, err := Fetch(s.URL)
87+
ogp.Intent.URL = testserver.URL + "/case/01_hello"
88+
err = ogp.Fetch()
7789
Expect(t, err).ToBe(nil)
78-
err = og.ToAbsURL().Fulfill()
79-
Expect(t, err).ToBe(nil)
80-
Expect(t, og.Image[0].URL).ToBe("http://www-cdn.jtvnw.net/images/twitch_logo3.jpg")
81-
}
8290

83-
func TestFetch_04(t *testing.T) {
84-
s := dummyServer(4)
85-
og, err := Fetch(s.URL)
86-
Expect(t, err).ToBe(nil)
87-
Expect(t, len(og.Image)).ToBe(1)
88-
Expect(t, og.Image[0].URL).ToBe("/images/01.png")
91+
When(t, "ogp already has an error", func(t *testing.T) {
92+
ogp := New(":INVALID_URL")
93+
err := ogp.Fetch()
94+
Expect(t, err).Not().ToBe(nil)
95+
})
8996
}
9097

91-
func TestFetch_05_TrustedTags(t *testing.T) {
92-
s := dummyServer(2)
93-
res, err := http.Get(s.URL)
98+
func TestFetchVideo(t *testing.T) {
99+
og, err := Fetch(testserver.URL + "/case/05_video")
94100
Expect(t, err).ToBe(nil)
95-
defer res.Body.Close()
96-
og := New(s.URL)
97-
og.Policy.TrustedTags = []string{HTMLMetaTag}
98-
err = og.Parse(res.Body)
99-
Expect(t, err).ToBe(nil)
100-
Expect(t, og.Title).Not().ToBe("はいさいナイト")
101-
Expect(t, og.Title).ToBe("")
101+
Expect(t, og.Video).ToBe([]Video{
102+
{
103+
URL: "https://www.youtube.com/embed/1MxA0i2rxQo",
104+
SecureURL: "https://www.youtube.com/embed/1MxA0i2rxQo",
105+
Type: "text/html",
106+
Width: 1280,
107+
Height: 720,
108+
},
109+
})
102110
}
103111

104-
func TestFetchWithContext(t *testing.T) {
105-
s := dummySlowServer(time.Millisecond * 300)
106-
defer s.Close()
107-
108-
ctx500ms, cancel500ms := context.WithTimeout(context.Background(), time.Millisecond*500)
109-
_, err := FetchWithContext(ctx500ms, s.URL)
112+
func TestOpenGraph_Parse(t *testing.T) {
113+
ogp := New("")
114+
r := strings.NewReader(`<html><meta property="og:title" content="test_test"></html>`)
115+
err := ogp.Parse(r)
110116
Expect(t, err).ToBe(nil)
111-
cancel500ms()
112-
113-
ctx100ms, cancel100ms := context.WithTimeout(context.Background(), time.Millisecond*100)
114-
_, err = FetchWithContext(ctx100ms, s.URL)
115-
Expect(t, err).Match(context.DeadlineExceeded.Error())
116-
cancel100ms()
117+
Expect(t, ogp.Title).ToBe("test_test")
117118
}
118119

119120
func TestOpenGraph_Walk(t *testing.T) {
120-
s := dummyServer(2)
121-
res, err := http.Get(s.URL)
121+
res, err := http.Get(testserver.URL + "/case/01_hello")
122122
Expect(t, err).ToBe(nil)
123123

124124
node, err := html.Parse(res.Body)
125125
Expect(t, err).ToBe(nil)
126126

127-
og := New(s.URL)
127+
og := New(testserver.URL + "/case/01_hello")
128128
err = og.Walk(node)
129129
Expect(t, err).ToBe(nil)
130-
Expect(t, og.Title).ToBe("はいさいナイト")
130+
Expect(t, og.Title).ToBe("Hello! Open Graph!!")
131131
res.Body.Close()
132132

133-
again := New(s.URL)
133+
again := New(testserver.URL + "/case/01_hello")
134134
err = again.Walk(node)
135135
Expect(t, err).ToBe(nil)
136-
Expect(t, again.Title).ToBe("はいさいナイト")
136+
Expect(t, again.Title).ToBe("Hello! Open Graph!!")
137137
}
138138

139-
func dummyServer(id int) *httptest.Server {
140-
marmoset.LoadViews("./test/html")
141-
r := marmoset.NewRouter()
142-
r.GET("/", func(w http.ResponseWriter, r *http.Request) {
143-
marmoset.Render(w).HTML(fmt.Sprintf("%02d", id), nil)
144-
})
145-
r.GET("/case/01", func(w http.ResponseWriter, r *http.Request) {
146-
w.WriteHeader(http.StatusNotFound)
147-
})
148-
return httptest.NewServer(r)
139+
func TestOpenGraph_ToAbs(t *testing.T) {
140+
ogp := New(testserver.URL + "/case/01_hello")
141+
err := ogp.Fetch()
142+
Expect(t, err).ToBe(nil)
143+
u, err := url.Parse(ogp.Image[0].URL)
144+
Expect(t, err).ToBe(nil)
145+
Expect(t, u.IsAbs()).ToBe(false)
146+
err = ogp.ToAbs()
147+
Expect(t, err).ToBe(nil)
148+
u, err = url.Parse(ogp.Image[0].URL)
149+
Expect(t, err).ToBe(nil)
150+
Expect(t, u.IsAbs()).ToBe(true)
151+
u, err = url.Parse(ogp.Audio[0].URL)
152+
Expect(t, err).ToBe(nil)
153+
Expect(t, u.IsAbs()).ToBe(true)
149154
}
150155

151-
func dummySlowServer(d time.Duration) *httptest.Server {
152-
var h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
153-
time.Sleep(d)
154-
t, _ := template.ParseFiles("./test/html/02.html")
155-
t.Execute(w, nil)
156+
// This server is ONLY for testing.
157+
func createTestServer() *httptest.Server {
158+
mmst.LoadViews("./test/html")
159+
r := mmst.NewRouter()
160+
r.GET("/case/(?P<name>[_a-zA-Z0-9]+)", func(w http.ResponseWriter, r *http.Request) {
161+
name := r.FormValue("name")
162+
switch {
163+
case strings.HasSuffix(name, ".json"):
164+
mmst.Render(w).JSON(200, mmst.P{})
165+
case name == "notfound":
166+
w.WriteHeader(http.StatusNotFound)
167+
default:
168+
mmst.Render(w).HTML(name, nil)
169+
}
156170
})
157-
return httptest.NewServer(h)
171+
r.GET("/slow/(?P<msec>[0-9]+)", func(w http.ResponseWriter, r *http.Request) {
172+
msec, err := strconv.Atoi(r.FormValue("msec"))
173+
if err != nil {
174+
w.WriteHeader(404)
175+
return
176+
}
177+
time.Sleep(time.Duration(msec) * time.Millisecond)
178+
mmst.Render(w).HTML("01_hello", nil)
179+
})
180+
return httptest.NewServer(r)
158181
}

example_test.go

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,14 @@
11
package opengraph
22

3-
import (
4-
"fmt"
5-
"log"
6-
"net/http"
7-
)
3+
import "fmt"
84

95
func ExampleFetch() {
10-
ogp, _ := Fetch("https://github.com/otiai10/gosseract")
11-
fmt.Println(ogp.Title)
12-
// Output: otiai10/gosseract
13-
}
14-
15-
func ExampleOpenGraph_Parse() {
16-
17-
client := http.DefaultClient
18-
res, err := client.Get("https://github.com/otiai10/amesh")
19-
if err != nil {
20-
log.Fatal(err)
21-
}
22-
defer res.Body.Close()
23-
24-
ogp := new(OpenGraph)
25-
ogp.Parse(res.Body)
26-
fmt.Println(ogp.Title)
27-
// Output: otiai10/amesh
6+
ogp, err := Fetch("https://ogp.me/")
7+
fmt.Println("title:", ogp.Title)
8+
fmt.Println("type:", ogp.Type)
9+
fmt.Println("error:", err)
10+
// Output:
11+
// title: Open Graph protocol
12+
// type: website
13+
// error: <nil>
2814
}

0 commit comments

Comments
 (0)