Do the Discord integration with personal projects!
This commit is contained in:
parent
37fcbb205c
commit
40cd19c5f0
|
@ -593,7 +593,7 @@ func (bot *botInstance) messageCreateOrUpdate(ctx context.Context, msg *Message)
|
|||
}
|
||||
|
||||
if msg.ChannelID == config.Config.Discord.ShowcaseChannelID {
|
||||
err := bot.processShowcaseMsg(ctx, msg, false)
|
||||
err := bot.processShowcaseMsg(ctx, msg)
|
||||
if err != nil {
|
||||
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process showcase message")
|
||||
return nil
|
||||
|
@ -601,14 +601,14 @@ func (bot *botInstance) messageCreateOrUpdate(ctx context.Context, msg *Message)
|
|||
return nil
|
||||
}
|
||||
|
||||
if msg.ChannelID == config.Config.Discord.JamShowcaseChannelID {
|
||||
err := bot.processShowcaseMsg(ctx, msg, true)
|
||||
if err != nil {
|
||||
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process jam showcase message")
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// if msg.ChannelID == config.Config.Discord.JamShowcaseChannelID {
|
||||
// err := bot.processShowcaseMsg(ctx, msg)
|
||||
// if err != nil {
|
||||
// logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process jam showcase message")
|
||||
// return nil
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
if msg.ChannelID == config.Config.Discord.LibraryChannelID {
|
||||
err := bot.processLibraryMsg(ctx, msg)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"git.handmade.network/hmn/hmn/src/assets"
|
||||
"git.handmade.network/hmn/hmn/src/config"
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/hmndata"
|
||||
"git.handmade.network/hmn/hmn/src/logging"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
|
@ -26,7 +27,7 @@ var reDiscordMessageLink = regexp.MustCompile(`https?://.+?(\s|$)`)
|
|||
var errNotEnoughInfo = errors.New("Discord didn't send enough info in this event for us to do this")
|
||||
|
||||
// TODO: Turn this ad-hoc isJam parameter into a tag or something
|
||||
func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message, isJam bool) error {
|
||||
func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message) error {
|
||||
switch msg.Type {
|
||||
case MessageTypeDefault, MessageTypeReply, MessageTypeApplicationCommand:
|
||||
default:
|
||||
|
@ -63,15 +64,60 @@ func (bot *botInstance) processShowcaseMsg(ctx context.Context, msg *Message, is
|
|||
return oops.New(err, "failed to create snippet in gateway")
|
||||
}
|
||||
|
||||
if isJam {
|
||||
tagId, err := db.QueryInt(ctx, tx, `SELECT id FROM tags WHERE text = 'wheeljam'`)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to fetch id of jam tag")
|
||||
}
|
||||
u, err := FetchDiscordUser(ctx, bot.dbConn, newMsg.UserID)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to look up HMN user information from Discord user")
|
||||
// we shouldn't see a "not found" here because of the AllowedToBlahBlahBlah check.
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, `INSERT INTO snippet_tags (snippet_id, tag_id) VALUES ($1, $2)`, snippet.ID, tagId)
|
||||
projects, err := hmndata.FetchProjects(ctx, bot.dbConn, &u.HMNUser, hmndata.ProjectsQuery{
|
||||
OwnerIDs: []int{u.HMNUser.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to look up user projects")
|
||||
}
|
||||
projectIDs := make([]int, len(projects))
|
||||
for i, p := range projects {
|
||||
projectIDs[i] = p.Project.ID
|
||||
}
|
||||
|
||||
// Try to associate tags in the message with project tags in HMN.
|
||||
// Match only tags for projects in which the current user is a collaborator.
|
||||
messageTags := getDiscordTags(msg.Content)
|
||||
type tagsRow struct {
|
||||
Tag models.Tag `db:"tags"`
|
||||
}
|
||||
itUserTags, err := db.Query(ctx, tx, tagsRow{},
|
||||
`
|
||||
SELECT $columns
|
||||
FROM
|
||||
tags
|
||||
JOIN handmade_project AS project ON project.tag = tags.id
|
||||
JOIN handmade_user_projects AS user_project ON user_project.project_id = project.id
|
||||
WHERE
|
||||
project.id = ANY ($1)
|
||||
`,
|
||||
projectIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to fetch tags for user projects")
|
||||
}
|
||||
iUserTags := itUserTags.ToSlice()
|
||||
|
||||
var tagIDs []int
|
||||
for _, itag := range iUserTags {
|
||||
tag := itag.(*tagsRow).Tag
|
||||
for _, messageTag := range messageTags {
|
||||
if tag.Text == messageTag {
|
||||
tagIDs = append(tagIDs, tag.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tagID := range tagIDs {
|
||||
_, err = tx.Exec(ctx, `INSERT INTO snippet_tags (snippet_id, tag_id) VALUES ($1, $2)`, snippet.ID, tagID)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to mark snippet as a jam snippet")
|
||||
return oops.New(err, "failed to add tag to snippet")
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
|
@ -519,29 +565,42 @@ func saveEmbed(
|
|||
return iDiscordEmbed.(*models.DiscordMessageEmbed), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Checks settings and permissions to decide whether we are allowed to create
|
||||
snippets for a user.
|
||||
*/
|
||||
func AllowedToCreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, discordUserId string) (bool, error) {
|
||||
canSave, err := db.QueryBool(ctx, tx,
|
||||
type DiscordUserAndStuff struct {
|
||||
DiscordUser models.DiscordUser `db:"duser"`
|
||||
HMNUser models.User `db:"u"`
|
||||
}
|
||||
|
||||
func FetchDiscordUser(ctx context.Context, dbConn db.ConnOrTx, discordUserID string) (*DiscordUserAndStuff, error) {
|
||||
iuser, err := db.QueryOne(ctx, dbConn, DiscordUserAndStuff{},
|
||||
`
|
||||
SELECT u.discord_save_showcase
|
||||
SELECT $columns
|
||||
FROM
|
||||
handmade_discorduser AS duser
|
||||
JOIN auth_user AS u ON duser.hmn_user_id = u.id
|
||||
WHERE
|
||||
duser.userid = $1
|
||||
`,
|
||||
discordUserId,
|
||||
discordUserID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iuser.(*DiscordUserAndStuff), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Checks settings and permissions to decide whether we are allowed to create
|
||||
snippets for a user.
|
||||
*/
|
||||
func AllowedToCreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, discordUserId string) (bool, error) {
|
||||
u, err := FetchDiscordUser(ctx, tx, discordUserId)
|
||||
if errors.Is(err, db.NotFound) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, oops.New(err, "failed to check if we can save Discord message")
|
||||
}
|
||||
|
||||
return canSave, nil
|
||||
return u.HMNUser.DiscordSaveShowcase, nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -720,3 +779,14 @@ func messageHasLinks(content string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
var REDiscordTag = regexp.MustCompile(`>([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)`)
|
||||
|
||||
func getDiscordTags(content string) []string {
|
||||
matches := REDiscordTag.FindAllStringSubmatch(content, -1)
|
||||
result := make([]string, len(matches))
|
||||
for i, m := range matches {
|
||||
result[i] = strings.ToLower(m[1])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -41,6 +41,33 @@ func FetchSnippets(
|
|||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
if len(q.Tags) > 0 {
|
||||
// Get snippet IDs with this tag, then use that in the main query
|
||||
type snippetIDRow struct {
|
||||
SnippetID int `db:"snippet_id"`
|
||||
}
|
||||
itSnippetIDs, err := db.Query(ctx, tx, snippetIDRow{},
|
||||
`
|
||||
SELECT DISTINCT snippet_id
|
||||
FROM
|
||||
snippet_tags
|
||||
JOIN tags ON snippet_tags.tag_id = tags.id
|
||||
WHERE
|
||||
tags.id = ANY ($1)
|
||||
`,
|
||||
q.Tags,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, oops.New(err, "failed to get snippet IDs for tag")
|
||||
}
|
||||
iSnippetIDs := itSnippetIDs.ToSlice()
|
||||
|
||||
q.IDs = make([]int, len(iSnippetIDs))
|
||||
for i := range iSnippetIDs {
|
||||
q.IDs[i] = iSnippetIDs[i].(*snippetIDRow).SnippetID
|
||||
}
|
||||
}
|
||||
|
||||
var qb db.QueryBuilder
|
||||
qb.Add(
|
||||
`
|
||||
|
@ -50,8 +77,6 @@ func FetchSnippets(
|
|||
LEFT JOIN auth_user AS owner ON snippet.owner_id = owner.id
|
||||
LEFT JOIN handmade_asset AS asset ON snippet.asset_id = asset.id
|
||||
LEFT JOIN handmade_discordmessage AS discord_message ON snippet.discord_message_id = discord_message.id
|
||||
LEFT JOIN snippet_tags ON snippet.id = snippet_tags.snippet_id
|
||||
LEFT JOIN tags ON snippet_tags.tag_id = tags.id
|
||||
WHERE
|
||||
TRUE
|
||||
`,
|
||||
|
@ -62,9 +87,6 @@ func FetchSnippets(
|
|||
if len(q.OwnerIDs) > 0 {
|
||||
qb.Add(`AND snippet.owner_id = ANY ($?)`, q.OwnerIDs)
|
||||
}
|
||||
if len(q.Tags) > 0 {
|
||||
qb.Add(`AND snippet_tags.tag_id = ANY ($?)`, q.Tags)
|
||||
}
|
||||
if currentUser == nil {
|
||||
qb.Add(
|
||||
`AND owner.status = $? -- snippet owner is Approved`,
|
||||
|
@ -92,34 +114,59 @@ func FetchSnippets(
|
|||
Owner *models.User `db:"owner"`
|
||||
Asset *models.Asset `db:"asset"`
|
||||
DiscordMessage *models.DiscordMessage `db:"discord_message"`
|
||||
Tag *models.Tag `db:"tags"`
|
||||
}
|
||||
|
||||
it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...)
|
||||
it, err := db.Query(ctx, tx, resultRow{}, qb.String(), qb.Args()...)
|
||||
if err != nil {
|
||||
return nil, oops.New(err, "failed to fetch threads")
|
||||
}
|
||||
iresults := it.ToSlice()
|
||||
|
||||
result := make([]SnippetAndStuff, 0, len(iresults)) // allocate extra space because why not
|
||||
currentSnippetId := -1
|
||||
for _, iresult := range iresults {
|
||||
result := make([]SnippetAndStuff, len(iresults)) // allocate extra space because why not
|
||||
snippetIDs := make([]int, len(iresults))
|
||||
for i, iresult := range iresults {
|
||||
row := *iresult.(*resultRow)
|
||||
|
||||
if row.Snippet.ID != currentSnippetId {
|
||||
// we have moved onto a new snippet; make a new entry
|
||||
result = append(result, SnippetAndStuff{
|
||||
Snippet: row.Snippet,
|
||||
Owner: row.Owner,
|
||||
Asset: row.Asset,
|
||||
DiscordMessage: row.DiscordMessage,
|
||||
// no tags! tags next
|
||||
})
|
||||
result[i] = SnippetAndStuff{
|
||||
Snippet: row.Snippet,
|
||||
Owner: row.Owner,
|
||||
Asset: row.Asset,
|
||||
DiscordMessage: row.DiscordMessage,
|
||||
// no tags! tags next
|
||||
}
|
||||
snippetIDs[i] = row.Snippet.ID
|
||||
}
|
||||
|
||||
if row.Tag != nil {
|
||||
result[len(result)-1].Tags = append(result[len(result)-1].Tags, row.Tag)
|
||||
}
|
||||
// Fetch tags
|
||||
type snippetTagRow struct {
|
||||
SnippetID int `db:"snippet_tags.snippet_id"`
|
||||
Tag *models.Tag `db:"tags"`
|
||||
}
|
||||
itSnippetTags, err := db.Query(ctx, tx, snippetTagRow{},
|
||||
`
|
||||
SELECT $columns
|
||||
FROM
|
||||
snippet_tags
|
||||
JOIN tags ON snippet_tags.tag_id = tags.id
|
||||
WHERE
|
||||
snippet_tags.snippet_id = ANY($1)
|
||||
`,
|
||||
snippetIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, oops.New(err, "failed to fetch tags for snippets")
|
||||
}
|
||||
iSnippetTags := itSnippetTags.ToSlice()
|
||||
|
||||
// associate tags with snippets
|
||||
resultBySnippetId := make(map[int]*SnippetAndStuff)
|
||||
for i := range result {
|
||||
resultBySnippetId[result[i].Snippet.ID] = &result[i]
|
||||
}
|
||||
for _, iSnippetTag := range iSnippetTags {
|
||||
snippetTag := iSnippetTag.(*snippetTagRow)
|
||||
item := resultBySnippetId[snippetTag.SnippetID]
|
||||
item.Tags = append(item.Tags, snippetTag.Tag)
|
||||
}
|
||||
|
||||
err = tx.Commit(ctx)
|
||||
|
|
|
@ -370,7 +370,7 @@ func TimelineItemsToJSON(items []TimelineItem) string {
|
|||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"tags":[`)
|
||||
for _, tag := range item.Tags {
|
||||
for i, tag := range item.Tags {
|
||||
builder.WriteString(`{`)
|
||||
|
||||
builder.WriteString(`"text":"`)
|
||||
|
@ -382,6 +382,9 @@ func TimelineItemsToJSON(items []TimelineItem) string {
|
|||
builder.WriteString(`"`)
|
||||
|
||||
builder.WriteString(`}`)
|
||||
if i < len(item.Tags)-1 {
|
||||
builder.WriteString(`,`)
|
||||
}
|
||||
}
|
||||
builder.WriteString(`]`)
|
||||
|
||||
|
|
Loading…
Reference in New Issue