diff --git a/.drone.yml b/.drone.yml index ab65c02..a9ede79 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,4 +6,7 @@ steps: - name: test image: golang commands: - - go test \ No newline at end of file + - go test + environment: + IMGUR_CLIENT_ID: + from_secret: imgur_client_id \ No newline at end of file diff --git a/imgur.go b/imgur.go index cee442b..3c374c7 100644 --- a/imgur.go +++ b/imgur.go @@ -1,9 +1,140 @@ package linkinfo +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "path/filepath" + "regexp" + "strings" +) + +const imgurApiUrl = "https://api.imgur.com/3/%s/%s.json" + var ( imgurHosts = []string{"imgur.com", "i.imgur.com"} + + imgurAlbumRegexp = regexp.MustCompile("^/(a|gallery)/") ) -func (i *LinkInfoApi) ImgurLinkHandler(link string) (*LinkInfo, error) { - return nil, nil +type ImgurOptions struct { + Option + + ClientId string +} + +type ImgurInfoApi struct { + api *LinkInfoApi + opts *ImgurOptions +} + +type imgurResponse struct { + Success bool `json:"success"` + Data imgurData `json:"data"` +} + +type imgurData struct { + Type string `json:"type"` + Title string `json:"title"` + Description string `json:"description"` + Width int `json:"width"` + Height int `json:"height"` + Animated bool `json:"animated"` + Nsfw bool `json:"nsfw"` +} + +func (i *ImgurInfoApi) Handler(link string) (*LinkInfo, error) { + if i.opts.ClientId == "" { + return nil, errors.New("invalid client id") + } + + u, err := url.Parse(link) + + if err != nil { + return nil, err + } + + id := filepath.Base(u.Path) + + extension := filepath.Ext(id) + + if extension != "" { + id = id[0 : len(id)-len(extension)] + } + + imageType := "image" + + if imgurAlbumRegexp.MatchString(link) { + imageType = "album" + } + + req, err := http.NewRequest("GET", fmt.Sprintf(imgurApiUrl, imageType, id), nil) + + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Client-ID "+i.opts.ClientId) + + res, err := i.api.Client.Do(req) + + if err != nil { + return nil, err + } + + defer res.Body.Close() + + var response imgurResponse + + if err := json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + + if !response.Success { + return nil, errors.New("imgur api returned success=false") + } + + var title string + + if response.Data.Title != "" { + title = "Imgur - " + response.Data.Title + } else { + title = "Imgur - " + id + + switch response.Data.Type { + case "image/jpeg": + title += ".jpg" + case "image/png": + title += ".png" + case "image/gif": + title += ".gif" + } + } + + attribs := make([]string, 0) + + if imageType == "album" { + attribs = append(attribs, "Album") + } else { + attribs = append(attribs, fmt.Sprintf("%dx%d", response.Data.Width, response.Data.Height)) + + if response.Data.Animated { + attribs = append(attribs, "Animated") + } + } + + title += " (" + strings.Join(attribs, ", ") + ")" + + if response.Data.Nsfw { + attribs = append(attribs, "NSFW") + } + + info := &LinkInfo{ + Title: title, + Description: response.Data.Description, + } + + return info, nil } diff --git a/imgur_test.go b/imgur_test.go new file mode 100644 index 0000000..ccd6742 --- /dev/null +++ b/imgur_test.go @@ -0,0 +1,22 @@ +package linkinfo + +import ( + "os" + "testing" +) + +func TestImgurInfoApi_Handler(t *testing.T) { + api := New(&ImgurOptions{ + ClientId: os.Getenv("IMGUR_CLIENT_ID"), + }) + + info, err := api.Imgur.Handler("https://i.imgur.com/1GKJg79.gifv") + + if err != nil { + t.Fatal("Error getting imgur info:", err) + } + + if info.Title != "Imgur - Approaching the weekend like (720x404, Animated)" { + t.Fatal("Unexpected title", info.Title) + } +} diff --git a/linkinfo.go b/linkinfo.go index b23e42f..d66cf76 100644 --- a/linkinfo.go +++ b/linkinfo.go @@ -12,7 +12,9 @@ type LinkHandler struct { } type LinkInfoApi struct { - Client *http.Client + Client *http.Client + + Imgur *ImgurInfoApi linkHandlers []*LinkHandler } @@ -25,26 +27,45 @@ type LinkInfo struct { Redirects []string `json:"redirects,omitempty"` } -func New() *LinkInfoApi { +func New(opts ...Option) *LinkInfoApi { api := &LinkInfoApi{ Client: &http.Client{ Timeout: 60 * time.Second, }, } + for _, opt := range opts { + switch opt.(type) { + case *ImgurOptions: + api.Imgur = &ImgurInfoApi{api, opt.(*ImgurOptions)} + } + } + api.registerDefaultHandlers() return api } func (i *LinkInfoApi) registerDefaultHandlers() { + i.linkHandlers = make([]*LinkHandler, 0) + + if i.Imgur != nil { + i.linkHandlers = append(i.linkHandlers, &LinkHandler{ + Hosts: imgurHosts, + Handler: i.Imgur.Handler, + }) + } + i.linkHandlers = []*LinkHandler{ {Hosts: youtubeHosts, Handler: i.YoutubeLinkHandler}, - {Hosts: imgurHosts, Handler: i.ImgurLinkHandler}, {Hosts: twitterHosts, Handler: i.TwitterLinkHandler}, } } +func (i *LinkInfoApi) AddHandler(handler *LinkHandler) { + i.linkHandlers = append(i.linkHandlers, handler) +} + func (i *LinkInfoApi) Retrieve(link string) (*LinkInfo, error) { u, err := url.Parse(link) diff --git a/options.go b/options.go new file mode 100644 index 0000000..e939270 --- /dev/null +++ b/options.go @@ -0,0 +1,4 @@ +package linkinfo + +type Option interface { +}