commit a6b7271

delthas  ·  2024-08-07 21:55:29 +0000 UTC
parent 5ec99d5
Try opening links as previews based on their content type

Instead of their URL path file extension.

Additionally, add a specific case for Twitter/X, which has broken
embeds at the moment. I can finally have my Twitter image embeds
in my terminal :-)
1 files changed,  +60, -20
M app.go
M app.go
+60, -20
  1@@ -4,6 +4,7 @@ import (
  2 	"crypto/tls"
  3 	"errors"
  4 	"fmt"
  5+	"html"
  6 	"image"
  7 	_ "image/gif"
  8 	_ "image/jpeg"
  9@@ -15,8 +16,8 @@ import (
 10 	"net/url"
 11 	"os"
 12 	"os/exec"
 13-	"path"
 14 	"path/filepath"
 15+	"regexp"
 16 	"strings"
 17 	"sync"
 18 	"time"
 19@@ -901,7 +902,22 @@ func (app *App) handleNickEvent(ev *events.EventClickNick) {
 20 	}
 21 }
 22 
 23+var patternOpenGraphImage = regexp.MustCompile(`<meta property="og:image" content="(.*?)"/>`)
 24+
 25 func (app *App) fetchImage(link string) (image.Image, error) {
 26+	if u, err := url.Parse(link); err == nil {
 27+		changed := true
 28+		switch u.Host {
 29+		case "twitter.com", "x.com":
 30+			u.Host = "fixupx.com"
 31+		default:
 32+			changed = false
 33+		}
 34+		if changed {
 35+			link = u.String()
 36+		}
 37+	}
 38+
 39 	c := http.Client{
 40 		Timeout: 6 * time.Second,
 41 	}
 42@@ -912,9 +928,15 @@ func (app *App) fetchImage(link string) (image.Image, error) {
 43 	if res.StatusCode != http.StatusOK {
 44 		return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
 45 	}
 46-	contentType := res.Header.Get("Content-Type")
 47+	contentType, _, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
 48+	if err != nil {
 49+		return nil, fmt.Errorf("unexpected content type: %v", res.Header.Get("Content-Type"))
 50+	}
 51+	var isHTML bool
 52 	switch contentType {
 53-	case "image/gif", "image/jpeg", "image/png":
 54+	case "image/gif", "image/jpeg", "image/png": // Actual image, fetch
 55+	case "text/html": // Might have an opengraph image, try fetching
 56+		isHTML = true
 57 	default:
 58 		return nil, fmt.Errorf("unexpected content type: %v", contentType)
 59 	}
 60@@ -922,6 +944,21 @@ func (app *App) fetchImage(link string) (image.Image, error) {
 61 	if err != nil {
 62 		return nil, err
 63 	}
 64+	if isHTML {
 65+		b, err := io.ReadAll(io.LimitReader(res.Body, 10*1024))
 66+		if err != nil {
 67+			return nil, fmt.Errorf("unexpected read error: %v", err)
 68+		}
 69+		m := patternOpenGraphImage.FindSubmatch(b)
 70+		if len(m) < 2 {
 71+			return nil, fmt.Errorf("image embed not found")
 72+		}
 73+		imageLink := html.UnescapeString(string(m[1]))
 74+		res, err = c.Get(imageLink)
 75+		if err != nil {
 76+			return nil, err
 77+		}
 78+	}
 79 	img, _, err := ui.DecodeImage(res.Body)
 80 	res.Body.Close()
 81 	if err != nil {
 82@@ -931,31 +968,31 @@ func (app *App) fetchImage(link string) (image.Image, error) {
 83 }
 84 
 85 func (app *App) handleLinkEvent(ev *events.EventClickLink) {
 86-	var open bool
 87-	if ev.Mouse {
 88-		open = true
 89-		if ev.Event.Modifiers != vaxis.ModCtrl {
 90-			if u, err := url.Parse(ev.Link); err == nil {
 91-				switch strings.ToLower(path.Ext(u.Path)) {
 92-				case ".jpg", ".jpeg", ".png", ".gif":
 93-					open = false
 94-				}
 95-			}
 96-		}
 97-	}
 98-	if open {
 99+	open := func() {
100 		if strings.HasPrefix(ev.Link, "-") {
101 			// Avoid injection of parameters.
102 			// Sadly xdg-open does not support "--"...
103 			return
104 		}
105-		go func() {
106-			cmd := exec.Command("xdg-open", ev.Link)
107-			cmd.Run()
108-		}()
109+		cmd := exec.Command("xdg-open", ev.Link)
110+		cmd.Run()
111+	}
112+
113+	if ev.Event.Modifiers == vaxis.ModCtrl {
114+		if ev.Mouse {
115+			// Only react to Ctrl+Click when mouse links are enabled.
116+
117+			// Explicit external link open requested with Ctrl+Click:
118+			// just run xdg-open.
119+			go open()
120+		}
121 		return
122 	}
123 
124+	// Regular link open requested:
125+	// Try fetching as an image and displaying a preview;
126+	// fall back to xdg-open if mouse links are enabled.
127+
128 	app.imageLoading = true
129 	go func() {
130 		img, err := app.fetchImage(ev.Link)
131@@ -966,6 +1003,9 @@ func (app *App) handleLinkEvent(ev *events.EventClickLink) {
132 					Image: nil,
133 				},
134 			}
135+			if ev.Mouse {
136+				open()
137+			}
138 		} else {
139 			app.events <- event{
140 				src: "*",