feat(share): add Open Graph support for social media previews#3227
feat(share): add Open Graph support for social media previews#3227WittF wants to merge 6 commits intocloudreve:masterfrom
Conversation
|
For a single shared file, is it a easy job to get thumbnail as the hero image? |
This need adding a new public thumbnail endpoint. |
5c41e47 to
3c66839
Compare
|
As enhancement, maybe later we can add multi-language support (like email template) & also support magic variables? |
Good idea! |
Localization for open graph is hard to implement, I don't think they have a standard for that. For now, we'd better use a unified template, and try not to include language specific phrase, If you have to, use English for now. |
- Detect social media bots (Facebook, Twitter, Discord, etc.) - Render OG meta tags with file name, size, and owner info - Auto-redirect to share page after preview is rendered
- Handle invalid/expired/password-protected shares gracefully - Show appropriate messages for login-required shares - Simplify bot detection to social media crawlers only - Extract isShareUnlocked() for code reuse
- Extend ShareOGData with additional fields for richer previews - Add magic variable replacement for File/Folder/Status scenarios - Refactor status handling with switch/case pattern - Separate rendering paths for files, folders, and status messages
- Extract LoadShareForInfo() and BuildRedirectURL() to pkg/share - Move OG template and preview logic to pkg/sharepreview - Extract IsSocialMediaBot() to pkg/util - Simplify service/share/visit.go to use new packages
Intercept /home route for social media bots viewing cloudreve://share paths and render OG preview directly.
Status constants ( const (
ogStatusInvalidLink = "Invalid Link"
ogStatusShareExpired = "Share Expired"
ogStatusPasswordRequired = "Password Required"
ogDefaultFileName = "Shared File"
ogDefaultFolderName = "Shared Folder"
) |
Add facebookcatalog, meta-externalagent, pinterestbot and organize bot list with comments by platform.
|
This feels "vibe coded"—the implementation is becoming messy and far too complicated. We need to stop iterating on this branch. I suggest STOP letting an AI handle another new iteration, or I can take it over myself. If you prefer to continue, please open a fresh PR. |
| return shareURI | ||
| } | ||
|
|
||
| func sanitizeInvalidPercentEscapes(raw string) string { |
There was a problem hiding this comment.
Can you explain why it this needed?
| shareURI, err := filemanagerfs.NewUriFromString(raw) | ||
| if err != nil { | ||
| sanitized := sanitizeInvalidPercentEscapes(raw) | ||
| if sanitized != raw { | ||
| shareURI, err = filemanagerfs.NewUriFromString(sanitized) | ||
| } | ||
| } | ||
| if err != nil && strings.Contains(raw, "%") { | ||
| unescaped, err := url.QueryUnescape(raw) | ||
| if err == nil { | ||
| shareURI, err = filemanagerfs.NewUriFromString(unescaped) | ||
| if err != nil { | ||
| sanitized := sanitizeInvalidPercentEscapes(unescaped) | ||
| if sanitized != unescaped { | ||
| shareURI, err = filemanagerfs.NewUriFromString(sanitized) |
There was a problem hiding this comment.
I don't get what you're doing here.
| c.Header("Content-Type", "text/html; charset=utf-8") | ||
| c.String(http.StatusOK, html) |
There was a problem hiding this comment.
c.HTML does the exact same thing in one line.
| // LoadShareForInfo loads share info for public preview/metadata access. | ||
| func LoadShareForInfo(ctx context.Context, shareClient inventory.ShareClient, shareID int, viewer *ent.User, password string) (*ent.Share, bool, LoadStatus, error) { | ||
| ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true) | ||
| ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true) | ||
| share, err := shareClient.GetByID(ctx, shareID) | ||
| if err != nil { | ||
| if ent.IsNotFound(err) { | ||
| return nil, false, LoadNotFound, nil | ||
| } | ||
| return nil, false, LoadError, err | ||
| } | ||
|
|
||
| if err := inventory.IsValidShare(share); err != nil { | ||
| return share, false, LoadExpired, err | ||
| } | ||
|
|
||
| unlocked := isShareUnlocked(share, password, viewer) | ||
| return share, unlocked, LoadOK, nil | ||
| } |
There was a problem hiding this comment.
You're still duplicating existing codes. Please reuse https://github.com/cloudreve/cloudreve/blob/47218607ff91727c404710d8f0c7895c16b201bc/service/share/visit.go#L60C31-L60C32
|
@HFO4 Sorry! I admit my fault.🥲 |
Move