this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: Imgur gallary preview cards available

+77 -18
+43 -14
internal/service/content.go
··· 39 39 DateMonth string `json:"date_month"` // e.g. "Jan" 40 40 DateRawDay string `json:"date_raw_day"` // e.g. "01" 41 41 DateYear string `json:"date_year"` // e.g. "2026" 42 + SuppressOG bool `json:"suppress_og"` 42 43 } 43 44 44 45 func NewContentService(cfg *config.Config) *ContentService { ··· 87 88 embed := fmt.Sprintf(`<blockquote class="twitter-tweet"><a href="%s" target="_blank">%s</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`, embedURL, item.Title) 88 89 d.Content = template.HTML(embed) 89 90 isTwitter = true 91 + d.SuppressOG = true 90 92 } 91 93 } 92 94 93 95 // Imgur 94 96 isImgur := false 95 97 if strings.Contains(item.URL, "imgur.com") { 96 - // Regex for ID extraction 97 - re := regexp.MustCompile(`imgur\.com\/(?:.*[\/-])?([a-zA-Z0-9]{5,})(?:\..*)?$`) 98 - matches := re.FindStringSubmatch(item.URL) 99 - if len(matches) > 1 { 100 - id := matches[1] 101 - videoURL := fmt.Sprintf("https://i.imgur.com/%s.mp4", id) 102 - imgURL := fmt.Sprintf("https://i.imgur.com/%s.jpg", id) 103 - 104 - // We render a video tag by default. If it fails to load (404 for static images, or other errors), 105 - // the onerror handler swaps it for a standard image tag. 106 - // This avoids server-side rate limits (HTTP 429) and speeds up response time. 107 - // Note: We wrap it in the anchor tag in the Go code, but the onerror replaces the VIDEO tag specifically. 98 + // Gallery Check 99 + if strings.Contains(item.URL, "/gallery/") || strings.Contains(item.URL, "/a/") { 100 + // It is a gallery 101 + // Create a nice looking card for the gallery 102 + // <i class="fa-regular fa-images"></i> is a font-awesome icon if available, but let's stick to text/SVG or existing styles. 103 + // converting to simple link with a visual cue 108 104 embed := fmt.Sprintf( 109 - `<a href="%s" target="_blank"><video autoplay loop muted playsinline style="max-width: 500px;" src="%s" onerror="this.onerror=null;this.outerHTML='<img src=\'%s\' style=\'max-width: 500px;\' />'"></video></a>`, 110 - item.URL, videoURL, imgURL) 105 + `<span class="imgur-gallery-card" style="display: inline-block; overflow: hidden; border: 1px solid #444; border-radius: 5px; background-color: #222; max-width: 400px; width: 100%%; vertical-align: top;"> 106 + <a href="%s" target="_blank" style="color: #fff; text-decoration: none; display: block;"> 107 + <span class="gallery-image-container" style="display: block; background-color: #000; text-align: center; min-height: 200px; line-height: 200px;"> 108 + <span style="font-size: 48px;">📸</span> 109 + </span> 110 + <span style="display: block; padding: 10px;"> 111 + <span style="font-weight: bold; display: block;">Imgur Gallery</span> 112 + <span style="font-size: 0.9em; opacity: 0.8; display: block;">%s</span> 113 + </span> 114 + </a> 115 + </span>`, item.URL, item.Title) 111 116 112 117 d.Content = template.HTML(embed) 113 118 isImgur = true 119 + d.SuppressOG = true 120 + 121 + } else { 122 + // Single Image / Video Detection 123 + re := regexp.MustCompile(`imgur\.com\/(?:.*[\/-])?([a-zA-Z0-9]{5,})(?:\..*)?$`) 124 + matches := re.FindStringSubmatch(item.URL) 125 + if len(matches) > 1 { 126 + id := matches[1] 127 + videoURL := fmt.Sprintf("https://i.imgur.com/%s.mp4", id) 128 + imgURL := fmt.Sprintf("https://i.imgur.com/%s.jpg", id) 129 + 130 + // We render a video tag by default. If it fails to load (404 for static images, or other errors), 131 + // the onerror handler swaps it for a standard image tag. 132 + // This avoids server-side rate limits (HTTP 429) and speeds up response time. 133 + // Note: We wrap it in the anchor tag in the Go code, but the onerror replaces the VIDEO tag specifically. 134 + embed := fmt.Sprintf( 135 + `<a href="%s" target="_blank"><video autoplay loop muted playsinline style="max-width: 500px;" src="%s" onerror="this.onerror=null;this.outerHTML='<img src=\'%s\' style=\'max-width: 500px;\' />'"></video></a>`, 136 + item.URL, videoURL, imgURL) 137 + 138 + d.Content = template.HTML(embed) 139 + isImgur = true 140 + d.SuppressOG = true 141 + } 114 142 } 115 143 } 116 144 ··· 129 157 embed := fmt.Sprintf(`<a href="%s" target="_blank"><img src="%s" alt="%s" /></a>`, photoPage, item.URL, item.Title) 130 158 d.Content = template.HTML(embed) 131 159 isFlickr = true 160 + d.SuppressOG = true 132 161 } 133 162 } 134 163
+33 -3
internal/templates/views/index.html
··· 45 45 var contentType = item.getAttribute("data-content-type"); 46 46 var ircId = item.getAttribute("data-irc-link-id"); 47 47 var previewDiv = item.querySelector(".og-preview"); 48 + var imgurCard = item.querySelector(".imgur-gallery-card"); 48 49 49 50 // Only load preview for non-image content (text/html) 50 - if (!url || (contentType && contentType.match(/image/))) { 51 + // Exception: Imgur Gallery Cards need to fetch the cover image even if "image" is in content type 52 + if (!url || (contentType && contentType.match(/image/) && !imgurCard)) { 53 + return; 54 + } 55 + 56 + // If no preview div AND no imgur card, nothing to do 57 + if (!previewDiv && !imgurCard) { 51 58 return; 52 59 } 53 60 ··· 84 91 if (data.error || (data.status && data.status >= 400)) { 85 92 var status = data.status || 404; 86 93 var linkSpan = item.querySelector(".link"); 87 - if (linkSpan) { 94 + if (linkSpan && !imgurCard) { 88 95 var statusPrefix = '<span class="http-error-badge">' + status + '</span> '; 89 96 linkSpan.innerHTML = '<a href="' + escapeHtml(url) + '" class="missing-link" target="_blank">' + statusPrefix + escapeHtml(url) + '</a>'; 90 97 } 91 - previewDiv.remove(); 98 + if (previewDiv) previewDiv.remove(); 92 99 return; 93 100 } 94 101 ··· 124 131 var imageUrl = data.image || data.twitter_image || null; 125 132 var title = data.title || data.twitter_title || null; 126 133 var description = data.description || data.twitter_description || null; 134 + 135 + // SPECIAL HANDLING: Imgur Gallery Card 136 + if (imgurCard) { 137 + if (imageUrl) { 138 + // Find the placeholder container 139 + // .gallery-image-container 140 + var imgContainer = imgurCard.querySelector(".gallery-image-container"); 141 + if (imgContainer) { 142 + // Replace specific placeholder or just set innerHTML 143 + // We want to replace the whole container contents (camera icon) with the image 144 + // and ensure styles are good. 145 + var imgHtml = '<img src="' + escapeHtml(imageUrl) + '" style="width: 100%; height: auto; display: block; object-fit: cover;" />'; 146 + imgContainer.innerHTML = imgHtml; 147 + // Remove min-height/centering styles from container if needed, or keeping them is fine if image covers it. 148 + imgContainer.style.minHeight = "auto"; 149 + imgContainer.style.lineHeight = "normal"; 150 + imgContainer.style.backgroundColor = "transparent"; 151 + } 152 + } 153 + return; 154 + } 155 + 156 + if (!previewDiv) return; 127 157 128 158 // Only show preview if we have at least an image, title, or description 129 159 if (!imageUrl && !title && !description && !data.embed_html) {
+1 -1
internal/templates/views/tumble_item_ircLink.html
··· 1 1 <div class="item" data-url="{{.URL}}" data-content-type="{{.ContentType}}" data-irc-link-id="{{.ID}}"> 2 2 <span class="link">{{.Content}}</span> 3 3 <span class="author"><a href="/?poster={{.User}}" data-tooltip="{{.FormattedDate}}">{{.User}}</a></span> 4 - <div class="og-preview" id="og-preview-{{.ID}}"></div> 4 + {{ if not .SuppressOG }}<div class="og-preview" id="og-preview-{{.ID}}"></div>{{ end }} 5 5 </div>