feedbag.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package handler
  2. import (
  3. "context"
  4. "github.com/mkaminski/goaim/oscar"
  5. "github.com/mkaminski/goaim/server"
  6. "time"
  7. )
  8. func NewFeedbagService(sm server.SessionManager, fm server.FeedbagManager) *FeedbagService {
  9. return &FeedbagService{sm: sm, fm: fm}
  10. }
  11. type FeedbagService struct {
  12. sm server.SessionManager
  13. fm server.FeedbagManager
  14. }
  15. func (s FeedbagService) RightsQueryHandler(context.Context) oscar.XMessage {
  16. return oscar.XMessage{
  17. SnacFrame: oscar.SnacFrame{
  18. FoodGroup: oscar.FEEDBAG,
  19. SubGroup: oscar.FeedbagRightsReply,
  20. },
  21. SnacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
  22. TLVRestBlock: oscar.TLVRestBlock{
  23. TLVList: oscar.TLVList{
  24. oscar.NewTLV(0x03, uint16(200)),
  25. oscar.NewTLV(0x04, []uint16{
  26. 0x3D,
  27. 0x3D,
  28. 0x64,
  29. 0x64,
  30. 0x01,
  31. 0x01,
  32. 0x32,
  33. 0x00,
  34. 0x00,
  35. 0x03,
  36. 0x00,
  37. 0x00,
  38. 0x00,
  39. 0x80,
  40. 0xFF,
  41. 0x14,
  42. 0xC8,
  43. 0x01,
  44. 0x00,
  45. 0x01,
  46. 0x00,
  47. }),
  48. oscar.NewTLV(0x05, uint16(200)),
  49. oscar.NewTLV(0x06, uint16(200)),
  50. oscar.NewTLV(0x07, uint16(200)),
  51. oscar.NewTLV(0x08, uint16(200)),
  52. oscar.NewTLV(0x09, uint16(200)),
  53. oscar.NewTLV(0x0A, uint16(200)),
  54. oscar.NewTLV(0x0C, uint16(200)),
  55. oscar.NewTLV(0x0D, uint16(200)),
  56. oscar.NewTLV(0x0E, uint16(100)),
  57. },
  58. },
  59. },
  60. }
  61. }
  62. func (s FeedbagService) QueryHandler(_ context.Context, sess *server.Session) (oscar.XMessage, error) {
  63. fb, err := s.fm.Retrieve(sess.ScreenName())
  64. if err != nil {
  65. return oscar.XMessage{}, err
  66. }
  67. lm := time.UnixMilli(0)
  68. if len(fb) > 0 {
  69. lm, err = s.fm.LastModified(sess.ScreenName())
  70. if err != nil {
  71. return oscar.XMessage{}, err
  72. }
  73. }
  74. return oscar.XMessage{
  75. SnacFrame: oscar.SnacFrame{
  76. FoodGroup: oscar.FEEDBAG,
  77. SubGroup: oscar.FeedbagReply,
  78. },
  79. SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  80. Version: 0,
  81. Items: fb,
  82. LastUpdate: uint32(lm.Unix()),
  83. },
  84. }, nil
  85. }
  86. func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
  87. fb, err := s.fm.Retrieve(sess.ScreenName())
  88. if err != nil {
  89. return oscar.XMessage{}, err
  90. }
  91. lm := time.UnixMilli(0)
  92. if len(fb) > 0 {
  93. lm, err = s.fm.LastModified(sess.ScreenName())
  94. if err != nil {
  95. return oscar.XMessage{}, err
  96. }
  97. if lm.Before(time.Unix(int64(snacPayloadIn.LastUpdate), 0)) {
  98. return oscar.XMessage{
  99. SnacFrame: oscar.SnacFrame{
  100. FoodGroup: oscar.FEEDBAG,
  101. SubGroup: oscar.FeedbagReplyNotModified,
  102. },
  103. SnacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  104. LastUpdate: uint32(lm.Unix()),
  105. Count: uint8(len(fb)),
  106. },
  107. }, nil
  108. }
  109. }
  110. return oscar.XMessage{
  111. SnacFrame: oscar.SnacFrame{
  112. FoodGroup: oscar.FEEDBAG,
  113. SubGroup: oscar.FeedbagReply,
  114. },
  115. SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  116. Version: 0,
  117. Items: fb,
  118. LastUpdate: uint32(lm.Unix()),
  119. },
  120. }, nil
  121. }
  122. func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error) {
  123. for _, item := range snacPayloadIn.Items {
  124. // don't let users block themselves, it causes the AIM client to go
  125. // into a weird state.
  126. if item.ClassID == 3 && item.Name == sess.ScreenName() {
  127. return oscar.XMessage{
  128. SnacFrame: oscar.SnacFrame{
  129. FoodGroup: oscar.FEEDBAG,
  130. SubGroup: oscar.FeedbagErr,
  131. },
  132. SnacOut: oscar.SnacError{
  133. Code: oscar.ErrorCodeNotSupportedByHost,
  134. },
  135. }, nil
  136. }
  137. }
  138. if err := s.fm.Upsert(sess.ScreenName(), snacPayloadIn.Items); err != nil {
  139. return oscar.XMessage{}, nil
  140. }
  141. for _, item := range snacPayloadIn.Items {
  142. switch item.ClassID {
  143. case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit: // add new buddy
  144. unicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
  145. case oscar.FeedbagClassIDDeny: // block buddy
  146. // notify this user that buddy is offline
  147. unicastDeparture(ctx, item.Name, sess.ScreenName(), s.sm)
  148. // notify former buddy that this user is offline
  149. unicastDeparture(ctx, sess.ScreenName(), item.Name, s.sm)
  150. }
  151. }
  152. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  153. for range snacPayloadIn.Items {
  154. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
  155. }
  156. return oscar.XMessage{
  157. SnacFrame: oscar.SnacFrame{
  158. FoodGroup: oscar.FEEDBAG,
  159. SubGroup: oscar.FeedbagStatus,
  160. },
  161. SnacOut: snacPayloadOut,
  162. }, nil
  163. }
  164. func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error) {
  165. if err := s.fm.Upsert(sess.ScreenName(), snacPayloadIn.Items); err != nil {
  166. return oscar.XMessage{}, nil
  167. }
  168. for _, item := range snacPayloadIn.Items {
  169. switch item.ClassID {
  170. case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit:
  171. unicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
  172. }
  173. }
  174. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  175. for range snacPayloadIn.Items {
  176. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
  177. }
  178. return oscar.XMessage{
  179. SnacFrame: oscar.SnacFrame{
  180. FoodGroup: oscar.FEEDBAG,
  181. SubGroup: oscar.FeedbagStatus,
  182. },
  183. SnacOut: snacPayloadOut,
  184. }, nil
  185. }
  186. func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error) {
  187. if err := s.fm.Delete(sess.ScreenName(), snacPayloadIn.Items); err != nil {
  188. return oscar.XMessage{}, err
  189. }
  190. for _, item := range snacPayloadIn.Items {
  191. if item.ClassID == oscar.FeedbagClassIDDeny {
  192. unicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
  193. unicastArrival(ctx, sess.ScreenName(), item.Name, s.sm)
  194. }
  195. }
  196. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  197. for range snacPayloadIn.Items {
  198. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000) // success by default
  199. }
  200. return oscar.XMessage{
  201. SnacFrame: oscar.SnacFrame{
  202. FoodGroup: oscar.FEEDBAG,
  203. SubGroup: oscar.FeedbagStatus,
  204. },
  205. SnacOut: snacPayloadOut,
  206. }, nil
  207. }
  208. // StartClusterHandler exists to capture the SNAC input in unit tests to verify
  209. // it's correctly unmarshalled.
  210. func (s FeedbagService) StartClusterHandler(context.Context, oscar.SNAC_0x13_0x11_FeedbagStartCluster) {
  211. }