forked from External/ergo
reactions
This commit is contained in:
parent
d73b6bac86
commit
3b0fecd381
9 changed files with 189 additions and 115 deletions
|
|
@ -34,9 +34,13 @@ func (client *Client) RedisBroadcast(message ...string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) Broadcast(command string, params ...string) {
|
func (channel *Channel) Broadcast(command string, params ...string) {
|
||||||
|
channel.BroadcastFrom(channel.server.name, command, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (channel *Channel) BroadcastFrom(prefix string, command string, params ...string) {
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
for _, session := range member.Sessions() {
|
for _, session := range member.Sessions() {
|
||||||
session.Send(nil, member.server.name, command, params...)
|
session.Send(nil, prefix, command, params...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +157,6 @@ func (server *Server) GetUrlMime(url string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
contentType, valid := meta["format"].(string)
|
contentType, valid := meta["format"].(string)
|
||||||
fmt.Printf("%+v\n", meta)
|
|
||||||
if !valid {
|
if !valid {
|
||||||
println("No content type")
|
println("No content type")
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -727,6 +727,9 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e
|
||||||
if !itemIsStorable(&item, channel.server.Config()) {
|
if !itemIsStorable(&item, channel.server.Config()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if item.Target == "" {
|
||||||
|
item.Target = channel.nameCasefolded
|
||||||
|
}
|
||||||
|
|
||||||
status, target, _ := channel.historyStatus(channel.server.Config())
|
status, target, _ := channel.historyStatus(channel.server.Config())
|
||||||
if status == HistoryPersistent {
|
if status == HistoryPersistent {
|
||||||
|
|
@ -1091,9 +1094,9 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
||||||
nick := NUHToNick(item.Nick)
|
nick := NUHToNick(item.Nick)
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
case history.Privmsg:
|
case history.Privmsg:
|
||||||
rb.AddSplitMessageFromClient(item.Nick, item.Account, item.IsBot, item.Tags, "PRIVMSG", chname, item.Message)
|
rb.AddSplitMessageFromClientWithReactions(item.Nick, item.Account, item.IsBot, item.Tags, "PRIVMSG", chname, item.Message, item.Reactions)
|
||||||
case history.Notice:
|
case history.Notice:
|
||||||
rb.AddSplitMessageFromClient(item.Nick, item.Account, item.IsBot, item.Tags, "NOTICE", chname, item.Message)
|
rb.AddSplitMessageFromClientWithReactions(item.Nick, item.Account, item.IsBot, item.Tags, "NOTICE", chname, item.Message, item.Reactions)
|
||||||
case history.Tagmsg:
|
case history.Tagmsg:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddSplitMessageFromClient(item.Nick, item.Account, item.IsBot, item.Tags, "TAGMSG", chname, item.Message)
|
rb.AddSplitMessageFromClient(item.Nick, item.Account, item.IsBot, item.Tags, "TAGMSG", chname, item.Message)
|
||||||
|
|
|
||||||
|
|
@ -931,11 +931,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
|
||||||
tags = item.Tags
|
tags = item.Tags
|
||||||
}
|
}
|
||||||
if !isSelfMessage(&item) {
|
if !isSelfMessage(&item) {
|
||||||
rb.AddSplitMessageFromClient(item.Nick, item.Account, item.IsBot, tags, command, nick, item.Message)
|
rb.AddSplitMessageFromClientWithReactions(item.Nick, item.Account, item.IsBot, tags, command, nick, item.Message, item.Reactions)
|
||||||
} else {
|
} else {
|
||||||
// this message was sent *from* the client to another nick; the target is item.Params[0]
|
// this message was sent *from* the client to another nick; the target is item.Params[0]
|
||||||
// substitute client's current nickmask in case client changed nick
|
// substitute client's current nickmask in case client changed nick
|
||||||
rb.AddSplitMessageFromClient(details.nickMask, item.Account, item.IsBot, tags, command, item.Params[0], item.Message)
|
rb.AddSplitMessageFromClientWithReactions(details.nickMask, item.Account, item.IsBot, tags, command, item.Params[0], item.Message, item.Reactions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,11 @@ func init() {
|
||||||
handler: zncHandler,
|
handler: zncHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
},
|
},
|
||||||
|
// CEF custom commands
|
||||||
|
"REACT": {
|
||||||
|
handler: reactHandler,
|
||||||
|
minParams: 2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeServices()
|
initializeServices()
|
||||||
|
|
|
||||||
|
|
@ -4085,6 +4085,33 @@ func zncHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REACT <msgid> :<reaction>
|
||||||
|
func reactHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
||||||
|
// This directly uses SQL stuff, since it's targeted at CEF, which requires a DB.
|
||||||
|
_, _, target, sender, _, pm, err := server.historyDB.GetMessage(msg.Params[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var operation string
|
||||||
|
if server.historyDB.HasReactionFromUser(msg.Params[0], client.AccountName(), msg.Params[1]) {
|
||||||
|
server.historyDB.DeleteReaction(msg.Params[0], client.AccountName(), msg.Params[1])
|
||||||
|
operation = "DEL"
|
||||||
|
} else {
|
||||||
|
server.historyDB.AddReaction(msg.Params[0], client.AccountName(), msg.Params[1])
|
||||||
|
operation = "ADD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if pm {
|
||||||
|
server.clients.Get(target).Send(nil, client.NickMaskString(), "REACT", operation, msg.Params[0], msg.Params[1])
|
||||||
|
server.clients.Get(sender).Send(nil, client.NickMaskString(), "REACT", operation, msg.Params[0], msg.Params[1])
|
||||||
|
} else {
|
||||||
|
server.channels.Get(target).BroadcastFrom(client.NickMaskString(), "REACT", operation, msg.Params[0], msg.Params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// fake handler for unknown commands
|
// fake handler for unknown commands
|
||||||
func unknownCommandHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
func unknownCommandHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
||||||
var message string
|
var message string
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,12 @@ for direct use by end users.`,
|
||||||
duplicate: true,
|
duplicate: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"react": {
|
||||||
|
text: `REACT <msgid> <reaction>
|
||||||
|
|
||||||
|
Toggles a reaction to a message. CEF-specific`,
|
||||||
|
},
|
||||||
|
|
||||||
// Informational
|
// Informational
|
||||||
"modes": {
|
"modes": {
|
||||||
textGenerator: modesTextGenerator,
|
textGenerator: modesTextGenerator,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,12 @@ const (
|
||||||
initialAutoSize = 32
|
initialAutoSize = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Reaction struct {
|
||||||
|
Name string
|
||||||
|
Total int
|
||||||
|
SampleUsers []string
|
||||||
|
}
|
||||||
|
|
||||||
// Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
|
// Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Type ItemType
|
Type ItemType
|
||||||
|
|
@ -49,6 +55,8 @@ type Item struct {
|
||||||
// required by CHATHISTORY:
|
// required by CHATHISTORY:
|
||||||
Target string `json:"Target"`
|
Target string `json:"Target"`
|
||||||
IsBot bool `json:"IsBot,omitempty"`
|
IsBot bool `json:"IsBot,omitempty"`
|
||||||
|
|
||||||
|
Reactions []Reaction
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasMsgid tests whether a message has the message id `msgid`.
|
// HasMsgid tests whether a message has the message id `msgid`.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package mysql
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -30,14 +31,6 @@ const (
|
||||||
// maximum length in bytes of any message target (nickname or channel name) in its
|
// maximum length in bytes of any message target (nickname or channel name) in its
|
||||||
// canonicalized (i.e., casefolded) state:
|
// canonicalized (i.e., casefolded) state:
|
||||||
MaxTargetLength = 64
|
MaxTargetLength = 64
|
||||||
|
|
||||||
// latest schema of the db
|
|
||||||
latestDbSchema = "2"
|
|
||||||
keySchemaVersion = "db.version"
|
|
||||||
// minor version indicates rollback-safe upgrades, i.e.,
|
|
||||||
// you can downgrade oragono and everything will work
|
|
||||||
latestDbMinorVersion = "2"
|
|
||||||
keySchemaMinorVersion = "db.minorversion"
|
|
||||||
cleanupRowLimit = 50
|
cleanupRowLimit = 50
|
||||||
cleanupPauseTime = 10 * time.Minute
|
cleanupPauseTime = 10 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
@ -52,6 +45,13 @@ type MySQL struct {
|
||||||
insertConversation *sql.Stmt
|
insertConversation *sql.Stmt
|
||||||
insertAccountMessage *sql.Stmt
|
insertAccountMessage *sql.Stmt
|
||||||
|
|
||||||
|
getReactionsQuery *sql.Stmt
|
||||||
|
getSingleReaction *sql.Stmt
|
||||||
|
addReaction *sql.Stmt
|
||||||
|
deleteReaction *sql.Stmt
|
||||||
|
|
||||||
|
getMessageById *sql.Stmt
|
||||||
|
|
||||||
stateMutex sync.Mutex
|
stateMutex sync.Mutex
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
|
|
@ -124,102 +124,17 @@ func (mysql *MySQL) Open() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mysql *MySQL) fixSchemas() (err error) {
|
func (mysql *MySQL) fixSchemas() (err error) {
|
||||||
_, err = mysql.db.Exec(`CREATE TABLE IF NOT EXISTS metadata (
|
// 3M now handles this
|
||||||
key_name VARCHAR(32) primary key,
|
|
||||||
value VARCHAR(32) NOT NULL
|
|
||||||
) CHARSET=ascii COLLATE=ascii_bin;`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var schema string
|
|
||||||
err = mysql.db.QueryRow(`select value from metadata where key_name = ?;`, keySchemaVersion).Scan(&schema)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
err = mysql.createTables()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = mysql.db.Exec(`insert into metadata (key_name, value) values (?, ?);`, keySchemaVersion, latestDbSchema)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = mysql.db.Exec(`insert into metadata (key_name, value) values (?, ?);`, keySchemaMinorVersion, latestDbMinorVersion)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if err == nil && schema != latestDbSchema {
|
|
||||||
// TODO figure out what to do about schema changes
|
|
||||||
return fmt.Errorf("incompatible schema: got %s, expected %s", schema, latestDbSchema)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var minorVersion string
|
|
||||||
err = mysql.db.QueryRow(`select value from metadata where key_name = ?;`, keySchemaMinorVersion).Scan(&minorVersion)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// XXX for now, the only minor version upgrade is the account tracking tables
|
|
||||||
err = mysql.createComplianceTables()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = mysql.db.Exec(`insert into metadata (key_name, value) values (?, ?);`, keySchemaMinorVersion, latestDbMinorVersion)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err == nil && minorVersion == "1" {
|
|
||||||
// upgrade from 2.1 to 2.2: create the correspondents table
|
|
||||||
_, err = mysql.db.Exec(`update metadata set value = ? where key_name = ?;`, latestDbMinorVersion, keySchemaMinorVersion)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err == nil && minorVersion != latestDbMinorVersion {
|
|
||||||
// TODO: if minorVersion < latestDbMinorVersion, upgrade,
|
|
||||||
// if latestDbMinorVersion < minorVersion, ignore because backwards compatible
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mysql *MySQL) createTables() (err error) {
|
func (mysql *MySQL) createTables() (err error) {
|
||||||
_, err = mysql.db.Exec(fmt.Sprintf(`CREATE TABLE history (
|
// 3M now handles this
|
||||||
msgid BINARY(16) NOT NULL PRIMARY KEY,
|
|
||||||
data BLOB NOT NULL,
|
|
||||||
target VARBINARY(%[1]d) NOT NULL,
|
|
||||||
sender VARBINARY(%[1]d) NOT NULL,
|
|
||||||
nanotime BIGINT UNSIGNED NOT NULL,
|
|
||||||
pm boolean as (SUBSTRING(target, 1, 1) != "#") PERSISTENT,
|
|
||||||
KEY (msgid(4))
|
|
||||||
) CHARSET=ascii COLLATE=ascii_bin;`, MaxTargetLength, MaxTargetLength))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = mysql.createComplianceTables()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mysql *MySQL) createComplianceTables() (err error) {
|
func (mysql *MySQL) createComplianceTables() (err error) {
|
||||||
_, err = mysql.db.Exec(fmt.Sprintf(`CREATE TABLE account_messages (
|
// 3M now handles this
|
||||||
history_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
|
||||||
account VARBINARY(%[1]d) NOT NULL,
|
|
||||||
KEY (account, history_id)
|
|
||||||
) CHARSET=ascii COLLATE=ascii_bin;`, MaxTargetLength))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = mysql.db.Exec(fmt.Sprintf(`CREATE TABLE forget (
|
|
||||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
account VARBINARY(%[1]d) NOT NULL
|
|
||||||
) CHARSET=ascii COLLATE=ascii_bin;`, MaxTargetLength))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,7 +204,8 @@ func (mysql *MySQL) deleteHistoryIDs(ctx context.Context, ids []uint64) (err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = mysql.db.ExecContext(ctx, fmt.Sprintf(`DELETE FROM history WHERE id in %s;`, inClause))
|
fmt.Printf(`DELETE FROM history WHERE msgid in %s;`, inClause)
|
||||||
|
_, err = mysql.db.ExecContext(ctx, fmt.Sprintf(`DELETE FROM history WHERE msgid in %s;`, inClause))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -299,6 +215,7 @@ func (mysql *MySQL) deleteHistoryIDs(ctx context.Context, ids []uint64) (err err
|
||||||
|
|
||||||
func (mysql *MySQL) selectCleanupIDs(ctx context.Context, age time.Duration) (ids []uint64, maxNanotime int64, err error) {
|
func (mysql *MySQL) selectCleanupIDs(ctx context.Context, age time.Duration) (ids []uint64, maxNanotime int64, err error) {
|
||||||
before := timestampSnowflake(time.Now().Add(-age))
|
before := timestampSnowflake(time.Now().Add(-age))
|
||||||
|
maxNanotime = time.Now().Add(-age).Unix() * 1000000000
|
||||||
|
|
||||||
rows, err := mysql.db.QueryContext(ctx, `
|
rows, err := mysql.db.QueryContext(ctx, `
|
||||||
SELECT history.msgid
|
SELECT history.msgid
|
||||||
|
|
@ -310,8 +227,7 @@ func (mysql *MySQL) selectCleanupIDs(ctx context.Context, age time.Duration) (id
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
idset := make(map[uint64]struct{}, cleanupRowLimit)
|
ids = make([]uint64, cleanupRowLimit)
|
||||||
ids = make([]uint64, len(idset))
|
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
|
@ -323,6 +239,7 @@ func (mysql *MySQL) selectCleanupIDs(ctx context.Context, age time.Duration) (id
|
||||||
ids[i] = id
|
ids[i] = id
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
ids = ids[0:i]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,6 +358,38 @@ func (mysql *MySQL) prepareStatements() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mysql.getReactionsQuery, err = mysql.db.Prepare(`select react, count(*) as total, (select JSON_ARRAYAGG(user)
|
||||||
|
from reactions r
|
||||||
|
where r.msgid = main.msgid
|
||||||
|
and r.react = main.react
|
||||||
|
limit 3) as sample
|
||||||
|
from reactions as main
|
||||||
|
where main.msgid = ?
|
||||||
|
group by react, msgid;`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql.getSingleReaction, err = mysql.db.Prepare(`SELECT COUNT(*) FROM reactions WHERE msgid = ? AND user = ? AND react = ?`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql.deleteReaction, err = mysql.db.Prepare(`DELETE FROM reactions WHERE msgid = ? AND user = ? AND react = ?`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql.addReaction, err = mysql.db.Prepare(`INSERT INTO reactions(msgid, user, react) VALUES (?, ?, ?)`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql.getMessageById, err = mysql.db.Prepare(`SELECT msgid, data, target, sender, nanotime, pm FROM history WHERE msgid = ?`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -649,6 +598,44 @@ func (mysql *MySQL) Export(account string, writer io.Writer) {
|
||||||
return*/
|
return*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kinda an intermediary function due to the CEF DB structure
|
||||||
|
func (mysql *MySQL) GetMessage(msgid string) (id uint64, item history.Item, target string, sender string, nanotime uint64, pm bool, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), mysql.getTimeout())
|
||||||
|
defer cancel()
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
row := mysql.getMessageById.QueryRowContext(ctx, msgid)
|
||||||
|
err = row.Scan(&id, &data, &target, &sender, &nanotime, &pm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = unmarshalItem(data, &item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mysql *MySQL) HasReactionFromUser(msgid string, user string, reaction string) (exists bool) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), mysql.getTimeout())
|
||||||
|
defer cancel()
|
||||||
|
row := mysql.getSingleReaction.QueryRowContext(ctx, msgid, user, reaction)
|
||||||
|
var count int
|
||||||
|
row.Scan(&count)
|
||||||
|
return count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mysql *MySQL) AddReaction(msgid string, user string, reaction string) (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), mysql.getTimeout())
|
||||||
|
defer cancel()
|
||||||
|
_, err = mysql.addReaction.ExecContext(ctx, msgid, user, reaction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mysql *MySQL) DeleteReaction(msgid string, user string, reaction string) (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), mysql.getTimeout())
|
||||||
|
defer cancel()
|
||||||
|
_, err = mysql.deleteReaction.ExecContext(ctx, msgid, user, reaction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData bool) (result time.Time, id uint64, data []byte, err error) {
|
func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData bool) (result time.Time, id uint64, data []byte, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -662,7 +649,7 @@ func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData b
|
||||||
// May have to adjust it some day
|
// May have to adjust it some day
|
||||||
row := mysql.db.QueryRowContext(ctx, fmt.Sprintf(`
|
row := mysql.db.QueryRowContext(ctx, fmt.Sprintf(`
|
||||||
SELECT %s FROM history
|
SELECT %s FROM history
|
||||||
WHERE history.msgid = CAST(? AS INT) LIMIT 1;`, cols), msgid)
|
WHERE history.msgid = CAST(? AS UNSIGNED) LIMIT 1;`, cols), msgid)
|
||||||
var nanoSeq sql.NullInt64
|
var nanoSeq sql.NullInt64
|
||||||
if !includeData {
|
if !includeData {
|
||||||
err = row.Scan(&nanoSeq)
|
err = row.Scan(&nanoSeq)
|
||||||
|
|
@ -694,8 +681,10 @@ func (mysql *MySQL) selectItems(ctx context.Context, query string, args ...inter
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var blob []byte
|
var blob []byte
|
||||||
|
var msgid uint64
|
||||||
var item history.Item
|
var item history.Item
|
||||||
err = rows.Scan(&blob)
|
|
||||||
|
err = rows.Scan(&blob, &msgid)
|
||||||
if mysql.logError("could not scan history item", err) {
|
if mysql.logError("could not scan history item", err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -703,6 +692,25 @@ func (mysql *MySQL) selectItems(ctx context.Context, query string, args ...inter
|
||||||
if mysql.logError("could not unmarshal history item", err) {
|
if mysql.logError("could not unmarshal history item", err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
reactions, rErr := mysql.getReactionsQuery.Query(msgid)
|
||||||
|
if mysql.logError("could not get reactions", rErr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var react string
|
||||||
|
var total int
|
||||||
|
var sample string
|
||||||
|
for reactions.Next() {
|
||||||
|
reactions.Scan(&react, &total, &sample)
|
||||||
|
var sampleDecoded []string
|
||||||
|
json.Unmarshal([]byte(sample), &sampleDecoded)
|
||||||
|
|
||||||
|
item.Reactions = append(item.Reactions, history.Reaction{
|
||||||
|
Name: react,
|
||||||
|
Total: total,
|
||||||
|
SampleUsers: sampleDecoded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
results = append(results, item)
|
results = append(results, item)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -722,13 +730,12 @@ func (mysql *MySQL) betweenTimestamps(ctx context.Context, target, correspondent
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryBuf strings.Builder
|
var queryBuf strings.Builder
|
||||||
|
|
||||||
args := make([]interface{}, 0, 7)
|
args := make([]interface{}, 0, 7)
|
||||||
if correspondent == "" {
|
if correspondent == "" {
|
||||||
fmt.Fprintf(&queryBuf, "SELECT history.data from history WHERE target = ? ")
|
fmt.Fprintf(&queryBuf, "SELECT data, msgid FROM history WHERE target = ? ")
|
||||||
args = append(args, target)
|
args = append(args, target)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(&queryBuf, "SELECT history.data from history WHERE (target = ? and sender = ?) OR (target = ? and sender = ?)")
|
fmt.Fprintf(&queryBuf, "SELECT data, msgid FROM history WHERE (target = ? and sender = ?) OR (target = ? and sender = ?)")
|
||||||
args = append(args, target, correspondent, correspondent, target)
|
args = append(args, target, correspondent, correspondent, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -737,7 +744,7 @@ func (mysql *MySQL) betweenTimestamps(ctx context.Context, target, correspondent
|
||||||
args = append(args, after.UnixNano())
|
args = append(args, after.UnixNano())
|
||||||
}
|
}
|
||||||
if !before.IsZero() {
|
if !before.IsZero() {
|
||||||
fmt.Fprintf(&queryBuf, " AND nanotime < ?")
|
fmt.Fprintf(&queryBuf, " AND nanotime <= ?")
|
||||||
args = append(args, before.UnixNano())
|
args = append(args, before.UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ergochat/ergo/irc/history"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ergochat/ergo/irc/caps"
|
"github.com/ergochat/ergo/irc/caps"
|
||||||
|
|
@ -121,8 +124,12 @@ func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMa
|
||||||
rb.AddMessage(msg)
|
rb.AddMessage(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
|
||||||
func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, isBot bool, tags map[string]string, command string, target string, message utils.SplitMessage) {
|
func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, isBot bool, tags map[string]string, command string, target string, message utils.SplitMessage) {
|
||||||
|
rb.AddSplitMessageFromClientWithReactions(fromNickMask, fromAccount, isBot, tags, command, target, message, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
||||||
|
func (rb *ResponseBuffer) AddSplitMessageFromClientWithReactions(fromNickMask string, fromAccount string, isBot bool, tags map[string]string, command string, target string, message utils.SplitMessage, reactions []history.Reaction) {
|
||||||
if message.Is512() {
|
if message.Is512() {
|
||||||
if message.Message == "" {
|
if message.Message == "" {
|
||||||
// XXX this is a TAGMSG
|
// XXX this is a TAGMSG
|
||||||
|
|
@ -153,6 +160,14 @@ func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAcc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if reactions != nil && len(reactions) >= 1 {
|
||||||
|
var text string
|
||||||
|
for _, react := range reactions {
|
||||||
|
text = strings.Join([]string{message.Msgid, react.Name, strconv.Itoa(react.Total)}, " ")
|
||||||
|
text += " " + strings.Join(react.SampleUsers, " ")
|
||||||
|
rb.Add(nil, rb.target.server.name, "REACTIONS", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb *ResponseBuffer) addEchoMessage(tags map[string]string, nickMask, accountName, command, target string, message utils.SplitMessage) {
|
func (rb *ResponseBuffer) addEchoMessage(tags map[string]string, nickMask, accountName, command, target string, message utils.SplitMessage) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue