preference.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. package handlers
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "math/rand"
  7. "net/http"
  8. "strings"
  9. "github.com/mk6i/open-oscar-server/server/webapi/types"
  10. "github.com/mk6i/open-oscar-server/state"
  11. "github.com/mk6i/open-oscar-server/wire"
  12. )
  13. // PreferenceHandler handles Web AIM API preference-related endpoints.
  14. type PreferenceHandler struct {
  15. FeedbagService FeedbagService
  16. SessionManager *state.WebAPISessionManager
  17. Logger *slog.Logger
  18. }
  19. // webBuddyPrefs maps Web AIM API preference names to OSCAR buddy-pref bit
  20. // numbers, which are stored as a bitmask in the user's feedbag (see
  21. // wire.BuddyPref). Default values for absent prefs are owned by wire.BuddyPref,
  22. // not here.
  23. var webBuddyPrefs = map[string]uint16{
  24. "displayLogin": wire.FeedbagBuddyPrefsDisplayLogin,
  25. "displayEBuddy": wire.FeedbagBuddyPrefsDisplayEBuddy,
  26. "playEnter": wire.FeedbagBuddyPrefsPlayEnter,
  27. "playExit": wire.FeedbagBuddyPrefsPlayExit,
  28. "viewIMTimestamps": wire.FeedbagBuddyPrefsViewIMStamp,
  29. "viewSmilies": wire.FeedbagBuddyPrefsViewSmileys,
  30. "acceptIcons": wire.FeedbagBuddyPrefsAcceptIcons,
  31. "knockNonAOLIMs": wire.FeedbagBuddyPrefsKnockNonAOLIMs,
  32. "knockNonListIMs": wire.FeedbagBuddyPrefsKnockNonListIMs,
  33. "discloseIdle": wire.FeedbagBuddyPrefsDiscloseIdle,
  34. "acceptCustomBart": wire.FeedbagBuddyPrefsAcceptCustomBart,
  35. "acceptNonListBart": wire.FeedbagBuddyPrefsAcceptNonListBart,
  36. "acceptBgs": wire.FeedbagBuddyPrefsAcceptBgs,
  37. "acceptChromes": wire.FeedbagBuddyPrefsAcceptChromes,
  38. "acceptBLSounds": wire.FeedbagBuddyPrefsAcceptBLSounds,
  39. "acceptIMsounds": wire.FeedbagBuddyPrefsAcceptIMSounds,
  40. "noSeeRecentBuddies": wire.FeedbagBuddyPrefsNoSeeRecentBuddies,
  41. "acceptSMSLegal": wire.FeedbagBuddyPrefsAcceptSMSLegal,
  42. "enterDoesCRLF": wire.FeedbagBuddyPrefsEnterDoesCRLF,
  43. "playIMSound": wire.FeedbagBuddyPrefsPlayIMSound,
  44. "discloseTyping": wire.FeedbagBuddyPrefsDiscloseTyping,
  45. "acceptSuperIcons": wire.FeedbagBuddyPrefsAcceptSuperIcons,
  46. "acceptBLRichText": wire.FeedbagBuddyPrefsAcceptBLRichText,
  47. "reduceIMSound": wire.FeedbagBuddyPrefsReduceIMSound,
  48. "confirmDirectIM": wire.FeedbagBuddyPrefsConfirmDirectIM,
  49. "oneTabbedIMWindow": wire.FeedbagBuddyPrefsOneTabbedIMWindow,
  50. "buddyInfoOnMouseover": wire.FeedbagBuddyPrefsBuddyInfoOnMouseover,
  51. "discloseBuddyMatches": wire.FeedbagBuddyPrefsDiscloseBuddyMatches,
  52. "catchIMs": wire.FeedbagBuddyPrefsCatchIMs,
  53. "showFriendlyName": wire.FeedbagBuddyPrefsShowFriendlyName,
  54. "discloseRadio": wire.FeedbagBuddyPrefsDiscloseRadio,
  55. "showCapabilities": wire.FeedbagBuddyPrefsShowCapabilities,
  56. "showBuddyListFilter": wire.FeedbagBuddyPrefsShowBuddyListFilter,
  57. "showAwayIdle": wire.FeedbagBuddyPrefsShowAwayIdle,
  58. "showMobile": wire.FeedbagBuddyPrefsShowMobile,
  59. "sortBuddyList": wire.FeedbagBuddyPrefsSortBuddyList,
  60. "catchIMsForClient": wire.FeedbagBuddyPrefsCatchIMsForClient,
  61. "newMessageSmallNotification": wire.FeedbagBuddyPrefsNewMessageSmallNotify,
  62. "noFrequentBuddies": wire.FeedbagBuddyPrefsNoFrequentBuddies,
  63. "blogAwayMessages": wire.FeedbagBuddyPrefsBlogAwayMessages,
  64. "blogAIMSigMessages": wire.FeedbagBuddyPrefsBlogAIMSigMessages,
  65. "blogNoComments": wire.FeedbagBuddyPrefsBlogNoComments,
  66. "friendOfFriend": wire.FeedbagBuddyPrefsFriendOfFriend,
  67. "friendGetContactList": wire.FeedbagBuddyPrefsFriendGetContactList,
  68. "compadInit": wire.FeedbagBuddyPrefsCompadInit,
  69. "sendBuddyFeed": wire.FeedbagBuddyPrefsSendBuddyFeed,
  70. "blkSendIMWhileAway": wire.FeedbagBuddyPrefsBlkSendIMWhileAway,
  71. "showBuddyFeed": wire.FeedbagBuddyPrefsShowBuddyFeed,
  72. "noSaveVanityInfo": wire.FeedbagBuddyPrefsNoSaveVanityInfo,
  73. "acceptOffLineIM": wire.FeedbagBuddyPrefsAcceptOfflineIM,
  74. "showGroups": wire.FeedbagBuddyPrefsShowGroups,
  75. "sortGroup": wire.FeedbagBuddyPrefsSortGroup,
  76. "showOffLineBuddies": wire.FeedbagBuddyPrefsShowOfflineBuddies,
  77. "expandBuddies": wire.FeedbagBuddyPrefsExpandBuddies,
  78. "thirdPartyFeeds": wire.FeedbagBuddyPrefsThirdPartyFeeds,
  79. "notifyReceivedInvite": wire.FeedbagBuddyPrefsNotifyReceivedInvite,
  80. "apfAutoAccept": wire.FeedbagBuddyPrefsApfAutoAccept,
  81. "apfAutoAcceptBuddy": wire.FeedbagBuddyPrefsApfAutoAcceptBuddy,
  82. "blockAwayMsgFeed": wire.FeedbagBuddyPrefsBlockAwayMsgFeed,
  83. "blockAIMProfileFeed": wire.FeedbagBuddyPrefsBlockAIMProfileFeed,
  84. "blockAIMPagesFeed": wire.FeedbagBuddyPrefsBlockAIMPagesFeed,
  85. "blockJournalsFeed": wire.FeedbagBuddyPrefsBlockJournalsFeed,
  86. "blockLocationFeed": wire.FeedbagBuddyPrefsBlockLocationFeed,
  87. "blockStickiesFeed": wire.FeedbagBuddyPrefsBlockStickiesFeed,
  88. "blockUncutFeed": wire.FeedbagBuddyPrefsBlockUncutFeed,
  89. "blockLinksFeed": wire.FeedbagBuddyPrefsBlockLinksFeed,
  90. "blockAIMBulletinFeed": wire.FeedbagBuddyPrefsBlockAIMBulletinFeed,
  91. "saveStatusMsg": wire.FeedbagBuddyPrefsSaveStatusMsg,
  92. // Not in the spec Preferences enum, but sent by the web client.
  93. "apfNotifyReceivedInviteByEmail": wire.FeedbagBuddyPrefsApfNotifyReceivedByEmail,
  94. "showOfflineGrp": wire.FeedbagBuddyPrefsShowOfflineGrp,
  95. "offlineGrpCollapsed": wire.FeedbagBuddyPrefsOfflineGrpCollapsed,
  96. "firstImSoundOnly": wire.FeedbagBuddyPrefsFirstIMSoundOnly,
  97. "imblastInviteNotify": wire.FeedbagBuddyPrefsImblastInviteNotify,
  98. // Web-client-only preferences with no OSCAR buddy-pref equivalent. OSCAR
  99. // defines prefs through 0x4B, so we persist these in the same feedbag
  100. // buddy-prefs bitmask at positions above that range; no real OSCAR client
  101. // reads or writes these bits.
  102. "viewIMsInBubbles": wire.FeedbagBuddyPrefsViewIMsInBubbles,
  103. "viewIMTimestampsRelative": wire.FeedbagBuddyPrefsViewIMTimestampsRelative,
  104. "globalOTR": wire.FeedbagBuddyPrefsGlobalOTR,
  105. "imblastInviteFromBuddyOnly": wire.FeedbagBuddyPrefsImblastInviteFromBuddyOnly,
  106. }
  107. // PermitDenyData contains permit/deny list information.
  108. type PermitDenyData struct {
  109. PDMode string `json:"pdMode" xml:"pdMode"`
  110. PermitList []string `json:"allows,omitempty" xml:"allows>user,omitempty"`
  111. DenyList []string `json:"blocks,omitempty" xml:"blocks>user,omitempty"`
  112. }
  113. // SetPreferences handles GET /preference/set requests to update user preferences.
  114. func (h *PreferenceHandler) SetPreferences(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
  115. ctx := r.Context()
  116. // Preferences are stored as OSCAR buddy prefs in the feedbag, which requires
  117. // an OSCAR session to act on behalf of.
  118. instance := session.OSCARSession
  119. // Read-modify-write the buddy-prefs item so bits the web client doesn't
  120. // manage (e.g. the typing-events bit consumed by the OSCAR session) survive.
  121. item, err := buddyPrefsItem(ctx, h.FeedbagService, instance)
  122. if err != nil {
  123. h.Logger.ErrorContext(ctx, "failed to retrieve feedbag", "err", err.Error())
  124. h.sendError(w, http.StatusInternalServerError, "failed to retrieve feedbag")
  125. return
  126. }
  127. applied := make(map[string]interface{})
  128. for name, pref := range webBuddyPrefs {
  129. val := r.URL.Query().Get(name)
  130. if val == "" {
  131. continue
  132. }
  133. on := parseBoolPref(val)
  134. item.TLVList = wire.SetBuddyPref(item.TLVList, pref, on)
  135. applied[name] = boolToPrefInt(on)
  136. }
  137. if len(applied) > 0 {
  138. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem}
  139. if _, err := h.FeedbagService.UpsertItem(ctx, instance, frame, []wire.FeedbagItem{item}); err != nil {
  140. h.Logger.ErrorContext(ctx, "failed to set preferences", "err", err.Error())
  141. h.sendError(w, http.StatusInternalServerError, "failed to save preferences")
  142. return
  143. }
  144. // Notify the client's open windows via the event stream so display
  145. // changes (e.g. bubbles/classic) take effect immediately without a
  146. // browser refresh.
  147. session.EventQueue.Push(types.EventTypePreference, applied)
  148. }
  149. h.Logger.DebugContext(ctx, "preferences updated",
  150. "screenName", session.ScreenName.String(),
  151. "prefCount", len(applied),
  152. )
  153. // Send success response
  154. response := BaseResponse{}
  155. response.Response.StatusCode = 200
  156. response.Response.StatusText = "OK"
  157. response.Response.Data = applied
  158. SendResponse(w, r, response, h.Logger)
  159. }
  160. // GetPreferences handles GET /preference/get requests to retrieve user preferences.
  161. func (h *PreferenceHandler) GetPreferences(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
  162. ctx := r.Context()
  163. // Load the buddy-prefs bitmask from the feedbag. Absent prefs fall back to
  164. // the spec default.
  165. var prefsList wire.TLVList
  166. if item, err := buddyPrefsItem(ctx, h.FeedbagService, session.OSCARSession); err != nil {
  167. h.Logger.WarnContext(ctx, "failed to get preferences", "err", err.Error())
  168. } else {
  169. prefsList = item.TLVList
  170. }
  171. // When specific preferences are named in the query (e.g. playIMSound=1), the
  172. // client is selecting those; otherwise return all preferences.
  173. requestedPrefs := make(map[string]interface{})
  174. for name, pref := range webBuddyPrefs {
  175. if r.URL.Query().Has(name) {
  176. requestedPrefs[name] = effectivePrefValue(prefsList, pref)
  177. }
  178. }
  179. var prefs map[string]interface{}
  180. if len(requestedPrefs) > 0 {
  181. prefs = requestedPrefs
  182. } else {
  183. prefs = make(map[string]interface{}, len(webBuddyPrefs))
  184. for name, pref := range webBuddyPrefs {
  185. prefs[name] = effectivePrefValue(prefsList, pref)
  186. }
  187. }
  188. h.Logger.DebugContext(ctx, "preferences retrieved",
  189. "screenName", session.ScreenName.String(),
  190. "prefCount", len(prefs),
  191. "requested", len(requestedPrefs) > 0,
  192. )
  193. // AMF clients (e.g. Gromit) expect the payload shaped a specific way. Pref
  194. // values are already numeric 0/1, which is what these clients expect.
  195. format := strings.ToLower(r.URL.Query().Get("f"))
  196. if format == "amf" || format == "amf3" {
  197. // Ensure prefs is never empty for Gromit.
  198. if len(prefs) == 0 {
  199. prefs = map[string]interface{}{"playIMSound": 1}
  200. }
  201. // A single preference is returned directly; multiple are wrapped in
  202. // jsonData for Gromit compatibility.
  203. if len(prefs) != 1 {
  204. prefs = map[string]interface{}{"jsonData": prefs}
  205. }
  206. h.Logger.DebugContext(ctx, "AMF preference response",
  207. "prefCount", len(prefs),
  208. "format", format,
  209. )
  210. }
  211. // Send response in requested format
  212. response := BaseResponse{}
  213. response.Response.StatusCode = 200
  214. response.Response.StatusText = "OK"
  215. response.Response.Data = prefs
  216. SendResponse(w, r, response, h.Logger)
  217. }
  218. // buddyPrefsItem returns the user's buddy-prefs feedbag item, creating a fresh
  219. // (empty) one if the feedbag does not have it yet.
  220. func buddyPrefsItem(ctx context.Context, fs FeedbagService, instance *state.SessionInstance) (wire.FeedbagItem, error) {
  221. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery}
  222. fb, err := fs.Query(ctx, instance, frame)
  223. if err != nil {
  224. return wire.FeedbagItem{}, err
  225. }
  226. reply, ok := fb.Body.(wire.SNAC_0x13_0x06_FeedbagReply)
  227. if !ok {
  228. return wire.FeedbagItem{}, fmt.Errorf("unexpected feedbag reply type")
  229. }
  230. for _, item := range reply.Items {
  231. if item.ClassID == wire.FeedbagClassIdBuddyPrefs {
  232. return item, nil
  233. }
  234. }
  235. // No buddy-prefs item yet; create one. Only a single item of this class is
  236. // allowed, with an empty name and no group.
  237. return wire.FeedbagItem{
  238. ClassID: wire.FeedbagClassIdBuddyPrefs,
  239. ItemID: uint16(rand.Intn(0xFFFF)),
  240. }, nil
  241. }
  242. // effectiveBuddyPrefs returns the 0/1 value of every web buddy pref, using the
  243. // feedbag value when the pref's valid bit is set and the spec default otherwise.
  244. // This mirrors GetPreferences so the pushed preference event and the
  245. // preference/get endpoint agree: server-side defaults (e.g. showGroups) reach
  246. // the client even for prefs the user has never explicitly set. The web client
  247. // reads these from the startup preference event and has no other default for
  248. // them, so an omitted pref would silently fall back to the client's own hidden
  249. // default (which, for showGroups, hides group headers).
  250. func effectiveBuddyPrefs(list wire.TLVList) map[string]interface{} {
  251. prefs := make(map[string]interface{}, len(webBuddyPrefs))
  252. for name, pref := range webBuddyPrefs {
  253. prefs[name] = effectivePrefValue(list, pref)
  254. }
  255. return prefs
  256. }
  257. // effectivePrefValue returns the 0/1 value for the buddy pref prefNum, deferring
  258. // to wire.BuddyPref for both the stored value and its default. Values are emitted
  259. // as numbers (not "1"/"0" strings) because the web client evaluates them with
  260. // JavaScript truthiness/numeric comparisons, where the string "0" is truthy.
  261. func effectivePrefValue(list wire.TLVList, prefNum uint16) int {
  262. return boolToPrefInt(wire.BuddyPref(list, prefNum))
  263. }
  264. // parseBoolPref interprets a Web API preference query value as a boolean.
  265. func parseBoolPref(v string) bool {
  266. switch strings.ToLower(v) {
  267. case "1", "true", "yes", "on":
  268. return true
  269. default:
  270. return false
  271. }
  272. }
  273. func boolToPrefInt(b bool) int {
  274. if b {
  275. return 1
  276. }
  277. return 0
  278. }
  279. // SetPermitDeny handles GET /preference/setPermitDeny requests to update permit/deny settings.
  280. func (h *PreferenceHandler) SetPermitDeny(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
  281. ctx := r.Context()
  282. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery}
  283. fb, err := h.FeedbagService.Query(r.Context(), session.OSCARSession, frame)
  284. if err != nil {
  285. h.sendError(w, http.StatusInternalServerError, "failed to retrieve feedbag")
  286. return
  287. }
  288. reply, ok := fb.Body.(wire.SNAC_0x13_0x06_FeedbagReply)
  289. if !ok {
  290. h.sendError(w, http.StatusInternalServerError, "failed to retrieve feedbag")
  291. return
  292. }
  293. fl := state.NewFeedbagList(reply.Items, rand.Intn)
  294. // Get pdMode parameter
  295. pdModeStr := r.URL.Query().Get("pdMode")
  296. if pdModeStr != "" {
  297. switch pdModeStr { // todo: are the string ints possible inputs?
  298. case "permitAll", "1":
  299. fl.SetMode(uint8(wire.FeedbagPDModePermitAll))
  300. case "denyAll", "2":
  301. fl.SetMode(uint8(wire.FeedbagPDModeDenyAll))
  302. case "permitSome", "3":
  303. fl.SetMode(uint8(wire.FeedbagPDModePermitSome))
  304. case "denySome", "4":
  305. fl.SetMode(uint8(wire.FeedbagPDModeDenySome))
  306. case "permitOnList", "5":
  307. fl.SetMode(uint8(wire.FeedbagPDModePermitOnList))
  308. default:
  309. h.sendError(w, http.StatusBadRequest, "invalid pdMode value")
  310. return
  311. }
  312. }
  313. // Handle permit list updates
  314. if pdAllow := r.URL.Query().Get("pdAllow"); pdAllow != "" {
  315. users := strings.Split(pdAllow, ",")
  316. for _, user := range users {
  317. user = strings.TrimSpace(user)
  318. if user != "" {
  319. fl.PermitUser(user)
  320. }
  321. }
  322. }
  323. if pdAllowRemove := r.URL.Query().Get("pdAllowRemove"); pdAllowRemove != "" {
  324. users := strings.Split(pdAllowRemove, ",")
  325. for _, user := range users {
  326. user = strings.TrimSpace(user)
  327. if user != "" {
  328. fl.DeletePermit(user)
  329. }
  330. }
  331. }
  332. // Handle deny list updates
  333. if pdBlock := r.URL.Query().Get("pdBlock"); pdBlock != "" {
  334. users := strings.Split(pdBlock, ",")
  335. for _, user := range users {
  336. user = strings.TrimSpace(user)
  337. if user != "" {
  338. fl.DenyUser(user)
  339. }
  340. }
  341. }
  342. if pdBlockRemove := r.URL.Query().Get("pdBlockRemove"); pdBlockRemove != "" {
  343. users := strings.Split(pdBlockRemove, ",")
  344. for _, user := range users {
  345. user = strings.TrimSpace(user)
  346. if user != "" {
  347. fl.DeleteDeny(user)
  348. }
  349. }
  350. }
  351. if pending := fl.PendingUpdates(); len(pending) > 0 {
  352. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem}
  353. if _, err := h.FeedbagService.UpsertItem(ctx, session.OSCARSession, frame, pending); err != nil {
  354. h.Logger.ErrorContext(ctx, "failed to set PD mode", "err", err.Error())
  355. h.sendError(w, http.StatusInternalServerError, "failed to update PD mode")
  356. return
  357. }
  358. }
  359. if pending := fl.PendingDeletes(); len(pending) > 0 {
  360. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem}
  361. body := wire.SNAC_0x13_0x0A_FeedbagDeleteItem{Items: pending}
  362. if _, err := h.FeedbagService.DeleteItem(ctx, session.OSCARSession, frame, body); err != nil {
  363. h.Logger.ErrorContext(ctx, "failed to set PD mode", "err", err.Error())
  364. h.sendError(w, http.StatusInternalServerError, "failed to update PD mode")
  365. return
  366. }
  367. }
  368. pdd := permitDenyData(fl.Items())
  369. h.Logger.DebugContext(ctx, "permit/deny settings updated",
  370. "screenName", session.ScreenName.String(),
  371. "pdMode", pdd.PDMode,
  372. "permitCount", len(pdd.PermitList),
  373. "denyCount", len(pdd.DenyList),
  374. )
  375. response := BaseResponse{}
  376. response.Response.StatusCode = 200
  377. response.Response.StatusText = "OK"
  378. response.Response.Data = pdd
  379. SendResponse(w, r, response, h.Logger)
  380. }
  381. func permitDenyData(fl []wire.FeedbagItem) PermitDenyData {
  382. pdd := PermitDenyData{}
  383. for _, item := range fl {
  384. switch item.ClassID {
  385. case wire.FeedbagClassIDDeny:
  386. pdd.DenyList = append(pdd.DenyList, item.Name)
  387. case wire.FeedbagClassIDPermit:
  388. pdd.PermitList = append(pdd.PermitList, item.Name)
  389. case wire.FeedbagClassIdPdinfo:
  390. mode, _ := item.Uint8(wire.FeedbagAttributesPdMode)
  391. switch wire.FeedbagPDMode(mode) {
  392. case wire.FeedbagPDModePermitAll:
  393. pdd.PDMode = "permitAll"
  394. case wire.FeedbagPDModeDenyAll:
  395. pdd.PDMode = "denyAll"
  396. case wire.FeedbagPDModePermitSome:
  397. pdd.PDMode = "permitSome"
  398. case wire.FeedbagPDModeDenySome:
  399. pdd.PDMode = "denySome"
  400. case wire.FeedbagPDModePermitOnList:
  401. pdd.PDMode = "permitOnList"
  402. }
  403. }
  404. }
  405. return pdd
  406. }
  407. // GetPermitDeny handles GET /preference/getPermitDeny requests to retrieve permit/deny settings.
  408. func (h *PreferenceHandler) GetPermitDeny(w http.ResponseWriter, r *http.Request, session *state.WebAPISession) {
  409. ctx := r.Context()
  410. frame := wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery}
  411. fb, err := h.FeedbagService.Query(r.Context(), session.OSCARSession, frame)
  412. if err != nil {
  413. h.sendError(w, http.StatusInternalServerError, "failed to retrieve feedbag")
  414. return
  415. }
  416. reply, ok := fb.Body.(wire.SNAC_0x13_0x06_FeedbagReply)
  417. if !ok {
  418. h.sendError(w, http.StatusInternalServerError, "failed to retrieve feedbag")
  419. return
  420. }
  421. pdd := permitDenyData(reply.Items)
  422. h.Logger.DebugContext(ctx, "permit/deny settings retrieved",
  423. "screenName", session.ScreenName.String(),
  424. "pdMode", pdd.PDMode,
  425. "permitCount", len(pdd.PermitList),
  426. "denyCount", len(pdd.DenyList),
  427. )
  428. response := BaseResponse{}
  429. response.Response.StatusCode = 200
  430. response.Response.StatusText = "OK"
  431. response.Response.Data = pdd
  432. SendResponse(w, r, response, h.Logger)
  433. }
  434. func (h *PreferenceHandler) sendError(w http.ResponseWriter, statusCode int, message string) {
  435. // todo log
  436. SendError(w, statusCode, message)
  437. }