chat.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package server
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "log/slog"
  7. "sync"
  8. "time"
  9. "github.com/mkaminski/goaim/oscar"
  10. )
  11. type ChatHandler interface {
  12. ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)
  13. }
  14. func NewChatRouter(logger *slog.Logger) ChatRouter {
  15. return ChatRouter{
  16. ChatHandler: ChatService{},
  17. RouteLogger: RouteLogger{
  18. Logger: logger,
  19. },
  20. }
  21. }
  22. type ChatRouter struct {
  23. ChatHandler
  24. RouteLogger
  25. }
  26. func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
  27. switch SNACFrame.SubGroup {
  28. case oscar.ChatChannelMsgToHost:
  29. inSNAC := oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{}
  30. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  31. return err
  32. }
  33. outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, chatSessMgr, inSNAC)
  34. if err != nil {
  35. return err
  36. }
  37. if outSNAC == nil {
  38. return nil
  39. }
  40. rt.Logger.InfoContext(ctx, "user sent a chat message")
  41. rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
  42. return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
  43. default:
  44. return ErrUnsupportedSubGroup
  45. }
  46. }
  47. type ChatService struct {
  48. }
  49. func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
  50. snacFrameOut := oscar.SnacFrame{
  51. FoodGroup: oscar.CHAT,
  52. SubGroup: oscar.ChatChannelMsgToClient,
  53. }
  54. snacPayloadOut := oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  55. Cookie: snacPayloadIn.Cookie,
  56. Channel: snacPayloadIn.Channel,
  57. TLVRestBlock: oscar.TLVRestBlock{
  58. TLVList: snacPayloadIn.TLVList,
  59. },
  60. }
  61. snacPayloadOut.AddTLV(
  62. oscar.NewTLV(oscar.ChatTLVSenderInformation, oscar.TLVUserInfo{
  63. ScreenName: sess.ScreenName(),
  64. WarningLevel: sess.Warning(),
  65. TLVBlock: oscar.TLVBlock{
  66. TLVList: sess.UserInfo(),
  67. },
  68. }),
  69. )
  70. // send message to all the participants except sender
  71. chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
  72. SnacFrame: snacFrameOut,
  73. SnacOut: snacPayloadOut,
  74. })
  75. var ret *oscar.XMessage
  76. if _, ackMsg := snacPayloadIn.GetTLV(oscar.ChatTLVEnableReflectionFlag); ackMsg {
  77. // reflect the message back to the sender
  78. ret = &oscar.XMessage{
  79. SnacFrame: snacFrameOut,
  80. SnacOut: snacPayloadOut,
  81. }
  82. }
  83. return ret, nil
  84. }
  85. func SetOnlineChatUsers(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager) {
  86. snacPayloadOut := oscar.SNAC_0x0E_0x03_ChatUsersJoined{}
  87. sessions := chatSessMgr.Participants()
  88. for _, uSess := range sessions {
  89. snacPayloadOut.Users = append(snacPayloadOut.Users, oscar.TLVUserInfo{
  90. ScreenName: uSess.ScreenName(),
  91. WarningLevel: uSess.Warning(),
  92. TLVBlock: oscar.TLVBlock{
  93. TLVList: uSess.UserInfo(),
  94. },
  95. })
  96. }
  97. chatSessMgr.SendToScreenName(ctx, sess.ScreenName(), oscar.XMessage{
  98. SnacFrame: oscar.SnacFrame{
  99. FoodGroup: oscar.CHAT,
  100. SubGroup: oscar.ChatUsersJoined,
  101. },
  102. SnacOut: snacPayloadOut,
  103. })
  104. }
  105. func AlertUserJoined(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager) {
  106. chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
  107. SnacFrame: oscar.SnacFrame{
  108. FoodGroup: oscar.CHAT,
  109. SubGroup: oscar.ChatUsersJoined,
  110. },
  111. SnacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
  112. Users: []oscar.TLVUserInfo{
  113. {
  114. ScreenName: sess.ScreenName(),
  115. WarningLevel: sess.Warning(),
  116. TLVBlock: oscar.TLVBlock{
  117. TLVList: sess.UserInfo(),
  118. },
  119. },
  120. },
  121. },
  122. })
  123. }
  124. func AlertUserLeft(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager) {
  125. chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
  126. SnacFrame: oscar.SnacFrame{
  127. FoodGroup: oscar.CHAT,
  128. SubGroup: oscar.ChatUsersLeft,
  129. },
  130. SnacOut: oscar.SNAC_0x0E_0x04_ChatUsersLeft{
  131. Users: []oscar.TLVUserInfo{
  132. {
  133. ScreenName: sess.ScreenName(),
  134. WarningLevel: sess.Warning(),
  135. TLVBlock: oscar.TLVBlock{
  136. TLVList: sess.UserInfo(),
  137. },
  138. },
  139. },
  140. },
  141. })
  142. }
  143. func SendChatRoomInfoUpdate(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager, room ChatRoom) {
  144. chatSessMgr.SendToScreenName(ctx, sess.ScreenName(), oscar.XMessage{
  145. SnacFrame: oscar.SnacFrame{
  146. FoodGroup: oscar.CHAT,
  147. SubGroup: oscar.ChatRoomInfoUpdate,
  148. },
  149. SnacOut: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  150. Exchange: 4,
  151. Cookie: room.Cookie,
  152. InstanceNumber: 100,
  153. DetailLevel: 2,
  154. TLVBlock: oscar.TLVBlock{
  155. TLVList: room.TLVList(),
  156. },
  157. },
  158. })
  159. }
  160. type ChatRoom struct {
  161. CreateTime time.Time
  162. DetailLevel uint8
  163. Exchange uint16
  164. Cookie string
  165. InstanceNumber uint16
  166. Name string
  167. }
  168. func (c ChatRoom) TLVList() []oscar.TLV {
  169. return []oscar.TLV{
  170. oscar.NewTLV(0x00c9, uint16(15)),
  171. oscar.NewTLV(0x00ca, uint32(c.CreateTime.Unix())),
  172. oscar.NewTLV(0x00d1, uint16(1024)),
  173. oscar.NewTLV(0x00d2, uint16(100)),
  174. oscar.NewTLV(0x00d5, uint8(2)),
  175. oscar.NewTLV(0x006a, c.Name),
  176. oscar.NewTLV(0x00d3, c.Name),
  177. }
  178. }
  179. type ChatRegistry struct {
  180. chatRoomStore map[string]ChatRoom
  181. smStore map[string]ChatSessionManager
  182. mapMutex sync.RWMutex
  183. }
  184. func NewChatRegistry() *ChatRegistry {
  185. return &ChatRegistry{
  186. chatRoomStore: make(map[string]ChatRoom),
  187. smStore: make(map[string]ChatSessionManager),
  188. }
  189. }
  190. func (c *ChatRegistry) Register(room ChatRoom, sm ChatSessionManager) {
  191. c.mapMutex.Lock()
  192. defer c.mapMutex.Unlock()
  193. c.chatRoomStore[room.Cookie] = room
  194. c.smStore[room.Cookie] = sm
  195. }
  196. func (c *ChatRegistry) Retrieve(chatID string) (ChatRoom, ChatSessionManager, error) {
  197. c.mapMutex.RLock()
  198. defer c.mapMutex.RUnlock()
  199. cr, found := c.chatRoomStore[chatID]
  200. if !found {
  201. return ChatRoom{}, nil, errors.New("unable to find chat room")
  202. }
  203. sm, found := c.smStore[chatID]
  204. if !found {
  205. panic("unable to find session manager for chat")
  206. }
  207. return cr, sm, nil
  208. }
  209. func (c *ChatRegistry) MaybeRemoveRoom(chatID string) {
  210. c.mapMutex.Lock()
  211. defer c.mapMutex.Unlock()
  212. sm, found := c.smStore[chatID]
  213. if found && sm.Empty() {
  214. delete(c.chatRoomStore, chatID)
  215. delete(c.smStore, chatID)
  216. }
  217. }