| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- package handlers
- import (
- "context"
- "log/slog"
- "math/rand"
- "net/http"
- "strings"
- "github.com/mk6i/open-oscar-server/server/webapi/types"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // BuddyListHandler handles Web AIM API buddy list management endpoints.
- type BuddyListHandler struct {
- BuddyListManager *BuddyListManager
- Logger *slog.Logger
- FeedbagService FeedbagService
- }
- type FeedbagService interface {
- DeleteItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error)
- Query(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error)
- QueryIfModified(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error)
- RespondAuthorizeToHost(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error
- RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
- StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)
- EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) error
- UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error)
- Use(ctx context.Context, instance *state.SessionInstance) error
- }
- // AddBuddy handles GET /buddylist/addBuddy requests.
- func (h *BuddyListHandler) AddBuddy(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyName := strings.TrimSpace(r.URL.Query().Get("buddy"))
- groupName := strings.TrimSpace(r.URL.Query().Get("group"))
- if buddyName == "" {
- h.sendError(w, http.StatusBadRequest, "missing buddy parameter")
- return
- }
- if groupName == "" {
- groupName = "Buddies" // Default group
- }
- // Add buddy to feedbag
- resultCode, buddyInfo := h.addBuddyToFeedbag(ctx, session, buddyName, groupName)
- // Prepare response
- responseData := map[string]any{
- "resultCode": resultCode,
- }
- if resultCode == "success" {
- responseData["buddyInfo"] = buddyInfo
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = responseData
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- groups, err := h.BuddyListManager.GetBuddyListForUser(ctx, session)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to get buddy list for event", "err", err.Error())
- } else {
- blPayload := map[string]any{"groups": groups}
- session.EventQueue.Push(types.EventTypeBuddyList, blPayload)
- }
- }
- h.Logger.InfoContext(ctx, "buddy added",
- "aimsid", aimsid,
- "buddy", buddyName,
- "group", groupName,
- "result", resultCode,
- )
- }
- // AddGroup handles GET /buddylist/addGroup requests.
- func (h *BuddyListHandler) AddGroup(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- groupName := strings.TrimSpace(r.URL.Query().Get("group"))
- if groupName == "" {
- h.sendError(w, http.StatusBadRequest, "missing group parameter")
- return
- }
- resultCode := h.addGroupToFeedbag(ctx, session, groupName)
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- groups, err := h.BuddyListManager.GetBuddyListForUser(ctx, session)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to get buddy list for event", "err", err.Error())
- } else {
- blPayload := map[string]any{"groups": groups}
- session.EventQueue.Push(types.EventTypeBuddyList, blPayload)
- }
- }
- h.Logger.InfoContext(ctx, "buddy list group added",
- "aimsid", aimsid,
- "group", groupName,
- "result", resultCode,
- )
- }
- func (h *BuddyListHandler) addGroupToFeedbag(ctx context.Context, sess *state.WebAPISession, groupName string) string {
- // A session sees no SNAC for its own feedbag writes, so it drops the alias
- // cache itself. See WebAPISession.InvalidateAliases.
- defer sess.InvalidateAliases()
- frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery}
- snac, err := h.FeedbagService.Query(ctx, sess.OSCARSession, frame)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to retrieve feedbag", "err", err.Error())
- return "error"
- }
- reply, ok := snac.Body.(wire.SNAC_0x13_0x06_FeedbagReply)
- if !ok {
- return "error"
- }
- fl := state.NewFeedbagList(reply.Items, rand.Intn)
- fl.AddGroup(groupName)
- pending := fl.PendingUpdates()
- if len(pending) == 0 {
- return "alreadyExists"
- }
- insertFrame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem}
- if _, err := h.FeedbagService.UpsertItem(ctx, sess.OSCARSession, insertFrame, pending); err != nil {
- h.Logger.ErrorContext(ctx, "failed to add group", "err", err.Error())
- return "error"
- }
- return "success"
- }
- // feedbagGroupMatchesRequested returns true if a feedbag group row matches the
- // group the Web client asked for. OSCAR often stores the default group with an
- // empty name; GetBuddyListForUser labels that as "Buddies", so addBuddy must
- // treat "" and "Buddies" as the same bucket when the client sends group=Buddies.
- func feedbagGroupMatchesRequested(storedName, requested string) bool {
- req := strings.TrimSpace(requested)
- st := strings.TrimSpace(storedName)
- if strings.EqualFold(st, req) {
- return true
- }
- if strings.EqualFold(strings.TrimSpace(req), "Buddies") && st == "" {
- return true
- }
- return false
- }
- // storedGroupNameForRequest returns the feedbag group row Name for a Web client group label.
- // Rows with GroupID 0 are the root order record, not a named buddy group.
- func storedGroupNameForRequest(items []wire.FeedbagItem, requested string) (string, bool) {
- for _, item := range items {
- if item.ClassID != wire.FeedbagClassIdGroup {
- continue
- }
- if item.GroupID == 0 {
- continue
- }
- if feedbagGroupMatchesRequested(item.Name, requested) {
- return item.Name, true
- }
- }
- return "", false
- }
- // RemoveBuddy handles GET /buddylist/removeBuddy requests.
- func (h *BuddyListHandler) RemoveBuddy(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyName := strings.TrimSpace(r.URL.Query().Get("buddy"))
- groupName := strings.TrimSpace(r.URL.Query().Get("group"))
- allGroupsParam := r.URL.Query().Get("allGroups")
- allGroups := allGroupsParam == "true" || allGroupsParam == "1"
- if buddyName == "" {
- h.sendError(w, http.StatusBadRequest, "missing buddy parameter")
- return
- }
- resultCode, rmErr := h.BuddyListManager.RemoveBuddyFromFeedbag(ctx, session, buddyName, groupName, allGroups)
- if rmErr != nil {
- h.Logger.ErrorContext(ctx, "remove buddy failed", "err", rmErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- groups, err := h.BuddyListManager.GetBuddyListForUser(ctx, session)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to get buddy list for event", "err", err.Error())
- } else {
- blPayload := map[string]any{"groups": groups}
- session.EventQueue.Push(types.EventTypeBuddyList, blPayload)
- }
- }
- h.Logger.InfoContext(ctx, "buddy removed",
- "aimsid", aimsid,
- "buddy", buddyName,
- "group", groupName,
- "result", resultCode,
- )
- }
- // todo don't remove empty group?
- // RemoveGroup handles GET /buddylist/removeGroup requests.
- func (h *BuddyListHandler) RemoveGroup(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- groupName := strings.TrimSpace(r.URL.Query().Get("group"))
- if groupName == "" {
- h.sendError(w, http.StatusBadRequest, "missing group parameter")
- return
- }
- resultCode, rmErr := h.BuddyListManager.RemoveGroupFromFeedbag(ctx, session, groupName)
- if rmErr != nil {
- h.Logger.ErrorContext(ctx, "remove group failed", "err", rmErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- groups, err := h.BuddyListManager.GetBuddyListForUser(ctx, session)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to get buddy list for event", "err", err.Error())
- } else {
- blPayload := map[string]any{"groups": groups}
- session.EventQueue.Push(types.EventTypeBuddyList, blPayload)
- }
- }
- h.Logger.InfoContext(ctx, "buddy list group removed",
- "aimsid", aimsid,
- "group", groupName,
- "result", resultCode,
- )
- }
- // addBuddyToFeedbag adds a buddy to the user's feedbag.
- func (h *BuddyListHandler) addBuddyToFeedbag(ctx context.Context, sess *state.WebAPISession, buddyName, groupName string) (string, *BuddyPresenceInfo) {
- defer sess.InvalidateAliases()
- // Retrieve current feedbag
- frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery}
- snac, err := h.FeedbagService.Query(ctx, sess.OSCARSession, frame)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to retrieve feedbag", "err", err.Error())
- return "error", nil
- }
- reply, ok := snac.Body.(wire.SNAC_0x13_0x06_FeedbagReply)
- if !ok {
- // todo what
- return "error", nil
- }
- fl := state.NewFeedbagList(reply.Items, rand.Intn)
- fl.AddGroup(groupName)
- if pending := fl.PendingUpdates(); len(pending) > 0 {
- frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem}
- if _, err := h.FeedbagService.UpsertItem(ctx, sess.OSCARSession, frame, pending); err != nil {
- h.Logger.ErrorContext(ctx, "failed to add buddy", "err", err.Error())
- return "error", nil
- }
- }
- added, err := fl.AddBuddy(groupName, buddyName, "", "")
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to add buddy to feedbag", "err", err.Error())
- return "error", nil
- }
- if !added {
- return "alreadyExists", nil
- }
- if pending := fl.PendingUpdates(); len(pending) > 0 {
- buddyItems := make(map[uint16][]wire.FeedbagItem)
- for _, item := range pending {
- if item.ClassID == wire.FeedbagClassIdBuddy {
- if _, ok := buddyItems[item.GroupID]; !ok {
- buddyItems[item.GroupID] = nil
- }
- buddyItems[item.GroupID] = append(buddyItems[item.GroupID], item)
- }
- }
- for _, buddies := range buddyItems {
- frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem}
- if _, err := h.FeedbagService.UpsertItem(ctx, sess.OSCARSession, frame, buddies); err != nil {
- h.Logger.ErrorContext(ctx, "failed to add buddy", "err", err.Error())
- return "error", nil
- }
- }
- for _, item := range pending { // todo why not filter buddies out of pending?
- if item.ClassID == wire.FeedbagClassIdGroup {
- frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem}
- if _, err := h.FeedbagService.UpsertItem(ctx, sess.OSCARSession, frame, []wire.FeedbagItem{item}); err != nil {
- h.Logger.ErrorContext(ctx, "failed to add buddy", "err", err.Error())
- return "error", nil
- }
- }
- }
- }
- // Get current presence for the buddy
- buddyInfo := &BuddyPresenceInfo{
- AimID: state.NewIdentScreenName(buddyName).String(),
- DisplayID: buddyName,
- State: "offline", // Default to offline
- UserType: "aim",
- }
- // TODO: Check actual presence status and update buddyInfo accordingly
- return "success", buddyInfo
- }
- // AddTempBuddy handles GET /aim/addTempBuddy requests.
- // This adds temporary buddies to the session without persisting them to the feedbag.
- // The temporary buddies are only visible for the duration of the session.
- func (h *BuddyListHandler) AddTempBuddy(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyNames := r.URL.Query()["t"]
- if len(buddyNames) == 0 {
- h.sendError(w, http.StatusBadRequest, "missing buddy names (t parameter)")
- return
- }
- // Store temporary buddies in the session
- // Note: These are not persisted to the feedbag database
- if session.TempBuddies == nil {
- session.TempBuddies = make(map[string]bool)
- }
- for _, buddyName := range buddyNames {
- buddyName = strings.TrimSpace(buddyName)
- if buddyName != "" {
- session.TempBuddies[buddyName] = true
- }
- }
- // Prepare response
- responseData := map[string]any{
- "resultCode": "success",
- "buddyNames": buddyNames,
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = responseData
- SendResponse(w, r, resp, h.Logger)
- // Do not push buddylist events for temp buddies. The Web AIM client handles
- // addTempBuddy via the API response; a buddylist event without "groups" causes
- // the client to clear the entire contact list (zC always calls clear() first).
- h.Logger.InfoContext(ctx, "temporary buddies added",
- "aimsid", aimsid,
- "buddies", buddyNames,
- "count", len(buddyNames),
- )
- }
- // RemoveTempBuddy handles GET /aim/removeTempBuddy requests.
- // This removes temporary session buddies added via addTempBuddy.
- func (h *BuddyListHandler) RemoveTempBuddy(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyNames := r.URL.Query()["t"]
- if len(buddyNames) == 0 {
- h.sendError(w, http.StatusBadRequest, "missing buddy names (t parameter)")
- return
- }
- removed := make([]string, 0, len(buddyNames))
- for _, buddyName := range buddyNames {
- buddyName = strings.TrimSpace(buddyName)
- if buddyName == "" {
- continue
- }
- if session.TempBuddies != nil {
- delete(session.TempBuddies, buddyName)
- }
- removed = append(removed, buddyName)
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": "success",
- "buddyNames": removed,
- }
- SendResponse(w, r, resp, h.Logger)
- h.Logger.InfoContext(ctx, "temporary buddies removed",
- "aimsid", aimsid,
- "buddies", removed,
- "count", len(removed),
- )
- }
- // RenameGroup handles GET /buddylist/renameGroup requests.
- //
- // The Web AIM client calls this with oldGroup (current group name) and newGroup
- // (the requested new name). This is a stub; it does not yet rename the group in
- // the feedbag.
- func (h *BuddyListHandler) RenameGroup(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- oldGroup := strings.TrimSpace(r.URL.Query().Get("oldGroup"))
- newGroup := strings.TrimSpace(r.URL.Query().Get("newGroup"))
- if oldGroup == "" || newGroup == "" {
- h.sendError(w, http.StatusBadRequest, "missing oldGroup or newGroup parameter")
- return
- }
- resultCode, rnErr := h.BuddyListManager.RenameGroupInFeedbag(ctx, session, oldGroup, newGroup)
- if rnErr != nil {
- h.Logger.ErrorContext(ctx, "rename group failed", "err", rnErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- h.pushBuddyListEvent(ctx, session)
- }
- h.Logger.InfoContext(ctx, "buddy list group renamed",
- "aimsid", aimsid,
- "oldGroup", oldGroup,
- "newGroup", newGroup,
- "result", resultCode,
- )
- }
- // MoveBuddy handles GET /buddylist/moveBuddy requests.
- //
- // The Web AIM client calls this with buddy (the buddy to move), group (the
- // current group), and optionally newGroup (destination group) and beforeBuddy
- // (buddy to position it before). This is a stub; it does not yet move the buddy
- // in the feedbag.
- func (h *BuddyListHandler) MoveBuddy(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyName := strings.TrimSpace(r.URL.Query().Get("buddy"))
- groupName := strings.TrimSpace(r.URL.Query().Get("group"))
- newGroup := strings.TrimSpace(r.URL.Query().Get("newGroup"))
- beforeBuddy := strings.TrimSpace(r.URL.Query().Get("beforeBuddy"))
- if buddyName == "" {
- h.sendError(w, http.StatusBadRequest, "missing buddy parameter")
- return
- }
- if groupName == "" {
- h.sendError(w, http.StatusBadRequest, "missing group parameter")
- return
- }
- resultCode, mvErr := h.BuddyListManager.MoveBuddyInFeedbag(ctx, session, buddyName, groupName, newGroup, beforeBuddy)
- if mvErr != nil {
- h.Logger.ErrorContext(ctx, "move buddy failed", "err", mvErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- h.pushBuddyListEvent(ctx, session)
- }
- h.Logger.InfoContext(ctx, "buddy moved",
- "aimsid", aimsid,
- "buddy", buddyName,
- "group", groupName,
- "newGroup", newGroup,
- "beforeBuddy", beforeBuddy,
- "result", resultCode,
- )
- }
- // SetBuddyAttribute handles GET /buddylist/setBuddyAttribute requests.
- //
- // The Web AIM client calls this with t (the buddy) and friendly (the display
- // name / alias). This is a stub; it does not yet persist the attribute to the
- // feedbag.
- func (h *BuddyListHandler) SetBuddyAttribute(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- buddyName := strings.TrimSpace(r.URL.Query().Get("t"))
- friendly := strings.TrimSpace(r.URL.Query().Get("friendly"))
- if buddyName == "" {
- h.sendError(w, http.StatusBadRequest, "missing t parameter")
- return
- }
- resultCode, saErr := h.BuddyListManager.SetBuddyAttributeInFeedbag(ctx, session, buddyName, friendly)
- if saErr != nil {
- h.Logger.ErrorContext(ctx, "set buddy attribute failed", "err", saErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- h.pushBuddyListEvent(ctx, session)
- }
- h.Logger.InfoContext(ctx, "buddy attribute set",
- "aimsid", aimsid,
- "buddy", buddyName,
- "friendly", friendly,
- "result", resultCode,
- )
- }
- // SetGroupAttribute handles GET /buddylist/setGroupAttribute requests.
- //
- // The Web AIM client calls this with collapsed (the group's collapsed state)
- // and, for named groups, group. The unnamed default group omits group. This is
- // a stub; it does not yet persist the attribute to the feedbag.
- func (h *BuddyListHandler) SetGroupAttribute(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
- ctx := r.Context()
- aimsid := r.URL.Query().Get("aimsid")
- query := r.URL.Query()
- groupName := strings.TrimSpace(query.Get("group"))
- collapsedParam := strings.TrimSpace(query.Get("collapsed"))
- if collapsedParam == "" {
- h.sendError(w, http.StatusBadRequest, "missing collapsed parameter")
- return
- }
- collapsed := collapsedParam == "true" || collapsedParam == "1"
- resultCode, saErr := h.BuddyListManager.SetGroupAttributeInFeedbag(ctx, session, groupName, collapsed)
- if saErr != nil {
- h.Logger.ErrorContext(ctx, "set group attribute failed", "err", saErr.Error())
- }
- resp := BaseResponse{}
- resp.Response.StatusCode = 200
- resp.Response.StatusText = "OK"
- resp.Response.Data = map[string]any{
- "resultCode": resultCode,
- }
- SendResponse(w, r, resp, h.Logger)
- if resultCode == "success" {
- h.pushBuddyListEvent(ctx, session)
- }
- h.Logger.InfoContext(ctx, "buddy list group attribute set",
- "aimsid", aimsid,
- "group", groupName,
- "collapsed", collapsed,
- "result", resultCode,
- )
- }
- // pushBuddyListEvent refreshes the buddy list and pushes it to the session's
- // event queue so the Web client re-renders after a mutation.
- func (h *BuddyListHandler) pushBuddyListEvent(ctx context.Context, session *state.WebAPISession) {
- groups, err := h.BuddyListManager.GetBuddyListForUser(ctx, session)
- if err != nil {
- h.Logger.ErrorContext(ctx, "failed to get buddy list for event", "err", err.Error())
- return
- }
- blPayload := map[string]any{"groups": groups}
- session.EventQueue.Push(types.EventTypeBuddyList, blPayload)
- }
- // sendError is a convenience method that wraps the common SendError function.
- func (h *BuddyListHandler) sendError(w http.ResponseWriter, statusCode int, message string) {
- SendError(w, statusCode, message)
- }
|