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 {
|
if msg.ChannelID == config.Config.Discord.ShowcaseChannelID {
|
||||||
err := bot.processShowcaseMsg(ctx, msg, false)
|
err := bot.processShowcaseMsg(ctx, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process showcase message")
|
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process showcase message")
|
||||||
return nil
|
return nil
|
||||||
|
@ -601,14 +601,14 @@ func (bot *botInstance) messageCreateOrUpdate(ctx context.Context, msg *Message)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.ChannelID == config.Config.Discord.JamShowcaseChannelID {
|
// if msg.ChannelID == config.Config.Discord.JamShowcaseChannelID {
|
||||||
err := bot.processShowcaseMsg(ctx, msg, true)
|
// err := bot.processShowcaseMsg(ctx, msg)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process jam showcase message")
|
// logging.ExtractLogger(ctx).Error().Err(err).Msg("failed to process jam showcase message")
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
if msg.ChannelID == config.Config.Discord.LibraryChannelID {
|
if msg.ChannelID == config.Config.Discord.LibraryChannelID {
|
||||||
err := bot.processLibraryMsg(ctx, msg)
|
err := bot.processLibraryMsg(ctx, msg)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/assets"
|
"git.handmade.network/hmn/hmn/src/assets"
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"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/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"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")
|
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
|
// 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 {
|
switch msg.Type {
|
||||||
case MessageTypeDefault, MessageTypeReply, MessageTypeApplicationCommand:
|
case MessageTypeDefault, MessageTypeReply, MessageTypeApplicationCommand:
|
||||||
default:
|
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")
|
return oops.New(err, "failed to create snippet in gateway")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isJam {
|
u, err := FetchDiscordUser(ctx, bot.dbConn, newMsg.UserID)
|
||||||
tagId, err := db.QueryInt(ctx, tx, `SELECT id FROM tags WHERE text = 'wheeljam'`)
|
if err != nil {
|
||||||
if err != nil {
|
return oops.New(err, "failed to look up HMN user information from Discord user")
|
||||||
return oops.New(err, "failed to fetch id of jam tag")
|
// 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 {
|
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 {
|
} else if err != nil {
|
||||||
|
@ -519,29 +565,42 @@ func saveEmbed(
|
||||||
return iDiscordEmbed.(*models.DiscordMessageEmbed), nil
|
return iDiscordEmbed.(*models.DiscordMessageEmbed), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
type DiscordUserAndStuff struct {
|
||||||
Checks settings and permissions to decide whether we are allowed to create
|
DiscordUser models.DiscordUser `db:"duser"`
|
||||||
snippets for a user.
|
HMNUser models.User `db:"u"`
|
||||||
*/
|
}
|
||||||
func AllowedToCreateMessageSnippet(ctx context.Context, tx db.ConnOrTx, discordUserId string) (bool, error) {
|
|
||||||
canSave, err := db.QueryBool(ctx, tx,
|
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
|
FROM
|
||||||
handmade_discorduser AS duser
|
handmade_discorduser AS duser
|
||||||
JOIN auth_user AS u ON duser.hmn_user_id = u.id
|
JOIN auth_user AS u ON duser.hmn_user_id = u.id
|
||||||
WHERE
|
WHERE
|
||||||
duser.userid = $1
|
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) {
|
if errors.Is(err, db.NotFound) {
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return false, oops.New(err, "failed to check if we can save Discord message")
|
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
|
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)
|
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
|
var qb db.QueryBuilder
|
||||||
qb.Add(
|
qb.Add(
|
||||||
`
|
`
|
||||||
|
@ -50,8 +77,6 @@ func FetchSnippets(
|
||||||
LEFT JOIN auth_user AS owner ON snippet.owner_id = owner.id
|
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_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 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
|
WHERE
|
||||||
TRUE
|
TRUE
|
||||||
`,
|
`,
|
||||||
|
@ -62,9 +87,6 @@ func FetchSnippets(
|
||||||
if len(q.OwnerIDs) > 0 {
|
if len(q.OwnerIDs) > 0 {
|
||||||
qb.Add(`AND snippet.owner_id = ANY ($?)`, q.OwnerIDs)
|
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 {
|
if currentUser == nil {
|
||||||
qb.Add(
|
qb.Add(
|
||||||
`AND owner.status = $? -- snippet owner is Approved`,
|
`AND owner.status = $? -- snippet owner is Approved`,
|
||||||
|
@ -92,34 +114,59 @@ func FetchSnippets(
|
||||||
Owner *models.User `db:"owner"`
|
Owner *models.User `db:"owner"`
|
||||||
Asset *models.Asset `db:"asset"`
|
Asset *models.Asset `db:"asset"`
|
||||||
DiscordMessage *models.DiscordMessage `db:"discord_message"`
|
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 {
|
if err != nil {
|
||||||
return nil, oops.New(err, "failed to fetch threads")
|
return nil, oops.New(err, "failed to fetch threads")
|
||||||
}
|
}
|
||||||
iresults := it.ToSlice()
|
iresults := it.ToSlice()
|
||||||
|
|
||||||
result := make([]SnippetAndStuff, 0, len(iresults)) // allocate extra space because why not
|
result := make([]SnippetAndStuff, len(iresults)) // allocate extra space because why not
|
||||||
currentSnippetId := -1
|
snippetIDs := make([]int, len(iresults))
|
||||||
for _, iresult := range iresults {
|
for i, iresult := range iresults {
|
||||||
row := *iresult.(*resultRow)
|
row := *iresult.(*resultRow)
|
||||||
|
|
||||||
if row.Snippet.ID != currentSnippetId {
|
result[i] = SnippetAndStuff{
|
||||||
// we have moved onto a new snippet; make a new entry
|
Snippet: row.Snippet,
|
||||||
result = append(result, SnippetAndStuff{
|
Owner: row.Owner,
|
||||||
Snippet: row.Snippet,
|
Asset: row.Asset,
|
||||||
Owner: row.Owner,
|
DiscordMessage: row.DiscordMessage,
|
||||||
Asset: row.Asset,
|
// no tags! tags next
|
||||||
DiscordMessage: row.DiscordMessage,
|
|
||||||
// no tags! tags next
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
snippetIDs[i] = row.Snippet.ID
|
||||||
|
}
|
||||||
|
|
||||||
if row.Tag != nil {
|
// Fetch tags
|
||||||
result[len(result)-1].Tags = append(result[len(result)-1].Tags, row.Tag)
|
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)
|
err = tx.Commit(ctx)
|
||||||
|
|
|
@ -370,7 +370,7 @@ func TimelineItemsToJSON(items []TimelineItem) string {
|
||||||
builder.WriteString(`",`)
|
builder.WriteString(`",`)
|
||||||
|
|
||||||
builder.WriteString(`"tags":[`)
|
builder.WriteString(`"tags":[`)
|
||||||
for _, tag := range item.Tags {
|
for i, tag := range item.Tags {
|
||||||
builder.WriteString(`{`)
|
builder.WriteString(`{`)
|
||||||
|
|
||||||
builder.WriteString(`"text":"`)
|
builder.WriteString(`"text":"`)
|
||||||
|
@ -382,6 +382,9 @@ func TimelineItemsToJSON(items []TimelineItem) string {
|
||||||
builder.WriteString(`"`)
|
builder.WriteString(`"`)
|
||||||
|
|
||||||
builder.WriteString(`}`)
|
builder.WriteString(`}`)
|
||||||
|
if i < len(item.Tags)-1 {
|
||||||
|
builder.WriteString(`,`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.WriteString(`]`)
|
builder.WriteString(`]`)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue