Timeline manager (#40)

* start messing about with timeline manager

* i have no idea what i'm doing

* i continue to not know what i'm doing

* it's coming along

* bit more progress

* update timeline with new posts as they come in

* lint and fmt

* Select accounts where empty string

* restructure a bunch, get unfaves working

* moving stuff around

* federate status deletes properly

* mention regex better but not 100% there

* fix regex

* some more hacking away at the timeline code phew

* fix up some little things

* i can't even

* more timeline stuff

* move to ulid

* fiddley

* some lil fixes for kibou compatibility

* timelines working pretty alright!

* tidy + lint
This commit is contained in:
Tobi Smethurst
2021-06-13 18:42:28 +02:00
committed by GitHub
parent 6ac6f8d614
commit b4288f3c47
96 changed files with 3458 additions and 1679 deletions

View File

@@ -41,11 +41,11 @@ var (
mentionNameRegex = regexp.MustCompile(mentionNameRegexString)
// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1
mentionFinderRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)`
mentionFinderRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?:[^a-zA-Z0-9]|\W|$)?`
mentionFinderRegex = regexp.MustCompile(mentionFinderRegexString)
// hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1
hashtagFinderRegexString = fmt.Sprintf(`(?: |^|\W)?#([a-zA-Z0-9]{1,%d})(?:\b|\r)`, maximumHashtagLength)
hashtagFinderRegexString = fmt.Sprintf(`(?:\b)?#(\w{1,%d})(?:\b)`, maximumHashtagLength)
hashtagFinderRegex = regexp.MustCompile(hashtagFinderRegexString)
// emoji shortcode regex can be played with here: https://regex101.com/r/zMDRaG/1
@@ -85,21 +85,25 @@ var (
// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following
followingPathRegex = regexp.MustCompile(followingPathRegexString)
// see https://ihateregex.io/expr/uuid/
uuidRegexString = `[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}`
followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString)
// followPathRegex parses a path that validates and captures the username part and the ulid part
// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH
followPathRegex = regexp.MustCompile(followPathRegexString)
ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}`
likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath)
// likedPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked
likedPathRegex = regexp.MustCompile(likedPathRegexString)
likePathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, LikedPath, uuidRegexString)
// likePathRegex parses a path that validates and captures the username part and the uuid part
// from eg /users/example_username/liked/123e4567-e89b-12d3-a456-426655440000.
likePathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, LikedPath, ulidRegexString)
// likePathRegex parses a path that validates and captures the username part and the ulid part
// from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH
likePathRegex = regexp.MustCompile(likePathRegexString)
statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, uuidRegexString)
// statusesPathRegex parses a path that validates and captures the username part and the uuid part
// from eg /users/example_username/statuses/123e4567-e89b-12d3-a456-426655440000.
statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, ulidRegexString)
// statusesPathRegex parses a path that validates and captures the username part and the ulid part
// from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH
// The regex can be played with here: https://regex101.com/r/G9zuxQ/1
statusesPathRegex = regexp.MustCompile(statusesPathRegexString)
)

View File

@@ -35,7 +35,7 @@ func DeriveMentionsFromStatus(status string) []string {
for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) {
mentionedAccounts = append(mentionedAccounts, m[1])
}
return lower(unique(mentionedAccounts))
return unique(mentionedAccounts)
}
// DeriveHashtagsFromStatus takes a plaintext (ie., not html-formatted) status,
@@ -47,7 +47,7 @@ func DeriveHashtagsFromStatus(status string) []string {
for _, m := range hashtagFinderRegex.FindAllStringSubmatch(status, -1) {
tags = append(tags, m[1])
}
return lower(unique(tags))
return unique(tags)
}
// DeriveEmojisFromStatus takes a plaintext (ie., not html-formatted) status,
@@ -59,7 +59,7 @@ func DeriveEmojisFromStatus(status string) []string {
for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) {
emojis = append(emojis, m[1])
}
return lower(unique(emojis))
return unique(emojis)
}
// ExtractMentionParts extracts the username test_user and the domain example.org
@@ -94,24 +94,3 @@ func unique(s []string) []string {
}
return list
}
// lower lowercases all strings in a given string slice
func lower(s []string) []string {
new := []string{}
for _, i := range s {
new = append(new, strings.ToLower(i))
}
return new
}
// HTMLFormat takes a plaintext formatted status string, and converts it into
// a nice HTML-formatted string.
//
// This includes:
// - Replacing line-breaks with <p>
// - Replacing URLs with hrefs.
// - Replacing mentions with links to that account's URL as stored in the database.
func HTMLFormat(status string) string {
// TODO: write proper HTML formatting logic for a status
return status
}

View File

@@ -21,7 +21,6 @@ package util
import (
"fmt"
"net/url"
"strings"
)
const (
@@ -108,19 +107,19 @@ type UserURIs struct {
}
// GenerateURIForFollow returns the AP URI for a new follow -- something like:
// https://example.org/users/whatever_user/follow/41c7f33f-1060-48d9-84df-38dcb13cf0d8
// https://example.org/users/whatever_user/follow/01F7XTH1QGBAPMGF49WJZ91XGC
func GenerateURIForFollow(username string, protocol string, host string, thisFollowID string) string {
return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, FollowPath, thisFollowID)
}
// GenerateURIForLike returns the AP URI for a new like/fave -- something like:
// https://example.org/users/whatever_user/liked/41c7f33f-1060-48d9-84df-38dcb13cf0d8
// https://example.org/users/whatever_user/liked/01F7XTH1QGBAPMGF49WJZ91XGC
func GenerateURIForLike(username string, protocol string, host string, thisFavedID string) string {
return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, LikedPath, thisFavedID)
}
// GenerateURIForUpdate returns the AP URI for a new update activity -- something like:
// https://example.org/users/whatever_user#updates/41c7f33f-1060-48d9-84df-38dcb13cf0d8
// https://example.org/users/whatever_user#updates/01F7XTH1QGBAPMGF49WJZ91XGC
func GenerateURIForUpdate(username string, protocol string, host string, thisUpdateID string) string {
return fmt.Sprintf("%s://%s/%s/%s#%s/%s", protocol, host, UsersPath, username, UpdatePath, thisUpdateID)
}
@@ -162,58 +161,63 @@ func GenerateURIsForAccount(username string, protocol string, host string) *User
// IsUserPath returns true if the given URL path corresponds to eg /users/example_username
func IsUserPath(id *url.URL) bool {
return userPathRegex.MatchString(strings.ToLower(id.Path))
return userPathRegex.MatchString(id.Path)
}
// IsInboxPath returns true if the given URL path corresponds to eg /users/example_username/inbox
func IsInboxPath(id *url.URL) bool {
return inboxPathRegex.MatchString(strings.ToLower(id.Path))
return inboxPathRegex.MatchString(id.Path)
}
// IsOutboxPath returns true if the given URL path corresponds to eg /users/example_username/outbox
func IsOutboxPath(id *url.URL) bool {
return outboxPathRegex.MatchString(strings.ToLower(id.Path))
return outboxPathRegex.MatchString(id.Path)
}
// IsInstanceActorPath returns true if the given URL path corresponds to eg /actors/example_username
func IsInstanceActorPath(id *url.URL) bool {
return actorPathRegex.MatchString(strings.ToLower(id.Path))
return actorPathRegex.MatchString(id.Path)
}
// IsFollowersPath returns true if the given URL path corresponds to eg /users/example_username/followers
func IsFollowersPath(id *url.URL) bool {
return followersPathRegex.MatchString(strings.ToLower(id.Path))
return followersPathRegex.MatchString(id.Path)
}
// IsFollowingPath returns true if the given URL path corresponds to eg /users/example_username/following
func IsFollowingPath(id *url.URL) bool {
return followingPathRegex.MatchString(strings.ToLower(id.Path))
return followingPathRegex.MatchString(id.Path)
}
// IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW
func IsFollowPath(id *url.URL) bool {
return followPathRegex.MatchString(id.Path)
}
// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked
func IsLikedPath(id *url.URL) bool {
return likedPathRegex.MatchString(strings.ToLower(id.Path))
return likedPathRegex.MatchString(id.Path)
}
// IsLikePath returns true if the given URL path corresponds to eg /users/example_username/liked/SOME_UUID_OF_A_STATUS
// IsLikePath returns true if the given URL path corresponds to eg /users/example_username/liked/SOME_ULID_OF_A_STATUS
func IsLikePath(id *url.URL) bool {
return likePathRegex.MatchString(strings.ToLower(id.Path))
return likePathRegex.MatchString(id.Path)
}
// IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
// IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_ULID_OF_A_STATUS
func IsStatusesPath(id *url.URL) bool {
return statusesPathRegex.MatchString(strings.ToLower(id.Path))
return statusesPathRegex.MatchString(id.Path)
}
// ParseStatusesPath returns the username and uuid from a path such as /users/example_username/statuses/SOME_UUID_OF_A_STATUS
func ParseStatusesPath(id *url.URL) (username string, uuid string, err error) {
// ParseStatusesPath returns the username and ulid from a path such as /users/example_username/statuses/SOME_ULID_OF_A_STATUS
func ParseStatusesPath(id *url.URL) (username string, ulid string, err error) {
matches := statusesPathRegex.FindStringSubmatch(id.Path)
if len(matches) != 3 {
err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches))
return
}
username = matches[1]
uuid = matches[2]
ulid = matches[2]
return
}
@@ -272,14 +276,14 @@ func ParseFollowingPath(id *url.URL) (username string, err error) {
return
}
// ParseLikedPath returns the username and uuid from a path such as /users/example_username/liked/SOME_UUID_OF_A_STATUS
func ParseLikedPath(id *url.URL) (username string, uuid string, err error) {
// ParseLikedPath returns the username and ulid from a path such as /users/example_username/liked/SOME_ULID_OF_A_STATUS
func ParseLikedPath(id *url.URL) (username string, ulid string, err error) {
matches := likePathRegex.FindStringSubmatch(id.Path)
if len(matches) != 3 {
err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches))
return
}
username = matches[1]
uuid = matches[2]
ulid = matches[2]
return
}