Write on the margins of the internet. Powered by the AT Protocol.
0
fork

Configure Feed

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

better og image for collections and highlights and some minor order swaps done for annotations/bookmarks

+207 -16
+207 -16
backend/internal/api/og.go
··· 719 719 } 720 720 } 721 721 722 - text = "Highlight" 722 + targetTitle := "" 723 + if highlight.TargetTitle != nil { 724 + targetTitle = *highlight.TargetTitle 725 + } 726 + 723 727 if highlight.SelectorJSON != nil && *highlight.SelectorJSON != "" { 724 728 var selector struct { 725 729 Exact string `json:"exact"` ··· 734 738 sourceDomain = parsed.Host 735 739 } 736 740 } 741 + 742 + img := generateHighlightOGImagePNG(authorHandle, targetTitle, quote, sourceDomain, avatarURL) 743 + 744 + w.Header().Set("Content-Type", "image/png") 745 + w.Header().Set("Cache-Control", "public, max-age=86400") 746 + png.Encode(w, img) 747 + return 737 748 } else { 738 749 collection, err := h.db.GetCollectionByURI(uri) 739 750 if err == nil && collection != nil { ··· 752 763 if collection.Icon != nil && *collection.Icon != "" { 753 764 icon = iconToEmoji(*collection.Icon) 754 765 } 755 - text = fmt.Sprintf("%s %s", icon, collection.Name) 766 + 767 + description := "" 756 768 if collection.Description != nil && *collection.Description != "" { 757 - quote = *collection.Description 769 + description = *collection.Description 758 770 } 771 + 772 + img := generateCollectionOGImagePNG(authorHandle, collection.Name, description, icon, avatarURL) 773 + 774 + w.Header().Set("Content-Type", "image/png") 775 + w.Header().Set("Cache-Control", "public, max-age=86400") 776 + png.Encode(w, img) 777 + return 759 778 } else { 760 779 http.Error(w, "Record not found", http.StatusNotFound) 761 780 return ··· 815 834 816 835 contentWidth := width - (padding * 2) 817 836 837 + if text != "" { 838 + if len(text) > 300 { 839 + text = text[:297] + "..." 840 + } 841 + lines := wrapTextToWidth(text, contentWidth, 32) 842 + for i, line := range lines { 843 + if i >= 6 { 844 + break 845 + } 846 + drawText(img, line, padding, yPos+(i*42), textPrimary, 32, false) 847 + } 848 + yPos += (len(lines) * 42) + 40 849 + } 850 + 818 851 if quote != "" { 819 852 if len(quote) > 100 { 820 853 quote = quote[:97] + "..." ··· 833 866 drawText(img, "\""+line+"\"", padding+24, yPos+28+(i*32), textTertiary, 24, true) 834 867 } 835 868 yPos += 30 + (numLines * 32) + 30 836 - } 837 - 838 - if text != "" { 839 - if len(text) > 300 { 840 - text = text[:297] + "..." 841 - } 842 - lines := wrapTextToWidth(text, contentWidth, 32) 843 - for i, line := range lines { 844 - if i >= 6 { 845 - break 846 - } 847 - drawText(img, line, padding, yPos+(i*42), textPrimary, 32, false) 848 - } 849 869 } 850 870 851 871 drawText(img, source, padding, 580, textTertiary, 20, false) ··· 1004 1024 } 1005 1025 return lines 1006 1026 } 1027 + 1028 + func generateCollectionOGImagePNG(author, collectionName, description, icon, avatarURL string) image.Image { 1029 + width := 1200 1030 + height := 630 1031 + padding := 120 1032 + 1033 + bgPrimary := color.RGBA{12, 10, 20, 255} 1034 + accent := color.RGBA{168, 85, 247, 255} 1035 + textPrimary := color.RGBA{244, 240, 255, 255} 1036 + textSecondary := color.RGBA{168, 158, 200, 255} 1037 + textTertiary := color.RGBA{107, 95, 138, 255} 1038 + border := color.RGBA{45, 38, 64, 255} 1039 + 1040 + img := image.NewRGBA(image.Rect(0, 0, width, height)) 1041 + 1042 + draw.Draw(img, img.Bounds(), &image.Uniform{bgPrimary}, image.Point{}, draw.Src) 1043 + draw.Draw(img, image.Rect(0, 0, width, 12), &image.Uniform{accent}, image.Point{}, draw.Src) 1044 + 1045 + iconY := 120 1046 + var iconWidth int 1047 + if icon != "" { 1048 + emojiImg := fetchTwemojiImage(icon) 1049 + if emojiImg != nil { 1050 + iconSize := 96 1051 + drawScaledImage(img, emojiImg, padding, iconY, iconSize, iconSize) 1052 + iconWidth = iconSize + 32 1053 + } else { 1054 + drawText(img, icon, padding, iconY+70, textPrimary, 80, true) 1055 + iconWidth = 100 1056 + } 1057 + } 1058 + 1059 + drawText(img, collectionName, padding+iconWidth, iconY+65, textPrimary, 64, true) 1060 + 1061 + yPos := 280 1062 + contentWidth := width - (padding * 2) 1063 + 1064 + if description != "" { 1065 + if len(description) > 200 { 1066 + description = description[:197] + "..." 1067 + } 1068 + lines := wrapTextToWidth(description, contentWidth, 32) 1069 + for i, line := range lines { 1070 + if i >= 4 { 1071 + break 1072 + } 1073 + drawText(img, line, padding, yPos+(i*42), textSecondary, 32, false) 1074 + } 1075 + } else { 1076 + drawText(img, "A collection on Margin", padding, yPos, textTertiary, 32, false) 1077 + } 1078 + 1079 + yPos = 480 1080 + draw.Draw(img, image.Rect(padding, yPos, width-padding, yPos+1), &image.Uniform{border}, image.Point{}, draw.Src) 1081 + 1082 + avatarSize := 64 1083 + avatarX := padding 1084 + avatarY := yPos + 40 1085 + 1086 + avatarImg := fetchAvatarImage(avatarURL) 1087 + if avatarImg != nil { 1088 + drawCircularAvatar(img, avatarImg, avatarX, avatarY, avatarSize) 1089 + } else { 1090 + drawDefaultAvatar(img, author, avatarX, avatarY, avatarSize, accent) 1091 + } 1092 + 1093 + handleX := avatarX + avatarSize + 24 1094 + drawText(img, author, handleX, avatarY+42, textTertiary, 28, false) 1095 + 1096 + return img 1097 + } 1098 + 1099 + func fetchTwemojiImage(emoji string) image.Image { 1100 + var codes []string 1101 + for _, r := range emoji { 1102 + codes = append(codes, fmt.Sprintf("%x", r)) 1103 + } 1104 + hexCode := strings.Join(codes, "-") 1105 + 1106 + url := fmt.Sprintf("https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/%s.png", hexCode) 1107 + 1108 + resp, err := http.Get(url) 1109 + if err != nil || resp.StatusCode != 200 { 1110 + if strings.Contains(hexCode, "-fe0f") { 1111 + simpleHex := strings.ReplaceAll(hexCode, "-fe0f", "") 1112 + url = fmt.Sprintf("https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/%s.png", simpleHex) 1113 + resp, err = http.Get(url) 1114 + if err != nil || resp.StatusCode != 200 { 1115 + return nil 1116 + } 1117 + } else { 1118 + return nil 1119 + } 1120 + } 1121 + defer resp.Body.Close() 1122 + 1123 + img, _, err := image.Decode(resp.Body) 1124 + if err != nil { 1125 + return nil 1126 + } 1127 + return img 1128 + } 1129 + 1130 + func generateHighlightOGImagePNG(author, pageTitle, quote, source, avatarURL string) image.Image { 1131 + width := 1200 1132 + height := 630 1133 + padding := 100 1134 + 1135 + bgPrimary := color.RGBA{12, 10, 20, 255} 1136 + accent := color.RGBA{250, 204, 21, 255} 1137 + textPrimary := color.RGBA{244, 240, 255, 255} 1138 + textSecondary := color.RGBA{168, 158, 200, 255} 1139 + border := color.RGBA{45, 38, 64, 255} 1140 + 1141 + img := image.NewRGBA(image.Rect(0, 0, width, height)) 1142 + 1143 + draw.Draw(img, img.Bounds(), &image.Uniform{bgPrimary}, image.Point{}, draw.Src) 1144 + draw.Draw(img, image.Rect(0, 0, width, 12), &image.Uniform{accent}, image.Point{}, draw.Src) 1145 + 1146 + avatarSize := 64 1147 + avatarX := padding 1148 + avatarY := padding 1149 + 1150 + avatarImg := fetchAvatarImage(avatarURL) 1151 + if avatarImg != nil { 1152 + drawCircularAvatar(img, avatarImg, avatarX, avatarY, avatarSize) 1153 + } else { 1154 + drawDefaultAvatar(img, author, avatarX, avatarY, avatarSize, accent) 1155 + } 1156 + drawText(img, author, avatarX+avatarSize+24, avatarY+42, textSecondary, 28, false) 1157 + 1158 + contentWidth := width - (padding * 2) 1159 + yPos := 240 1160 + 1161 + if quote != "" { 1162 + if len(quote) > 200 { 1163 + quote = quote[:197] + "..." 1164 + } 1165 + 1166 + barHeight := 0 1167 + 1168 + lines := wrapTextToWidth(quote, contentWidth-40, 42) 1169 + barHeight = len(lines) * 56 1170 + 1171 + draw.Draw(img, image.Rect(padding, yPos, padding+8, yPos+barHeight), &image.Uniform{accent}, image.Point{}, draw.Src) 1172 + 1173 + for i, line := range lines { 1174 + if i >= 5 { 1175 + break 1176 + } 1177 + drawText(img, line, padding+40, yPos+42+(i*56), textPrimary, 42, false) 1178 + } 1179 + yPos += barHeight + 60 1180 + } 1181 + 1182 + draw.Draw(img, image.Rect(padding, yPos, width-padding, yPos+1), &image.Uniform{border}, image.Point{}, draw.Src) 1183 + yPos += 40 1184 + 1185 + if pageTitle != "" { 1186 + if len(pageTitle) > 60 { 1187 + pageTitle = pageTitle[:57] + "..." 1188 + } 1189 + drawText(img, pageTitle, padding, yPos+32, textSecondary, 32, true) 1190 + } 1191 + 1192 + if source != "" { 1193 + drawText(img, source, padding, yPos+80, textSecondary, 24, false) 1194 + } 1195 + 1196 + return img 1197 + }