icq.go 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326
  1. package foodgroup
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "log/slog"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/mk6i/open-oscar-server/state"
  12. "github.com/mk6i/open-oscar-server/wire"
  13. )
  14. var errICQBadRequest = errors.New("bad ICQ request")
  15. // NewICQService creates an instance of ICQService.
  16. func NewICQService(
  17. messageRelayer MessageRelayer,
  18. finder ICQUserFinder,
  19. userUpdater ICQUserUpdater,
  20. logger *slog.Logger,
  21. sessionRetriever SessionRetriever,
  22. offlineMessageManager OfflineMessageManager,
  23. ) *ICQService {
  24. return &ICQService{
  25. messageRelayer: messageRelayer,
  26. userFinder: finder,
  27. userUpdater: userUpdater,
  28. logger: logger,
  29. sessionRetriever: sessionRetriever,
  30. offlineMessageManager: offlineMessageManager,
  31. timeNow: time.Now,
  32. forwardICQAuthEvents: func(ctx context.Context, sender state.IdentScreenName, recipient state.IdentScreenName, authMsg wire.ICBMCh4Message) error {
  33. return fmt.Errorf("no ICBMService available")
  34. },
  35. }
  36. }
  37. // ICQService provides functionality for the ICQ food group.
  38. type ICQService struct {
  39. userFinder ICQUserFinder
  40. logger *slog.Logger
  41. messageRelayer MessageRelayer
  42. sessionRetriever SessionRetriever
  43. userUpdater ICQUserUpdater
  44. timeNow func() time.Time
  45. offlineMessageManager OfflineMessageManager
  46. forwardICQAuthEvents func(ctx context.Context, sender state.IdentScreenName, recipient state.IdentScreenName, authMsg wire.ICBMCh4Message) error
  47. }
  48. // BridgeFeedbagService enables the ICBMService to forward legacy ICQ events to
  49. // the ICBM service.
  50. func (s *ICQService) BridgeFeedbagService(service *FeedbagService) {
  51. s.forwardICQAuthEvents = service.ForwardICQAuthEvents
  52. }
  53. func (s *ICQService) DeleteMsgReq(ctx context.Context, instance *state.SessionInstance, seq uint16) error {
  54. if err := s.offlineMessageManager.DeleteMessages(ctx, instance.IdentScreenName()); err != nil {
  55. return fmt.Errorf("deleting messages: %w", err)
  56. }
  57. return nil
  58. }
  59. func (s *ICQService) FindByICQName(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0515_DBQueryMetaReqSearchByDetails, seq uint16) error {
  60. res, err := s.userFinder.FindByICQName(ctx, inBody.FirstName, inBody.LastName, inBody.NickName)
  61. if err != nil {
  62. s.logger.Error("FindByICQName failed", "err", err.Error())
  63. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  64. }
  65. if len(res) == 0 {
  66. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  67. }
  68. for i := 0; i < len(res); i++ {
  69. last := i == len(res)-1
  70. resp := s.createResult(res[i])
  71. if err := s.replySearchHit(ctx, instance, resp, inFrame.RequestID, last, seq); err != nil {
  72. return err
  73. }
  74. }
  75. return nil
  76. }
  77. func (s *ICQService) FindByICQEmail(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0529_DBQueryMetaReqSearchByEmail, seq uint16) error {
  78. res, err := s.userFinder.FindByICQEmail(ctx, inBody.Email)
  79. switch {
  80. case errors.Is(err, state.ErrNoUser):
  81. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  82. case err != nil:
  83. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  84. default:
  85. return s.replySearchHit(ctx, instance, s.createResult(res), inFrame.RequestID, true, seq)
  86. }
  87. }
  88. func (s *ICQService) FindByEmail3(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0573_DBQueryMetaReqSearchByEmail3, seq uint16) error {
  89. b, hasEmail := inBody.Bytes(wire.ICQTLVTagsEmail)
  90. if !hasEmail {
  91. return errors.New("unable to get email from request")
  92. }
  93. email := wire.ICQEmail{}
  94. if err := wire.UnmarshalLE(&email, bytes.NewReader(b)); err != nil {
  95. return fmt.Errorf("unmarshal email: %w", err)
  96. }
  97. res, err := s.userFinder.FindByICQEmail(ctx, email.Email)
  98. switch {
  99. case errors.Is(err, state.ErrNoUser):
  100. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  101. case err != nil:
  102. s.logger.Error("FindByICQEmail failed", "err", err.Error())
  103. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  104. default:
  105. return s.replySearchHit(ctx, instance, s.createResult(res), inFrame.RequestID, true, seq)
  106. }
  107. }
  108. func (s *ICQService) FindByICQInterests(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0533_DBQueryMetaReqSearchWhitePages, seq uint16) error {
  109. interests := strings.Split(inBody.InterestsKeyword, ",")
  110. res, err := s.userFinder.FindByICQInterests(ctx, inBody.InterestsCode, interests)
  111. if err != nil {
  112. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  113. }
  114. if len(res) == 0 {
  115. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  116. }
  117. for i := 0; i < len(res); i++ {
  118. last := i == len(res)-1
  119. resp := s.createResult(res[i])
  120. if err := s.replySearchHit(ctx, instance, resp, inFrame.RequestID, last, seq); err != nil {
  121. return err
  122. }
  123. }
  124. return nil
  125. }
  126. func (s *ICQService) FindByWhitePages2(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x055F_DBQueryMetaReqSearchWhitePages2, seq uint16) error {
  127. var criteria state.ICQUserSearchCriteria
  128. if uin, ok := inBody.Uint32LE(wire.ICQTLVTagsUIN); ok {
  129. criteria.UIN = &uin
  130. }
  131. if sNick, ok := inBody.ICQString(wire.ICQTLVTagsNickname); ok {
  132. criteria.NickName = &sNick
  133. }
  134. if sFirst, ok := inBody.ICQString(wire.ICQTLVTagsFirstName); ok {
  135. criteria.FirstName = &sFirst
  136. }
  137. if sLast, ok := inBody.ICQString(wire.ICQTLVTagsLastName); ok {
  138. criteria.LastName = &sLast
  139. }
  140. if sEmail, ok := inBody.ICQString(wire.ICQTLVTagsEmail); ok {
  141. criteria.Email = &sEmail
  142. }
  143. if b, ok := inBody.Bytes(wire.ICQTLVTagsAgeRangeSearch); ok {
  144. var ar struct {
  145. MinAge uint16
  146. MaxAge uint16
  147. }
  148. if err := wire.UnmarshalLE(&ar, bytes.NewReader(b)); err != nil {
  149. return fmt.Errorf("unmarshaling age range: %w", err)
  150. }
  151. criteria.MinAge = &ar.MinAge
  152. criteria.MaxAge = &ar.MaxAge
  153. }
  154. if gender, ok := inBody.Uint8(wire.ICQTLVTagsGender); ok {
  155. criteria.Gender = &gender
  156. }
  157. if lang, ok := inBody.Uint8(wire.ICQTLVTagsSpokenLanguage); ok {
  158. criteria.SpokenLanguage = &lang
  159. }
  160. if city, ok := inBody.ICQString(wire.ICQTLVTagsHomeCityName); ok {
  161. criteria.City = &city
  162. }
  163. if st, ok := inBody.ICQString(wire.ICQTLVTagsHomeStateAbbr); ok {
  164. criteria.State = &st
  165. }
  166. if cc, ok := inBody.Uint16LE(wire.ICQTLVTagsHomeCountryCode); ok {
  167. criteria.CountryCode = &cc
  168. }
  169. if company, ok := inBody.ICQString(wire.ICQTLVTagsWorkCompanyName); ok {
  170. criteria.Company = &company
  171. }
  172. if departmentName, ok := inBody.ICQString(wire.ICQTLVTagsWorkDepartmentName); ok {
  173. criteria.DepartmentName = &departmentName
  174. }
  175. if position, ok := inBody.ICQString(wire.ICQTLVTagsWorkPositionTitle); ok {
  176. criteria.Position = &position
  177. }
  178. if occ, ok := inBody.Uint16LE(wire.ICQTLVTagsWorkOccupationCode); ok {
  179. criteria.OccupationCode = &occ
  180. }
  181. if b, ok := inBody.Bytes(wire.ICQTLVTagsInterestsNode); ok {
  182. var n struct {
  183. Code uint16
  184. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  185. }
  186. if err := wire.UnmarshalLE(&n, bytes.NewReader(b)); err != nil {
  187. return fmt.Errorf("unmarshaling interests node: %w", err)
  188. }
  189. if n.Keyword != "" {
  190. criteria.InterestsKeywords = strings.Split(n.Keyword, ",")
  191. criteria.InterestsCode = &n.Code
  192. }
  193. }
  194. if b, ok := inBody.Bytes(wire.ICQTLVTagsAffiliationsNode); ok {
  195. var n struct {
  196. Code uint16
  197. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  198. }
  199. if err := wire.UnmarshalLE(&n, bytes.NewReader(b)); err != nil {
  200. return fmt.Errorf("unmarshaling affiliations node: %w", err)
  201. }
  202. if n.Keyword != "" {
  203. criteria.AffiliationsKeywords = strings.Split(n.Keyword, ",")
  204. for i := range criteria.AffiliationsKeywords {
  205. criteria.AffiliationsKeywords[i] = strings.TrimSpace(criteria.AffiliationsKeywords[i])
  206. }
  207. criteria.AffiliationsCode = &n.Code
  208. }
  209. }
  210. if b, ok := inBody.Bytes(wire.ICQTLVTagsPastInfoNode); ok {
  211. var n struct {
  212. Code uint16
  213. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  214. }
  215. if err := wire.UnmarshalLE(&n, bytes.NewReader(b)); err != nil {
  216. return fmt.Errorf("unmarshaling past info node: %w", err)
  217. }
  218. if n.Keyword != "" {
  219. criteria.PastAffiliationsKeywords = strings.Split(n.Keyword, ",")
  220. for i := range criteria.PastAffiliationsKeywords {
  221. criteria.PastAffiliationsKeywords[i] = strings.TrimSpace(criteria.PastAffiliationsKeywords[i])
  222. }
  223. criteria.PastAffiliationsCode = &n.Code
  224. }
  225. }
  226. if b, ok := inBody.Bytes(wire.ICQTLVTagsHomepageCategoryKeywords); ok {
  227. var p struct {
  228. Index uint16
  229. Keywords string `oscar:"len_prefix=uint16,nullterm"`
  230. }
  231. if err := wire.UnmarshalLE(&p, bytes.NewReader(b)); err != nil {
  232. return fmt.Errorf("unmarshaling homepage category keywords: %w", err)
  233. }
  234. criteria.HomePageCategoryIndex = new(p.Index)
  235. if p.Keywords != "" {
  236. criteria.HomePageKeywords = strings.Split(p.Keywords, ",")
  237. for i := range criteria.HomePageKeywords {
  238. criteria.HomePageKeywords[i] = strings.TrimSpace(criteria.HomePageKeywords[i])
  239. }
  240. }
  241. }
  242. if kw, ok := inBody.ICQString(wire.ICQTLVTagsWhitepagesSearchKeywords); ok {
  243. criteria.AnyKeyword = &kw
  244. }
  245. onlineOnly := false
  246. if f, ok := inBody.Uint8(wire.ICQTLVTagsSearchOnlineUsersFlag); ok && f != 0 {
  247. onlineOnly = true
  248. }
  249. users, err := s.userFinder.SearchICQUsers(ctx, criteria)
  250. if err != nil {
  251. if errors.Is(err, state.ErrICQSearchEmptyCriteria) {
  252. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  253. }
  254. s.logger.Error("FindByWhitePages2 failed", "err", err.Error())
  255. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  256. }
  257. if onlineOnly {
  258. var filtered []state.User
  259. for _, u := range users {
  260. if s.sessionRetriever.RetrieveSession(u.IdentScreenName) != nil {
  261. filtered = append(filtered, u)
  262. }
  263. }
  264. users = filtered
  265. }
  266. if len(users) == 0 {
  267. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  268. }
  269. for i := 0; i < len(users); i++ {
  270. isLast := i == len(users)-1
  271. details := s.createResult(users[i])
  272. if err := s.replySearchHit(ctx, instance, details, inFrame.RequestID, isLast, seq); err != nil {
  273. return err
  274. }
  275. }
  276. return nil
  277. }
  278. func (s *ICQService) FindByUIN(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x051F_DBQueryMetaReqSearchByUIN, seq uint16) error {
  279. res, err := s.userFinder.FindByUIN(ctx, inBody.UIN)
  280. switch {
  281. case errors.Is(err, state.ErrNoUser):
  282. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  283. case err != nil:
  284. s.logger.Error("FindByUIN failed", "err", err.Error())
  285. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  286. default:
  287. return s.replySearchHit(ctx, instance, s.createResult(res), inFrame.RequestID, true, seq)
  288. }
  289. }
  290. func (s *ICQService) FindByUIN2(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0569_DBQueryMetaReqSearchByUIN2, seq uint16) error {
  291. UIN, hasUIN := inBody.Uint32LE(wire.ICQTLVTagsUIN)
  292. if !hasUIN {
  293. return errors.New("unable to get UIN from request")
  294. }
  295. res, err := s.userFinder.FindByUIN(ctx, UIN)
  296. switch {
  297. case errors.Is(err, state.ErrNoUser):
  298. return s.reportNoSearchResults(ctx, instance, seq, inFrame.RequestID)
  299. case err != nil:
  300. s.logger.Error("FindByUIN failed", "err", err.Error())
  301. return s.reportSearchFailure(ctx, instance, seq, inFrame.RequestID)
  302. default:
  303. return s.replySearchHit(ctx, instance, s.createResult(res), inFrame.RequestID, true, seq)
  304. }
  305. }
  306. func (s *ICQService) FullUserInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x051F_DBQueryMetaReqSearchByUIN, seq uint16) error {
  307. user, err := s.userFinder.FindByUIN(ctx, inBody.UIN)
  308. if err != nil {
  309. return err
  310. }
  311. if err := s.userInfo(ctx, instance, user, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  312. return err
  313. }
  314. if err := s.moreUserInfo(ctx, instance, user, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  315. return err
  316. }
  317. if err := s.extraEmails(ctx, instance, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  318. return err
  319. }
  320. if err := s.homepageCat(ctx, instance, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  321. return err
  322. }
  323. if err := s.workInfo(ctx, instance, user, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  324. return err
  325. }
  326. if err := s.notes(ctx, instance, user, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  327. return err
  328. }
  329. if err := s.interests(ctx, instance, user, inFrame.RequestID, seq, wire.SNACFlagsMoreToCome); err != nil {
  330. return err
  331. }
  332. if err := s.affiliations(ctx, instance, user, inFrame.RequestID, seq, 0); err != nil {
  333. return err
  334. }
  335. return nil
  336. }
  337. func (s *ICQService) OfflineMsgReq(ctx context.Context, inFrame wire.SNACFrame, instance *state.SessionInstance, seq uint16) error {
  338. messages, err := s.offlineMessageManager.RetrieveMessages(ctx, instance.IdentScreenName())
  339. if err != nil {
  340. return fmt.Errorf("retrieving messages: %w", err)
  341. }
  342. for _, msgIn := range messages {
  343. if msgIn.Sender.UIN() != 0 {
  344. reply := wire.ICQ_0x0041_DBQueryOfflineMsgReply{
  345. ICQMetadata: wire.ICQMetadata{
  346. UIN: instance.UIN(),
  347. ReqType: wire.ICQDBQueryOfflineMsgReply,
  348. Seq: seq,
  349. },
  350. SenderUIN: msgIn.Sender.UIN(),
  351. Year: uint16(msgIn.Sent.Year()),
  352. Month: uint8(msgIn.Sent.Month()),
  353. Day: uint8(msgIn.Sent.Day()),
  354. Hour: uint8(msgIn.Sent.Hour()),
  355. Minute: uint8(msgIn.Sent.Minute()),
  356. }
  357. switch msgIn.Message.ChannelID {
  358. case wire.ICBMChannelIM:
  359. if payload, hasIM := msgIn.Message.Bytes(wire.ICBMTLVAOLIMData); hasIM {
  360. // send regular IM
  361. msgText, err := wire.UnmarshalICBMMessageText(payload)
  362. if err != nil {
  363. return fmt.Errorf("unmarshalling offline message: %w", err)
  364. }
  365. reply.MsgType = wire.ICBMExtendedMsgTypePlain
  366. reply.Message = msgText
  367. }
  368. case wire.ICBMChannelICQ:
  369. if b, hasAuthReq := msgIn.Message.Bytes(wire.ICBMTLVData); hasAuthReq {
  370. msg := wire.ICBMCh4Message{}
  371. buf := bytes.NewBuffer(b)
  372. if err := wire.UnmarshalLE(&msg, buf); err != nil {
  373. return err
  374. }
  375. if msg.MessageType == wire.ICBMMsgTypeAuthReq ||
  376. msg.MessageType == wire.ICBMMsgTypeAuthDeny ||
  377. msg.MessageType == wire.ICBMMsgTypeAuthOK ||
  378. msg.MessageType == wire.ICBMMsgTypeAdded {
  379. if instance.Session().UsesFeedbag() {
  380. // send auth grant/deny/request SNACs instead of the legacy MSG_TYPE_*
  381. // ICQ messages.
  382. if err := s.forwardICQAuthEvents(ctx, msgIn.Sender, msgIn.Recipient, msg); err != nil {
  383. return fmt.Errorf("s.forwardICQAuthEvents: %w", err)
  384. }
  385. continue // do not send these messages in response
  386. }
  387. }
  388. reply.MsgType = msg.MessageType
  389. reply.Flags = msg.Flags
  390. reply.Message = msg.Message
  391. }
  392. }
  393. if reply.MsgType == 0 {
  394. return fmt.Errorf("did not find an appropriate saved message payload. channel: %d",
  395. msgIn.Message.ChannelID)
  396. }
  397. msgOut := wire.ICQMessageReplyEnvelope{
  398. Message: reply,
  399. }
  400. if err := s.reply(ctx, instance, msgOut, inFrame.RequestID, wire.SNACFlagsMoreToCome); err != nil {
  401. return fmt.Errorf("sending offline message: %w", err)
  402. }
  403. } else {
  404. // offline message sent by AIM client. this SNAC supports string
  405. // screen names, whereas ICQ_0x0041_DBQueryOfflineMsgReply does not.
  406. clientIM := wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  407. Cookie: msgIn.Message.Cookie,
  408. ChannelID: msgIn.Message.ChannelID,
  409. TLVUserInfo: wire.TLVUserInfo{
  410. ScreenName: msgIn.Sender.String(),
  411. },
  412. TLVRestBlock: wire.TLVRestBlock{},
  413. }
  414. for _, tlv := range msgIn.Message.TLVList {
  415. clientIM.Append(tlv)
  416. }
  417. clientIM.Append(wire.NewTLVBE(wire.ICBMTLVSendTime, uint32(msgIn.Sent.Unix())))
  418. s.messageRelayer.RelayToScreenName(ctx, msgIn.Recipient, wire.SNACMessage{
  419. Frame: wire.SNACFrame{
  420. FoodGroup: wire.ICBM,
  421. SubGroup: wire.ICBMChannelMsgToClient,
  422. RequestID: wire.ReqIDFromServer,
  423. },
  424. Body: clientIM,
  425. })
  426. }
  427. }
  428. eofMsg := wire.ICQMessageReplyEnvelope{
  429. Message: wire.ICQ_0x0042_DBQueryOfflineMsgReplyLast{
  430. ICQMetadata: wire.ICQMetadata{
  431. UIN: instance.UIN(),
  432. ReqType: wire.ICQDBQueryOfflineMsgReplyLast,
  433. Seq: seq,
  434. },
  435. DroppedMessages: 0,
  436. },
  437. }
  438. if err := s.reply(ctx, instance, eofMsg, inFrame.RequestID, 0); err != nil {
  439. return fmt.Errorf("sending end of offline messages: %w", err)
  440. }
  441. return nil
  442. }
  443. func (s *ICQService) SetAffiliations(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations, seq uint16) error {
  444. if len(inBody.PastAffiliations) != 3 || len(inBody.Affiliations) != 3 {
  445. return fmt.Errorf("%w: expected 3 past affiliations and 3 affiliations", errICQBadRequest)
  446. }
  447. u := state.ICQAffiliations{
  448. PastCode1: inBody.PastAffiliations[0].Code,
  449. PastKeyword1: inBody.PastAffiliations[0].Keyword,
  450. PastCode2: inBody.PastAffiliations[1].Code,
  451. PastKeyword2: inBody.PastAffiliations[1].Keyword,
  452. PastCode3: inBody.PastAffiliations[2].Code,
  453. PastKeyword3: inBody.PastAffiliations[2].Keyword,
  454. CurrentCode1: inBody.Affiliations[0].Code,
  455. CurrentKeyword1: inBody.Affiliations[0].Keyword,
  456. CurrentCode2: inBody.Affiliations[1].Code,
  457. CurrentKeyword2: inBody.Affiliations[1].Keyword,
  458. CurrentCode3: inBody.Affiliations[2].Code,
  459. CurrentKeyword3: inBody.Affiliations[2].Keyword,
  460. }
  461. if err := s.userUpdater.SetAffiliations(ctx, instance.IdentScreenName(), u); err != nil {
  462. return err
  463. }
  464. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetAffiliations, inFrame.RequestID)
  465. }
  466. func (s *ICQService) SetBasicInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03EA_DBQueryMetaReqSetBasicInfo, seq uint16) error {
  467. u := state.ICQBasicInfo{
  468. CellPhone: inBody.CellPhone,
  469. CountryCode: inBody.CountryCode,
  470. EmailAddress: inBody.EmailAddress,
  471. FirstName: inBody.FirstName,
  472. GMTOffset: inBody.GMTOffset,
  473. Address: inBody.HomeAddress,
  474. City: inBody.City,
  475. Fax: inBody.Fax,
  476. Phone: inBody.Phone,
  477. State: inBody.State,
  478. LastName: inBody.LastName,
  479. Nickname: inBody.Nickname,
  480. PublishEmail: inBody.PublishEmail == wire.ICQUserFlagPublishEmailYes,
  481. ZIPCode: inBody.ZIP,
  482. }
  483. if err := s.userUpdater.SetBasicInfo(ctx, instance.IdentScreenName(), u); err != nil {
  484. return err
  485. }
  486. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetBasicInfo, inFrame.RequestID)
  487. }
  488. func (s *ICQService) SetEmails(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x040B_DBQueryMetaReqSetEmails, seq uint16) error {
  489. if len(inBody.Emails) > 0 {
  490. s.logger.Debug("adding additional emails is not yet supported")
  491. }
  492. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetEmails, inFrame.RequestID)
  493. }
  494. // SetICQInfo handles the TLV-based CLI_SET_FULLINFO (0x0C3A) request used to
  495. // update all profile information in a single packet. The TLV chain may
  496. // contain any subset of fields; omitted fields are preserved by loading the
  497. // current user record and overlaying only the values that were sent. Within a
  498. // multi-valued group (languages, interests, affiliations) any unspecified
  499. // slots are reset when at least one TLV in that group is present, matching
  500. // the way ICQLite tabs replace their entire group on save.
  501. func (s *ICQService) SetICQInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0C3A_DBQueryMetaReqSetFullInfo, seq uint16) error {
  502. user, err := s.userFinder.FindByUIN(ctx, instance.UIN())
  503. if err != nil {
  504. return fmt.Errorf("FindByUIN: %w", err)
  505. }
  506. // Multi-valued tags are collected in encounter order and applied to
  507. // their target struct after the loop.
  508. var langTLVs []uint8
  509. var interestTLVs, currentAffTLVs, pastAffTLVs [][]byte
  510. for _, tlv := range inBody.TLVList {
  511. switch tlv.Tag {
  512. // ----- ICQBasicInfo -----
  513. case wire.ICQTLVTagsFirstName:
  514. user.ICQInfo.Basic.FirstName = tlv.ICQString()
  515. case wire.ICQTLVTagsLastName:
  516. user.ICQInfo.Basic.LastName = tlv.ICQString()
  517. case wire.ICQTLVTagsNickname:
  518. user.ICQInfo.Basic.Nickname = tlv.ICQString()
  519. case wire.ICQTLVTagsEmail:
  520. var n struct {
  521. Email string `oscar:"len_prefix=uint16,nullterm"`
  522. Publish uint8
  523. }
  524. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  525. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  526. }
  527. user.ICQInfo.Basic.EmailAddress = n.Email
  528. // publish=0 means "publish my email", matching the
  529. // existing ICQUserFlagPublishEmailYes (0) convention.
  530. user.ICQInfo.Basic.PublishEmail = n.Publish == wire.ICQUserFlagPublishEmailYes
  531. case wire.ICQTLVTagsHomeCityName:
  532. user.ICQInfo.Basic.City = tlv.ICQString()
  533. case wire.ICQTLVTagsHomeStateAbbr:
  534. user.ICQInfo.Basic.State = tlv.ICQString()
  535. case wire.ICQTLVTagsHomeCountryCode:
  536. user.ICQInfo.Basic.CountryCode = tlv.Uint16LE()
  537. case wire.ICQTLVTagsHomeStreetAddress:
  538. user.ICQInfo.Basic.Address = tlv.ICQString()
  539. case wire.ICQTLVTagsHomeZipCode:
  540. user.ICQInfo.Basic.ZIPCode = strconv.FormatUint(uint64(tlv.Uint32LE()), 10)
  541. case wire.ICQTLVTagsHomePhoneNumber:
  542. user.ICQInfo.Basic.Phone = tlv.ICQString()
  543. case wire.ICQTLVTagsHomeFaxNumber:
  544. user.ICQInfo.Basic.Fax = tlv.ICQString()
  545. case wire.ICQTLVTagsHomeCellularPhoneNumber:
  546. user.ICQInfo.Basic.CellPhone = tlv.ICQString()
  547. case wire.ICQTLVTagsGMTOffset:
  548. user.ICQInfo.Basic.GMTOffset = tlv.Uint8()
  549. case wire.ICQTLVTagsOriginallyFromCity:
  550. user.ICQInfo.Basic.OriginallyFromCity = tlv.ICQString()
  551. case wire.ICQTLVTagsOriginallyFromState:
  552. user.ICQInfo.Basic.OriginallyFromState = tlv.ICQString()
  553. case wire.ICQTLVTagsOriginallyFromCountryCode:
  554. user.ICQInfo.Basic.OriginallyFromCountryCode = tlv.Uint16LE()
  555. // ----- ICQMoreInfo -----
  556. case wire.ICQTLVTagsGender:
  557. user.ICQInfo.More.Gender = uint16(tlv.Uint8())
  558. case wire.ICQTLVTagsSpokenLanguage:
  559. langTLVs = append(langTLVs, tlv.Uint8())
  560. case wire.ICQTLVTagsBirthdayInfo:
  561. var n struct {
  562. Year uint16
  563. Month uint16
  564. Day uint16
  565. }
  566. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  567. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  568. }
  569. user.ICQInfo.More.BirthYear = n.Year
  570. user.ICQInfo.More.BirthMonth = uint8(n.Month)
  571. user.ICQInfo.More.BirthDay = uint8(n.Day)
  572. case wire.ICQTLVTagsHomepageURL:
  573. user.ICQInfo.More.HomePageAddr = tlv.ICQString()
  574. // ----- ICQWorkInfo -----
  575. case wire.ICQTLVTagsWorkCompanyName:
  576. user.ICQInfo.Work.Company = tlv.ICQString()
  577. case wire.ICQTLVTagsWorkDepartmentName:
  578. user.ICQInfo.Work.Department = tlv.ICQString()
  579. case wire.ICQTLVTagsWorkPositionTitle:
  580. user.ICQInfo.Work.Position = tlv.ICQString()
  581. case wire.ICQTLVTagsWorkOccupationCode:
  582. user.ICQInfo.Work.OccupationCode = tlv.Uint16LE()
  583. case wire.ICQTLVTagsWorkStreetAddress:
  584. user.ICQInfo.Work.Address = tlv.ICQString()
  585. case wire.ICQTLVTagsWorkCityName:
  586. user.ICQInfo.Work.City = tlv.ICQString()
  587. case wire.ICQTLVTagsWorkStateName:
  588. user.ICQInfo.Work.State = tlv.ICQString()
  589. case wire.ICQTLVTagsWorkCountryCode:
  590. user.ICQInfo.Work.CountryCode = tlv.Uint16LE()
  591. case wire.ICQTLVTagsWorkZipCode:
  592. user.ICQInfo.Work.ZIPCode = strconv.FormatUint(uint64(tlv.Uint32LE()), 10)
  593. case wire.ICQTLVTagsWorkPhoneNumber:
  594. user.ICQInfo.Work.Phone = tlv.ICQString()
  595. case wire.ICQTLVTagsWorkFaxNumber:
  596. user.ICQInfo.Work.Fax = tlv.ICQString()
  597. case wire.ICQTLVTagsWorkWebpageURL:
  598. user.ICQInfo.Work.WebPage = tlv.ICQString()
  599. // ----- ICQPermissions -----
  600. case wire.ICQTLVTagsAuthorizationPermissions:
  601. // Matches the wire-struct convention used by SetPermissions:
  602. // authorization == 0 means "authorization required".
  603. user.ICQInfo.Permissions.AuthRequired = tlv.Uint8() == 0
  604. case wire.ICQTLVTagsShowWebStatusPermissions:
  605. user.ICQInfo.Permissions.WebAware = tlv.Uint8() == 1
  606. // ----- ICQUserNotes -----
  607. case wire.ICQTLVTagsNotesText:
  608. user.ICQInfo.Notes.Notes = tlv.ICQString()
  609. // ----- ICQInterests -----
  610. case wire.ICQTLVTagsInterestsNode: // may appear multiple times
  611. interestTLVs = append(interestTLVs, tlv.Value)
  612. // ----- ICQAffiliations -----
  613. case wire.ICQTLVTagsAffiliationsNode: // may appear multiple times
  614. currentAffTLVs = append(currentAffTLVs, tlv.Value)
  615. case wire.ICQTLVTagsPastInfoNode: // may appear multiple times
  616. pastAffTLVs = append(pastAffTLVs, tlv.Value)
  617. // ----- ICQHomepageCategory -----
  618. case wire.ICQTLVTagsHomepageCategoryKeywords:
  619. var n struct {
  620. Index uint16
  621. Description string `oscar:"len_prefix=uint16,nullterm"`
  622. }
  623. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  624. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  625. }
  626. user.ICQInfo.HomepageCategory.Index = n.Index
  627. user.ICQInfo.HomepageCategory.Description = n.Description
  628. user.ICQInfo.HomepageCategory.Enabled = true
  629. default:
  630. // Search-only or otherwise unmapped tags
  631. // (UIN, Age, AgeRangeSearch, WhitepagesSearchKeywords,
  632. // SearchOnlineUsersFlag).
  633. s.logger.Debug("ICQ SetICQInfo: ignoring unsupported TLV",
  634. "tag", fmt.Sprintf("0x%04X", tlv.Tag),
  635. "len", len(tlv.Value))
  636. }
  637. }
  638. if len(langTLVs) > 0 {
  639. user.ICQInfo.More.Lang1, user.ICQInfo.More.Lang2, user.ICQInfo.More.Lang3 = 0, 0, 0
  640. for i, v := range langTLVs {
  641. switch i {
  642. case 0:
  643. user.ICQInfo.More.Lang1 = v
  644. case 1:
  645. user.ICQInfo.More.Lang2 = v
  646. case 2:
  647. user.ICQInfo.More.Lang3 = v
  648. }
  649. }
  650. }
  651. if len(interestTLVs) > 0 {
  652. user.ICQInfo.Interests = state.ICQInterests{}
  653. for i, raw := range interestTLVs {
  654. var n struct {
  655. Code uint16
  656. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  657. }
  658. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  659. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  660. }
  661. switch i {
  662. case 0:
  663. user.ICQInfo.Interests.Code1, user.ICQInfo.Interests.Keyword1 = n.Code, n.Keyword
  664. case 1:
  665. user.ICQInfo.Interests.Code2, user.ICQInfo.Interests.Keyword2 = n.Code, n.Keyword
  666. case 2:
  667. user.ICQInfo.Interests.Code3, user.ICQInfo.Interests.Keyword3 = n.Code, n.Keyword
  668. case 3:
  669. user.ICQInfo.Interests.Code4, user.ICQInfo.Interests.Keyword4 = n.Code, n.Keyword
  670. }
  671. }
  672. }
  673. if len(currentAffTLVs) > 0 {
  674. user.ICQInfo.Affiliations.CurrentCode1, user.ICQInfo.Affiliations.CurrentKeyword1 = 0, ""
  675. user.ICQInfo.Affiliations.CurrentCode2, user.ICQInfo.Affiliations.CurrentKeyword2 = 0, ""
  676. user.ICQInfo.Affiliations.CurrentCode3, user.ICQInfo.Affiliations.CurrentKeyword3 = 0, ""
  677. for i, raw := range currentAffTLVs {
  678. var n struct {
  679. Code uint16
  680. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  681. }
  682. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  683. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  684. }
  685. switch i {
  686. case 0:
  687. user.ICQInfo.Affiliations.CurrentCode1, user.ICQInfo.Affiliations.CurrentKeyword1 = n.Code, n.Keyword
  688. case 1:
  689. user.ICQInfo.Affiliations.CurrentCode2, user.ICQInfo.Affiliations.CurrentKeyword2 = n.Code, n.Keyword
  690. case 2:
  691. user.ICQInfo.Affiliations.CurrentCode3, user.ICQInfo.Affiliations.CurrentKeyword3 = n.Code, n.Keyword
  692. }
  693. }
  694. }
  695. if len(pastAffTLVs) > 0 {
  696. user.ICQInfo.Affiliations.PastCode1, user.ICQInfo.Affiliations.PastKeyword1 = 0, ""
  697. user.ICQInfo.Affiliations.PastCode2, user.ICQInfo.Affiliations.PastKeyword2 = 0, ""
  698. user.ICQInfo.Affiliations.PastCode3, user.ICQInfo.Affiliations.PastKeyword3 = 0, ""
  699. for i, raw := range pastAffTLVs {
  700. var n struct {
  701. Code uint16
  702. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  703. }
  704. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  705. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  706. }
  707. switch i {
  708. case 0:
  709. user.ICQInfo.Affiliations.PastCode1, user.ICQInfo.Affiliations.PastKeyword1 = n.Code, n.Keyword
  710. case 1:
  711. user.ICQInfo.Affiliations.PastCode2, user.ICQInfo.Affiliations.PastKeyword2 = n.Code, n.Keyword
  712. case 2:
  713. user.ICQInfo.Affiliations.PastCode3, user.ICQInfo.Affiliations.PastKeyword3 = n.Code, n.Keyword
  714. }
  715. }
  716. }
  717. name := instance.IdentScreenName()
  718. if err := s.userUpdater.SetICQInfo(ctx, name, user.ICQInfo); err != nil {
  719. return err
  720. }
  721. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetFullInfo, inFrame.RequestID)
  722. }
  723. func (s *ICQService) SetICQPhone(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0654_DBQueryMetaReqSetICQPhone, seq uint16) error {
  724. s.logger.Debug("received SetICQPhone request")
  725. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetICQPhone, inFrame.RequestID)
  726. }
  727. func (s *ICQService) SetInterests(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0410_DBQueryMetaReqSetInterests, seq uint16) error {
  728. if len(inBody.Interests) != 4 {
  729. return fmt.Errorf("%w: expected 4 interests", errICQBadRequest)
  730. }
  731. u := state.ICQInterests{
  732. Code1: inBody.Interests[0].Code,
  733. Keyword1: inBody.Interests[0].Keyword,
  734. Code2: inBody.Interests[1].Code,
  735. Keyword2: inBody.Interests[1].Keyword,
  736. Code3: inBody.Interests[2].Code,
  737. Keyword3: inBody.Interests[2].Keyword,
  738. Code4: inBody.Interests[3].Code,
  739. Keyword4: inBody.Interests[3].Keyword,
  740. }
  741. if err := s.userUpdater.SetInterests(ctx, instance.IdentScreenName(), u); err != nil {
  742. return err
  743. }
  744. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetInterests, inFrame.RequestID)
  745. }
  746. func (s *ICQService) SetMoreInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03FD_DBQueryMetaReqSetMoreInfo, seq uint16) error {
  747. u := state.ICQMoreInfo{
  748. Gender: inBody.Gender,
  749. HomePageAddr: inBody.HomePageAddr,
  750. BirthYear: inBody.BirthYear,
  751. BirthMonth: inBody.BirthMonth,
  752. BirthDay: inBody.BirthDay,
  753. Lang1: inBody.Lang1,
  754. Lang2: inBody.Lang2,
  755. Lang3: inBody.Lang3,
  756. }
  757. if err := s.userUpdater.SetMoreInfo(ctx, instance.IdentScreenName(), u); err != nil {
  758. return err
  759. }
  760. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetMoreInfo, inFrame.RequestID)
  761. }
  762. // SetPermissions persists ICQ privacy flags. AuthRequired controls whether other
  763. // users need authorization to add this user; when true, contact pre-authorization
  764. // can allow specific users to add without a new prompt.
  765. func (s *ICQService) SetPermissions(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0424_DBQueryMetaReqSetPermissions, seq uint16) error {
  766. u := state.ICQPermissions{
  767. AuthRequired: inBody.Authorization == 0,
  768. WebAware: inBody.WebAware == 1,
  769. }
  770. if err := s.userUpdater.SetPermissions(ctx, instance.IdentScreenName(), u); err != nil {
  771. return err
  772. }
  773. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetPermissions, inFrame.RequestID)
  774. }
  775. func (s *ICQService) SetUserNotes(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes, seq uint16) error {
  776. u := state.ICQUserNotes{
  777. Notes: inBody.Notes,
  778. }
  779. if err := s.userUpdater.SetUserNotes(ctx, instance.IdentScreenName(), u); err != nil {
  780. return err
  781. }
  782. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetNotes, inFrame.RequestID)
  783. }
  784. func (s *ICQService) SetWorkInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo, seq uint16) error {
  785. icqWorkInfo := state.ICQWorkInfo{
  786. Company: inBody.Company,
  787. Department: inBody.Department,
  788. OccupationCode: inBody.OccupationCode,
  789. Position: inBody.Position,
  790. Address: inBody.Address,
  791. City: inBody.City,
  792. CountryCode: inBody.CountryCode,
  793. Fax: inBody.Fax,
  794. Phone: inBody.Phone,
  795. State: inBody.State,
  796. WebPage: inBody.WebPage,
  797. ZIPCode: inBody.ZIP,
  798. }
  799. if err := s.userUpdater.SetWorkInfo(ctx, instance.IdentScreenName(), icqWorkInfo); err != nil {
  800. return err
  801. }
  802. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetWorkInfo, inFrame.RequestID)
  803. }
  804. func (s *ICQService) ShortUserInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x04BA_DBQueryMetaReqShortInfo, seq uint16) error {
  805. user, err := s.userFinder.FindByUIN(ctx, inBody.UIN)
  806. if err != nil {
  807. if errors.Is(err, state.ErrNoUser) {
  808. msg := wire.ICQMessageReplyEnvelope{
  809. Message: wire.ICQ_0x07DA_0x0104_DBQueryMetaReplyShortInfo{
  810. ICQMetadata: wire.ICQMetadata{
  811. UIN: instance.UIN(),
  812. ReqType: wire.ICQDBQueryMetaReply,
  813. Seq: seq,
  814. },
  815. ReqSubType: wire.ICQDBQueryMetaReplyShortInfo,
  816. Success: wire.ICQStatusCodeErr,
  817. },
  818. }
  819. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  820. }
  821. return err
  822. }
  823. info := wire.ICQ_0x07DA_0x0104_DBQueryMetaReplyShortInfo{
  824. ICQMetadata: wire.ICQMetadata{
  825. UIN: instance.UIN(),
  826. ReqType: wire.ICQDBQueryMetaReply,
  827. Seq: seq,
  828. },
  829. ReqSubType: wire.ICQDBQueryMetaReplyShortInfo,
  830. Success: wire.ICQStatusCodeOK,
  831. Nickname: user.ICQInfo.Basic.Nickname,
  832. FirstName: user.ICQInfo.Basic.FirstName,
  833. LastName: user.ICQInfo.Basic.LastName,
  834. Email: user.ICQInfo.Basic.EmailAddress,
  835. Gender: uint8(user.ICQInfo.More.Gender),
  836. }
  837. if !user.ICQInfo.Permissions.AuthRequired {
  838. info.Authorization = 1
  839. }
  840. msg := wire.ICQMessageReplyEnvelope{
  841. Message: info,
  842. }
  843. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  844. }
  845. func (s *ICQService) XMLReqData(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0898_DBQueryMetaReqXMLReq, seq uint16) error {
  846. msg := wire.ICQMessageReplyEnvelope{
  847. Message: wire.ICQ_0x07DA_0x08A2_DBQueryMetaReplyXMLData{
  848. ICQMetadata: wire.ICQMetadata{
  849. UIN: instance.UIN(),
  850. ReqType: wire.ICQDBQueryMetaReply,
  851. Seq: seq,
  852. },
  853. ReqSubType: wire.ICQDBQueryMetaReplyXMLData,
  854. Success: wire.ICQStatusCodeErr,
  855. },
  856. }
  857. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  858. }
  859. func (s *ICQService) userInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  860. userInfo := wire.ICQ_0x07DA_0x00C8_DBQueryMetaReplyBasicInfo{
  861. ICQMetadata: wire.ICQMetadata{
  862. UIN: instance.UIN(),
  863. ReqType: wire.ICQDBQueryMetaReply,
  864. Seq: seq,
  865. },
  866. ReqSubType: wire.ICQDBQueryMetaReplyBasicInfo,
  867. Success: wire.ICQStatusCodeOK,
  868. Nickname: user.ICQInfo.Basic.Nickname,
  869. FirstName: user.ICQInfo.Basic.FirstName,
  870. LastName: user.ICQInfo.Basic.LastName,
  871. Email: user.ICQInfo.Basic.EmailAddress,
  872. City: user.ICQInfo.Basic.City,
  873. State: user.ICQInfo.Basic.State,
  874. Phone: user.ICQInfo.Basic.Phone,
  875. Fax: user.ICQInfo.Basic.Fax,
  876. Address: user.ICQInfo.Basic.Address,
  877. CellPhone: user.ICQInfo.Basic.CellPhone,
  878. ZIP: user.ICQInfo.Basic.ZIPCode,
  879. CountryCode: user.ICQInfo.Basic.CountryCode,
  880. GMTOffset: user.ICQInfo.Basic.GMTOffset,
  881. AuthFlag: 0, // required by default
  882. WebAware: 1,
  883. DCPerms: 0,
  884. }
  885. if !user.ICQInfo.Permissions.AuthRequired {
  886. userInfo.AuthFlag = 1
  887. }
  888. if user.ICQInfo.Permissions.WebAware {
  889. userInfo.WebAware = 1
  890. } else {
  891. userInfo.WebAware = 0
  892. }
  893. if user.ICQInfo.Basic.PublishEmail {
  894. userInfo.PublishEmail = wire.ICQUserFlagPublishEmailYes
  895. } else {
  896. userInfo.PublishEmail = wire.ICQUserFlagPublishEmailNo
  897. }
  898. msg := wire.ICQMessageReplyEnvelope{
  899. Message: userInfo,
  900. }
  901. return s.reply(ctx, instance, msg, requestID, flags)
  902. }
  903. func (s *ICQService) moreUserInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  904. msg := wire.ICQMessageReplyEnvelope{
  905. Message: wire.ICQ_0x07DA_0x00DC_DBQueryMetaReplyMoreInfo{
  906. ICQMetadata: wire.ICQMetadata{
  907. UIN: instance.UIN(),
  908. ReqType: wire.ICQDBQueryMetaReply,
  909. Seq: seq,
  910. },
  911. ReqSubType: wire.ICQDBQueryMetaReplyMoreInfo,
  912. Success: wire.ICQStatusCodeOK,
  913. Age: uint16(user.Age(s.timeNow)),
  914. Gender: uint8(user.ICQInfo.More.Gender),
  915. HomePageAddr: user.ICQInfo.More.HomePageAddr,
  916. BirthYear: user.ICQInfo.More.BirthYear,
  917. BirthMonth: user.ICQInfo.More.BirthMonth,
  918. BirthDay: user.ICQInfo.More.BirthDay,
  919. Lang1: user.ICQInfo.More.Lang1,
  920. Lang2: user.ICQInfo.More.Lang2,
  921. Lang3: user.ICQInfo.More.Lang3,
  922. City: user.ICQInfo.Basic.City,
  923. State: user.ICQInfo.Basic.State,
  924. CountryCode: user.ICQInfo.Basic.CountryCode,
  925. TimeZone: user.ICQInfo.Basic.GMTOffset,
  926. },
  927. }
  928. return s.reply(ctx, instance, msg, requestID, flags)
  929. }
  930. func (s *ICQService) extraEmails(ctx context.Context, instance *state.SessionInstance, requestID uint32, seq uint16, flags uint16) error {
  931. msg := wire.ICQMessageReplyEnvelope{
  932. Message: wire.ICQ_0x07DA_0x00EB_DBQueryMetaReplyExtEmailInfo{
  933. ICQMetadata: wire.ICQMetadata{
  934. UIN: instance.UIN(),
  935. ReqType: wire.ICQDBQueryMetaReply,
  936. Seq: seq,
  937. },
  938. ReqSubType: wire.ICQDBQueryMetaReplyExtEmailInfo,
  939. Success: wire.ICQStatusCodeOK,
  940. },
  941. }
  942. return s.reply(ctx, instance, msg, requestID, flags)
  943. }
  944. func (s *ICQService) homepageCat(ctx context.Context, instance *state.SessionInstance, requestID uint32, seq uint16, flags uint16) error {
  945. msg := wire.ICQMessageReplyEnvelope{
  946. Message: wire.ICQ_0x07DA_0x010E_DBQueryMetaReplyHomePageCat{
  947. ICQMetadata: wire.ICQMetadata{
  948. UIN: instance.UIN(),
  949. ReqType: wire.ICQDBQueryMetaReply,
  950. Seq: seq,
  951. },
  952. ReqSubType: wire.ICQDBQueryMetaReplyHomePageCat,
  953. Success: wire.ICQStatusCodeOK,
  954. },
  955. }
  956. return s.reply(ctx, instance, msg, requestID, flags)
  957. }
  958. func (s *ICQService) workInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  959. msg := wire.ICQMessageReplyEnvelope{
  960. Message: wire.ICQ_0x07DA_0x00D2_DBQueryMetaReplyWorkInfo{
  961. ICQMetadata: wire.ICQMetadata{
  962. UIN: instance.UIN(),
  963. ReqType: wire.ICQDBQueryMetaReply,
  964. Seq: seq,
  965. },
  966. ReqSubType: wire.ICQDBQueryMetaReplyWorkInfo,
  967. Success: wire.ICQStatusCodeOK,
  968. ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo: wire.ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo{
  969. City: user.ICQInfo.Work.City,
  970. State: user.ICQInfo.Work.State,
  971. Phone: user.ICQInfo.Work.Phone,
  972. Fax: user.ICQInfo.Work.Fax,
  973. Address: user.ICQInfo.Work.Address,
  974. ZIP: user.ICQInfo.Work.ZIPCode,
  975. CountryCode: user.ICQInfo.Work.CountryCode,
  976. Company: user.ICQInfo.Work.Company,
  977. Department: user.ICQInfo.Work.Department,
  978. Position: user.ICQInfo.Work.Position,
  979. OccupationCode: user.ICQInfo.Work.OccupationCode,
  980. WebPage: user.ICQInfo.Work.WebPage,
  981. },
  982. },
  983. }
  984. return s.reply(ctx, instance, msg, requestID, flags)
  985. }
  986. func (s *ICQService) notes(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  987. msg := wire.ICQMessageReplyEnvelope{
  988. Message: wire.ICQ_0x07DA_0x00E6_DBQueryMetaReplyNotes{
  989. ICQMetadata: wire.ICQMetadata{
  990. UIN: instance.UIN(),
  991. ReqType: wire.ICQDBQueryMetaReply,
  992. Seq: seq,
  993. },
  994. ReqSubType: wire.ICQDBQueryMetaReplyNotes,
  995. Success: wire.ICQStatusCodeOK,
  996. ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes: wire.ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes{
  997. Notes: user.ICQInfo.Notes.Notes,
  998. },
  999. },
  1000. }
  1001. return s.reply(ctx, instance, msg, requestID, flags)
  1002. }
  1003. func (s *ICQService) interests(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  1004. msg := wire.ICQMessageReplyEnvelope{
  1005. Message: wire.ICQ_0x07DA_0x00F0_DBQueryMetaReplyInterests{
  1006. ICQMetadata: wire.ICQMetadata{
  1007. UIN: instance.UIN(),
  1008. ReqType: wire.ICQDBQueryMetaReply,
  1009. Seq: seq,
  1010. },
  1011. ReqSubType: wire.ICQDBQueryMetaReplyInterests,
  1012. Success: wire.ICQStatusCodeOK,
  1013. Interests: []struct {
  1014. Code uint16
  1015. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1016. }{
  1017. {
  1018. Code: user.ICQInfo.Interests.Code1,
  1019. Keyword: user.ICQInfo.Interests.Keyword1,
  1020. },
  1021. {
  1022. Code: user.ICQInfo.Interests.Code2,
  1023. Keyword: user.ICQInfo.Interests.Keyword2,
  1024. },
  1025. {
  1026. Code: user.ICQInfo.Interests.Code3,
  1027. Keyword: user.ICQInfo.Interests.Keyword3,
  1028. },
  1029. {
  1030. Code: user.ICQInfo.Interests.Code4,
  1031. Keyword: user.ICQInfo.Interests.Keyword4,
  1032. },
  1033. },
  1034. },
  1035. }
  1036. return s.reply(ctx, instance, msg, requestID, flags)
  1037. }
  1038. func (s *ICQService) affiliations(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  1039. msg := wire.ICQMessageReplyEnvelope{
  1040. Message: wire.ICQ_0x07DA_0x00FA_DBQueryMetaReplyAffiliations{
  1041. ICQMetadata: wire.ICQMetadata{
  1042. UIN: instance.UIN(),
  1043. ReqType: wire.ICQDBQueryMetaReply,
  1044. Seq: seq,
  1045. },
  1046. ReqSubType: wire.ICQDBQueryMetaReplyAffiliations,
  1047. Success: wire.ICQStatusCodeOK,
  1048. ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations: wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations{
  1049. PastAffiliations: []struct {
  1050. Code uint16
  1051. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1052. }{
  1053. {
  1054. Code: user.ICQInfo.Affiliations.PastCode1,
  1055. Keyword: user.ICQInfo.Affiliations.PastKeyword1,
  1056. },
  1057. {
  1058. Code: user.ICQInfo.Affiliations.PastCode2,
  1059. Keyword: user.ICQInfo.Affiliations.PastKeyword2,
  1060. },
  1061. {
  1062. Code: user.ICQInfo.Affiliations.PastCode3,
  1063. Keyword: user.ICQInfo.Affiliations.PastKeyword3,
  1064. },
  1065. },
  1066. Affiliations: []struct {
  1067. Code uint16
  1068. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1069. }{
  1070. {
  1071. Code: user.ICQInfo.Affiliations.CurrentCode1,
  1072. Keyword: user.ICQInfo.Affiliations.CurrentKeyword1,
  1073. },
  1074. {
  1075. Code: user.ICQInfo.Affiliations.CurrentCode2,
  1076. Keyword: user.ICQInfo.Affiliations.CurrentKeyword2,
  1077. },
  1078. {
  1079. Code: user.ICQInfo.Affiliations.CurrentCode3,
  1080. Keyword: user.ICQInfo.Affiliations.CurrentKeyword3,
  1081. },
  1082. },
  1083. },
  1084. },
  1085. }
  1086. return s.reply(ctx, instance, msg, requestID, flags)
  1087. }
  1088. func (s *ICQService) reply(ctx context.Context, instance *state.SessionInstance, message wire.ICQMessageReplyEnvelope, requestID uint32, snacFlags uint16) error {
  1089. msg := wire.SNACMessage{
  1090. Frame: wire.SNACFrame{
  1091. FoodGroup: wire.ICQ,
  1092. SubGroup: wire.ICQDBReply,
  1093. RequestID: requestID,
  1094. Flags: snacFlags,
  1095. },
  1096. Body: wire.SNAC_0x15_0x02_DBReply{
  1097. TLVRestBlock: wire.TLVRestBlock{
  1098. TLVList: wire.TLVList{
  1099. wire.NewTLVBE(wire.ICQTLVTagsMetadata, message),
  1100. },
  1101. },
  1102. },
  1103. }
  1104. s.messageRelayer.RelayToScreenName(ctx, instance.IdentScreenName(), msg)
  1105. return nil
  1106. }
  1107. func (s *ICQService) replySearchHit(ctx context.Context, instance *state.SessionInstance, record wire.ICQUserSearchRecord, requestID uint32, last bool, seq uint16) error {
  1108. var resp any
  1109. var more uint16
  1110. if last {
  1111. resp = wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1112. ICQMetadata: wire.ICQMetadata{
  1113. UIN: instance.UIN(),
  1114. ReqType: wire.ICQDBQueryMetaReply,
  1115. Seq: seq,
  1116. },
  1117. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1118. Success: wire.ICQStatusCodeOK,
  1119. Details: record,
  1120. UsersLeft: 0,
  1121. }
  1122. } else {
  1123. resp = wire.ICQ_0x07DA_0x01A4_DBQueryMetaReplyUserFound{
  1124. ICQMetadata: wire.ICQMetadata{
  1125. UIN: instance.UIN(),
  1126. ReqType: wire.ICQDBQueryMetaReply,
  1127. Seq: seq,
  1128. },
  1129. ReqSubType: wire.ICQDBQueryMetaReplyUserFound,
  1130. Success: wire.ICQStatusCodeOK,
  1131. Details: record,
  1132. }
  1133. more = wire.SNACFlagsMoreToCome
  1134. }
  1135. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{Message: resp}, requestID, more)
  1136. }
  1137. func (s *ICQService) reportSearchFailure(ctx context.Context, instance *state.SessionInstance, seq uint16, requestID uint32) error {
  1138. resp := wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1139. ICQMetadata: wire.ICQMetadata{
  1140. UIN: instance.UIN(),
  1141. ReqType: wire.ICQDBQueryMetaReply,
  1142. Seq: seq,
  1143. },
  1144. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1145. Success: wire.ICQStatusCodeErr,
  1146. }
  1147. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{
  1148. Message: resp,
  1149. }, requestID, 0)
  1150. }
  1151. func (s *ICQService) reportNoSearchResults(ctx context.Context, instance *state.SessionInstance, seq uint16, requestID uint32) error {
  1152. resp := wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1153. ICQMetadata: wire.ICQMetadata{
  1154. UIN: instance.UIN(),
  1155. ReqType: wire.ICQDBQueryMetaReply,
  1156. Seq: seq,
  1157. },
  1158. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1159. Success: wire.ICQStatusCodeFail,
  1160. }
  1161. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{
  1162. Message: resp,
  1163. }, requestID, 0)
  1164. }
  1165. func (s *ICQService) reqAck(ctx context.Context, instance *state.SessionInstance, seq uint16, subType uint16, requestID uint32) error {
  1166. msg := wire.ICQMessageReplyEnvelope{
  1167. Message: wire.ICQ_0x07DA_0x00DC_DBQueryMetaReplyMoreInfo{
  1168. ICQMetadata: wire.ICQMetadata{
  1169. UIN: instance.UIN(),
  1170. ReqType: wire.ICQDBQueryMetaReply,
  1171. Seq: seq,
  1172. },
  1173. ReqSubType: subType,
  1174. Success: wire.ICQStatusCodeOK,
  1175. },
  1176. }
  1177. return s.reply(ctx, instance, msg, requestID, 0)
  1178. }
  1179. func (s *ICQService) createResult(res state.User) wire.ICQUserSearchRecord {
  1180. uin, _ := strconv.Atoi(res.IdentScreenName.String())
  1181. searchRecord := wire.ICQUserSearchRecord{
  1182. UIN: uint32(uin),
  1183. Nickname: res.ICQInfo.Basic.Nickname,
  1184. FirstName: res.ICQInfo.Basic.FirstName,
  1185. LastName: res.ICQInfo.Basic.LastName,
  1186. Email: res.ICQInfo.Basic.EmailAddress,
  1187. Gender: uint8(res.ICQInfo.More.Gender),
  1188. Age: res.Age(s.timeNow),
  1189. }
  1190. if !res.ICQInfo.Permissions.AuthRequired {
  1191. searchRecord.Authorization = 1
  1192. }
  1193. userSess := s.sessionRetriever.RetrieveSession(res.IdentScreenName)
  1194. if userSess != nil {
  1195. searchRecord.OnlineStatus = 1
  1196. }
  1197. return searchRecord
  1198. }