Link to HMN profiles from fishbowls where available
This commit is contained in:
parent
df4ff592b4
commit
7b5bf65c7b
|
@ -12,8 +12,9 @@ import (
|
|||
|
||||
type UsersQuery struct {
|
||||
// Ignored when using FetchUser
|
||||
UserIDs []int // if empty, all users
|
||||
Usernames []string // if empty, all users
|
||||
UserIDs []int // if empty, all users
|
||||
Usernames []string // if empty, all users
|
||||
DiscordUserIDs []string // if empty, no Discord filtering
|
||||
|
||||
// Flags to modify behavior
|
||||
AnyStatus bool // Bypasses shadowban system
|
||||
|
@ -44,8 +45,9 @@ func FetchUsers(
|
|||
}
|
||||
|
||||
type userRow struct {
|
||||
User models.User `db:"hmn_user"`
|
||||
AvatarAsset *models.Asset `db:"avatar"`
|
||||
User models.User `db:"hmn_user"`
|
||||
AvatarAsset *models.Asset `db:"avatar"`
|
||||
DiscordUser *models.DiscordUser `db:"discord_user"`
|
||||
}
|
||||
|
||||
var qb db.QueryBuilder
|
||||
|
@ -54,6 +56,7 @@ func FetchUsers(
|
|||
FROM
|
||||
hmn_user
|
||||
LEFT JOIN asset AS avatar ON avatar.id = hmn_user.avatar_asset_id
|
||||
LEFT JOIN discord_user ON discord_user.hmn_user_id = hmn_user.id
|
||||
WHERE
|
||||
TRUE
|
||||
`)
|
||||
|
@ -63,6 +66,9 @@ func FetchUsers(
|
|||
if len(q.Usernames) > 0 {
|
||||
qb.Add(`AND LOWER(hmn_user.username) = ANY($?)`, q.Usernames)
|
||||
}
|
||||
if len(q.DiscordUserIDs) > 0 {
|
||||
qb.Add(`AND discord_user.userid = ANY($?)`, q.DiscordUserIDs)
|
||||
}
|
||||
if !q.AnyStatus {
|
||||
if currentUser == nil {
|
||||
qb.Add(`AND hmn_user.status = $?`, models.UserStatusApproved)
|
||||
|
@ -89,6 +95,7 @@ func FetchUsers(
|
|||
for i, row := range userRows {
|
||||
user := row.User
|
||||
user.AvatarAsset = row.AvatarAsset
|
||||
user.DiscordUser = row.DiscordUser
|
||||
result[i] = &user
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ type User struct {
|
|||
|
||||
// Non-db fields, to be filled in by fetch helpers
|
||||
AvatarAsset *Asset
|
||||
DiscordUser *DiscordUser
|
||||
}
|
||||
|
||||
func (u *User) BestName() string {
|
||||
|
|
|
@ -65,6 +65,10 @@
|
|||
.fishbowl-banner a {
|
||||
color: {{ eq .Theme "dark" | ternary "#9ad0ff" "#1f4f99" }};
|
||||
}
|
||||
|
||||
.fishbowl .chatlog__author a {
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package website
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/hmndata"
|
||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
"git.handmade.network/hmn/hmn/src/utils"
|
||||
|
@ -157,6 +162,11 @@ func Fishbowl(c *RequestContext) ResponseData {
|
|||
}
|
||||
|
||||
contentsFile := utils.Must1(fishbowlFS.Open(info.ContentsPath))
|
||||
contents := string(utils.Must1(io.ReadAll(contentsFile)))
|
||||
contents, err := linkifyDiscordContent(c, c.Conn, contents)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to linkify fishbowl content"))
|
||||
}
|
||||
|
||||
tmpl := FishbowlData{
|
||||
BaseData: getBaseData(c, info.Title, []templates.Breadcrumb{
|
||||
|
@ -165,7 +175,7 @@ func Fishbowl(c *RequestContext) ResponseData {
|
|||
}),
|
||||
Slug: slug,
|
||||
Info: info,
|
||||
Contents: template.HTML(utils.Must1(io.ReadAll(contentsFile))),
|
||||
Contents: template.HTML(contents),
|
||||
}
|
||||
tmpl.BaseData.OpenGraphItems = append(tmpl.BaseData.OpenGraphItems, templates.OpenGraphItem{
|
||||
Property: "og:description",
|
||||
|
@ -173,7 +183,7 @@ func Fishbowl(c *RequestContext) ResponseData {
|
|||
})
|
||||
|
||||
var res ResponseData
|
||||
err := res.WriteTemplate("fishbowl.html", tmpl, c.Perf)
|
||||
err = res.WriteTemplate("fishbowl.html", tmpl, c.Perf)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render fishbowl index page"))
|
||||
}
|
||||
|
@ -186,3 +196,45 @@ func FishbowlFiles(c *RequestContext) ResponseData {
|
|||
AddCORSHeaders(c, &res)
|
||||
return res
|
||||
}
|
||||
|
||||
var reFishbowlDiscordUserId = regexp.MustCompile(`data-user-id="(\d+)"`)
|
||||
var reFishbowlDiscordAuthorHeader = regexp.MustCompile(`(?s:(<div class="chatlog__message">.*?)(<img class="chatlog__avatar".*?>)(.*?<span class="chatlog__author".*?data-user-id="(\d+)".*?>)(.*?)(</span>))`)
|
||||
|
||||
func linkifyDiscordContent(c *RequestContext, dbConn db.ConnOrTx, content string) (string, error) {
|
||||
discordUserIdSet := make(map[string]struct{})
|
||||
userIdMatches := reFishbowlDiscordUserId.FindAllStringSubmatch(content, -1)
|
||||
for _, m := range userIdMatches {
|
||||
discordUserIdSet[m[1]] = struct{}{}
|
||||
}
|
||||
discordUserIds := make([]string, 0, len(discordUserIdSet))
|
||||
for id := range discordUserIdSet {
|
||||
discordUserIds = append(discordUserIds, id)
|
||||
}
|
||||
|
||||
hmnUsers, err := hmndata.FetchUsers(c.Context(), dbConn, c.CurrentUser, hmndata.UsersQuery{
|
||||
DiscordUserIDs: discordUserIds,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return reFishbowlDiscordAuthorHeader.ReplaceAllStringFunc(content, func(s string) string {
|
||||
m := reFishbowlDiscordAuthorHeader.FindStringSubmatch(s)
|
||||
discordUserID := m[4]
|
||||
|
||||
var matchingUser *models.User
|
||||
for _, u := range hmnUsers {
|
||||
if u.DiscordUser.UserID == discordUserID {
|
||||
matchingUser = u
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchingUser == nil {
|
||||
return s
|
||||
} else {
|
||||
link := fmt.Sprintf(`<a href="%s" target="_blank">`, hmnurl.BuildUserProfile(matchingUser.Username))
|
||||
return m[1] + link + m[2] + "</a>" + m[3] + link + m[5] + "</a>" + m[6]
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue