// NewWithNamespace returns a new UUIDv5 (or v4 if name is empty), encoded with base57. func NewWithNamespace(name string) string { var u uuid.UUID switch { case name == "": u = uuid.NewV4() case strings.HasPrefix(name, "http"): u = uuid.NewV5(uuid.NamespaceURL, name) default: u = uuid.NewV5(uuid.NamespaceDNS, name) } return DefaultEncoder.Encode(u) }
// UUID returns a new (short) UUID. If name is non-empty, the namespace // matching the name will be used to generate a UUID. func (su *ShortUUID) UUID(name string) string { var u uuid.UUID switch { case name == "": u = uuid.NewV4() case strings.HasPrefix(name, "http"): u = uuid.NewV5(uuid.NamespaceDNS, name) default: u = uuid.NewV5(uuid.NamespaceURL, name) } return su.Encode(u) }
// /a/comment func (s *Server) CommentHandler(c *gin.Context) { c.Request.ParseForm() id := c.Request.Form.Get("id") entryId := c.Request.Form.Get("entry") rawBody := c.Request.Form.Get("body") if entryId == "" || rawBody == "" { c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"}) return } body := util.DefaultSanitize(rawBody) body = util.EntityToLink(body) profile, _ := s.CurrentUser(c) from := &pb.Feed{ Id: profile.Id, Name: profile.Name, Type: profile.Type, } comment := &pb.Comment{ Body: body, RawBody: rawBody, From: from, } var err error var uuid1 uuid.UUID if id != "" { uuid1, err = uuid.FromString(id) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"}) return } } else { comment.Date = time.Now().UTC().Format(time.RFC3339) name := entryId + profile.Uuid + comment.Date uuid1 = uuid.NewV5(uuid.NamespaceURL, name) } comment.Id = uuid1.String() req := &pb.CommentRequest{ Entry: entryId, Comment: comment, } ctx, cancel := DefaultTimeoutContext() defer cancel() _, err = s.client.CommentEntry(ctx, req) if RequestError(c, err) { return } comment.Commands = []string{"edit", "delete"} c.JSON(200, comment) }
// TODO: allow cross post to multiply feeds func (s *Server) EntryPostHandler(c *gin.Context) { var form struct { FeedId string `form:"feedid" binding:"required"` Body string `form:"body" binding:"required"` } c.BindWith(&form, binding.MultipartForm) if !s.feedWritable(c, form.FeedId) { c.AbortWithStatus(401) return } body := util.DefaultSanitize(form.Body) body = util.EntityToLink(body) ctx, cancel := DefaultTimeoutContext() defer cancel() profile, _ := s.CurrentUser(c) dt := time.Now().UTC() name := profile.Uuid + "/" + dt.Format(time.RFC3339) uuid1 := uuid.NewV5(uuid.NamespaceURL, name) from := &pb.Feed{ Id: profile.Id, Name: profile.Name, Type: profile.Type, } entry := &pb.Entry{ Id: uuid1.String(), Date: dt.Format(time.RFC3339), Body: body, RawBody: form.Body, From: from, // To: []*pb.Feed{from}, // Thumbnails: thumbnails, ProfileUuid: profile.Uuid, } entry, err := s.client.PostEntry(ctx, entry) if RequestError(c, err) { return } // c.JSON(200, gin.H{"entry": entry}) c.Redirect(http.StatusFound, "/") }
func (s *ApiServer) FixComment() error { req := &pb.FeedRequest{ Id: "public", Start: int32(0), PageSize: 50, } feed, _ := s.cachedFeed(req) for _, e := range feed.Entries { for _, cmt := range e.Comments { // date, _ := time.Parse(time.RFC3339, cmt.Date) profile, _ := store.GetProfile(s.mdb, cmt.From.Id) fixedName := e.Id + profile.Uuid + cmt.Date uuid1 := uuid.NewV5(uuid.NamespaceURL, fixedName) cmt.Id = uuid1.String() } store.PutEntry(s.rdb, e, true) } return nil }
func (fa *FeedAgent) fetchService(job *pb.FeedJob) (int, error) { stream, err := fa.client.ArchiveFeed(context.Background()) defer stream.CloseAndRecv() if err != nil { return 0, err } updated := time.Unix(job.Service.Updated, 0) authinfo := job.Service.Oauth if authinfo == nil { return 0, fmt.Errorf("skip job: no authinfo") } api := anaconda.NewTwitterApi(authinfo.AccessToken, authinfo.AccessTokenSecret) defer api.Close() v := url.Values{} v.Set("screen_name", authinfo.NickName) // goth user.NickName == screen_name tweets, _ := api.GetUserTimeline(v) n := 0 for i := len(tweets) - 1; i >= 0; i-- { tweet := tweets[i] // skip reply status if tweet.InReplyToStatusID != 0 { continue } url := "https://twitter.com/" + tweet.User.ScreenName + "/status/" + tweet.IdStr // deterministic uuid or feed will be polluted uuid1 := uuid.NewV5(uuid.NamespaceURL, url) tt, err := tweet.CreatedAtTime() if err != nil || tt.Before(updated) { continue } from := &pb.Feed{ Id: job.Profile.Id, Name: job.Profile.Name, Type: job.Profile.Type, } var thumbnails []*pb.Thumbnail for _, media := range tweet.Entities.Media { if media.Type != "photo" { continue } url := "" if media.Media_url_https != "" { url = media.Media_url_https } else { url = media.Media_url } thumb := &pb.Thumbnail{ Url: url, Link: media.Expanded_url, Width: int32(media.Sizes.Small.W), Height: int32(media.Sizes.Small.H), } thumbnails = append(thumbnails, thumb) } body := tweet.Text tags := ttext.ExtractHashtags(body) for _, tag := range tags { new := fmt.Sprintf("<a href=\"https://twitter.com/hashtag/%s\">%s</a>", tag, tag) body = strings.Replace(body, tag, new, -1) } urls := ttext.ExtractURLs(tweet.Text) for _, url := range urls { new := fmt.Sprintf("<a href=\"%s\">%s</a>", url, url) body = strings.Replace(body, url, new, -1) } entry := &pb.Entry{ Id: uuid1.String(), Url: url, Date: tt.Format(time.RFC3339), Body: body, RawBody: tweet.Text, RawLink: url, From: from, // To: []*pb.Feed{from}, Thumbnails: thumbnails, Via: &pb.Via{ Name: "Twitter", Url: url, }, ProfileUuid: job.Profile.Uuid, } if err := stream.Send(entry); err != nil { log.Printf("%v.Send(%v) = %v", stream, entry, err) return n, err } n++ } return n, nil }
// Return a new unique id :) func getUUID() string { // @todo is this good enough? var secret = uuid.NewV4() u := uuid.NewV5(secret, domain) return u.String() }