icq.go 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344
  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.replyToSelf(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. // The stored SNAC is whatever the sender sent. A send time it
  416. // carries is not ours, and TLVList lookups return the first match,
  417. // so it would shadow the stamp appended below.
  418. if tlv.Tag == wire.ICBMTLVSendTime {
  419. continue
  420. }
  421. clientIM.Append(tlv)
  422. }
  423. clientIM.Append(wire.NewTLVBE(wire.ICBMTLVSendTime, uint32(msgIn.Sent.Unix())))
  424. // Retrieval answers the instance that asked for it, not every instance
  425. // of the account.
  426. s.messageRelayer.RelayToSelf(ctx, instance, wire.SNACMessage{
  427. Frame: wire.SNACFrame{
  428. FoodGroup: wire.ICBM,
  429. SubGroup: wire.ICBMChannelMsgToClient,
  430. RequestID: wire.ReqIDFromServer,
  431. },
  432. Body: clientIM,
  433. })
  434. }
  435. }
  436. eofMsg := wire.ICQMessageReplyEnvelope{
  437. Message: wire.ICQ_0x0042_DBQueryOfflineMsgReplyLast{
  438. ICQMetadata: wire.ICQMetadata{
  439. UIN: instance.UIN(),
  440. ReqType: wire.ICQDBQueryOfflineMsgReplyLast,
  441. Seq: seq,
  442. },
  443. DroppedMessages: 0,
  444. },
  445. }
  446. if err := s.replyToSelf(ctx, instance, eofMsg, inFrame.RequestID, 0); err != nil {
  447. return fmt.Errorf("sending end of offline messages: %w", err)
  448. }
  449. return nil
  450. }
  451. func (s *ICQService) SetAffiliations(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations, seq uint16) error {
  452. if len(inBody.PastAffiliations) != 3 || len(inBody.Affiliations) != 3 {
  453. return fmt.Errorf("%w: expected 3 past affiliations and 3 affiliations", errICQBadRequest)
  454. }
  455. u := state.ICQAffiliations{
  456. PastCode1: inBody.PastAffiliations[0].Code,
  457. PastKeyword1: inBody.PastAffiliations[0].Keyword,
  458. PastCode2: inBody.PastAffiliations[1].Code,
  459. PastKeyword2: inBody.PastAffiliations[1].Keyword,
  460. PastCode3: inBody.PastAffiliations[2].Code,
  461. PastKeyword3: inBody.PastAffiliations[2].Keyword,
  462. CurrentCode1: inBody.Affiliations[0].Code,
  463. CurrentKeyword1: inBody.Affiliations[0].Keyword,
  464. CurrentCode2: inBody.Affiliations[1].Code,
  465. CurrentKeyword2: inBody.Affiliations[1].Keyword,
  466. CurrentCode3: inBody.Affiliations[2].Code,
  467. CurrentKeyword3: inBody.Affiliations[2].Keyword,
  468. }
  469. if err := s.userUpdater.SetAffiliations(ctx, instance.IdentScreenName(), u); err != nil {
  470. return err
  471. }
  472. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetAffiliations, inFrame.RequestID)
  473. }
  474. func (s *ICQService) SetBasicInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03EA_DBQueryMetaReqSetBasicInfo, seq uint16) error {
  475. u := state.ICQBasicInfo{
  476. CellPhone: inBody.CellPhone,
  477. CountryCode: inBody.CountryCode,
  478. EmailAddress: inBody.EmailAddress,
  479. FirstName: inBody.FirstName,
  480. GMTOffset: inBody.GMTOffset,
  481. Address: inBody.HomeAddress,
  482. City: inBody.City,
  483. Fax: inBody.Fax,
  484. Phone: inBody.Phone,
  485. State: inBody.State,
  486. LastName: inBody.LastName,
  487. Nickname: inBody.Nickname,
  488. PublishEmail: inBody.PublishEmail == wire.ICQUserFlagPublishEmailYes,
  489. ZIPCode: inBody.ZIP,
  490. }
  491. if err := s.userUpdater.SetBasicInfo(ctx, instance.IdentScreenName(), u); err != nil {
  492. return err
  493. }
  494. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetBasicInfo, inFrame.RequestID)
  495. }
  496. func (s *ICQService) SetEmails(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x040B_DBQueryMetaReqSetEmails, seq uint16) error {
  497. if len(inBody.Emails) > 0 {
  498. s.logger.Debug("adding additional emails is not yet supported")
  499. }
  500. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetEmails, inFrame.RequestID)
  501. }
  502. // SetICQInfo handles the TLV-based CLI_SET_FULLINFO (0x0C3A) request used to
  503. // update all profile information in a single packet. The TLV chain may
  504. // contain any subset of fields; omitted fields are preserved by loading the
  505. // current user record and overlaying only the values that were sent. Within a
  506. // multi-valued group (languages, interests, affiliations) any unspecified
  507. // slots are reset when at least one TLV in that group is present, matching
  508. // the way ICQLite tabs replace their entire group on save.
  509. func (s *ICQService) SetICQInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0C3A_DBQueryMetaReqSetFullInfo, seq uint16) error {
  510. user, err := s.userFinder.FindByUIN(ctx, instance.UIN())
  511. if err != nil {
  512. return fmt.Errorf("FindByUIN: %w", err)
  513. }
  514. // Multi-valued tags are collected in encounter order and applied to
  515. // their target struct after the loop.
  516. var langTLVs []uint8
  517. var interestTLVs, currentAffTLVs, pastAffTLVs [][]byte
  518. for _, tlv := range inBody.TLVList {
  519. switch tlv.Tag {
  520. // ----- ICQBasicInfo -----
  521. case wire.ICQTLVTagsFirstName:
  522. user.ICQInfo.Basic.FirstName = tlv.ICQString()
  523. case wire.ICQTLVTagsLastName:
  524. user.ICQInfo.Basic.LastName = tlv.ICQString()
  525. case wire.ICQTLVTagsNickname:
  526. user.ICQInfo.Basic.Nickname = tlv.ICQString()
  527. case wire.ICQTLVTagsEmail:
  528. var n struct {
  529. Email string `oscar:"len_prefix=uint16,nullterm"`
  530. Publish uint8
  531. }
  532. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  533. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  534. }
  535. user.ICQInfo.Basic.EmailAddress = n.Email
  536. // publish=0 means "publish my email", matching the
  537. // existing ICQUserFlagPublishEmailYes (0) convention.
  538. user.ICQInfo.Basic.PublishEmail = n.Publish == wire.ICQUserFlagPublishEmailYes
  539. case wire.ICQTLVTagsHomeCityName:
  540. user.ICQInfo.Basic.City = tlv.ICQString()
  541. case wire.ICQTLVTagsHomeStateAbbr:
  542. user.ICQInfo.Basic.State = tlv.ICQString()
  543. case wire.ICQTLVTagsHomeCountryCode:
  544. user.ICQInfo.Basic.CountryCode = tlv.Uint16LE()
  545. case wire.ICQTLVTagsHomeStreetAddress:
  546. user.ICQInfo.Basic.Address = tlv.ICQString()
  547. case wire.ICQTLVTagsHomeZipCode:
  548. user.ICQInfo.Basic.ZIPCode = strconv.FormatUint(uint64(tlv.Uint32LE()), 10)
  549. case wire.ICQTLVTagsHomePhoneNumber:
  550. user.ICQInfo.Basic.Phone = tlv.ICQString()
  551. case wire.ICQTLVTagsHomeFaxNumber:
  552. user.ICQInfo.Basic.Fax = tlv.ICQString()
  553. case wire.ICQTLVTagsHomeCellularPhoneNumber:
  554. user.ICQInfo.Basic.CellPhone = tlv.ICQString()
  555. case wire.ICQTLVTagsGMTOffset:
  556. user.ICQInfo.Basic.GMTOffset = tlv.Uint8()
  557. case wire.ICQTLVTagsOriginallyFromCity:
  558. user.ICQInfo.Basic.OriginallyFromCity = tlv.ICQString()
  559. case wire.ICQTLVTagsOriginallyFromState:
  560. user.ICQInfo.Basic.OriginallyFromState = tlv.ICQString()
  561. case wire.ICQTLVTagsOriginallyFromCountryCode:
  562. user.ICQInfo.Basic.OriginallyFromCountryCode = tlv.Uint16LE()
  563. // ----- ICQMoreInfo -----
  564. case wire.ICQTLVTagsGender:
  565. user.ICQInfo.More.Gender = uint16(tlv.Uint8())
  566. case wire.ICQTLVTagsSpokenLanguage:
  567. langTLVs = append(langTLVs, tlv.Uint8())
  568. case wire.ICQTLVTagsBirthdayInfo:
  569. var n struct {
  570. Year uint16
  571. Month uint16
  572. Day uint16
  573. }
  574. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  575. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  576. }
  577. user.ICQInfo.More.BirthYear = n.Year
  578. user.ICQInfo.More.BirthMonth = uint8(n.Month)
  579. user.ICQInfo.More.BirthDay = uint8(n.Day)
  580. case wire.ICQTLVTagsHomepageURL:
  581. user.ICQInfo.More.HomePageAddr = tlv.ICQString()
  582. // ----- ICQWorkInfo -----
  583. case wire.ICQTLVTagsWorkCompanyName:
  584. user.ICQInfo.Work.Company = tlv.ICQString()
  585. case wire.ICQTLVTagsWorkDepartmentName:
  586. user.ICQInfo.Work.Department = tlv.ICQString()
  587. case wire.ICQTLVTagsWorkPositionTitle:
  588. user.ICQInfo.Work.Position = tlv.ICQString()
  589. case wire.ICQTLVTagsWorkOccupationCode:
  590. user.ICQInfo.Work.OccupationCode = tlv.Uint16LE()
  591. case wire.ICQTLVTagsWorkStreetAddress:
  592. user.ICQInfo.Work.Address = tlv.ICQString()
  593. case wire.ICQTLVTagsWorkCityName:
  594. user.ICQInfo.Work.City = tlv.ICQString()
  595. case wire.ICQTLVTagsWorkStateName:
  596. user.ICQInfo.Work.State = tlv.ICQString()
  597. case wire.ICQTLVTagsWorkCountryCode:
  598. user.ICQInfo.Work.CountryCode = tlv.Uint16LE()
  599. case wire.ICQTLVTagsWorkZipCode:
  600. user.ICQInfo.Work.ZIPCode = strconv.FormatUint(uint64(tlv.Uint32LE()), 10)
  601. case wire.ICQTLVTagsWorkPhoneNumber:
  602. user.ICQInfo.Work.Phone = tlv.ICQString()
  603. case wire.ICQTLVTagsWorkFaxNumber:
  604. user.ICQInfo.Work.Fax = tlv.ICQString()
  605. case wire.ICQTLVTagsWorkWebpageURL:
  606. user.ICQInfo.Work.WebPage = tlv.ICQString()
  607. // ----- ICQPermissions -----
  608. case wire.ICQTLVTagsAuthorizationPermissions:
  609. // Matches the wire-struct convention used by SetPermissions:
  610. // authorization == 0 means "authorization required".
  611. user.ICQInfo.Permissions.AuthRequired = tlv.Uint8() == 0
  612. case wire.ICQTLVTagsShowWebStatusPermissions:
  613. user.ICQInfo.Permissions.WebAware = tlv.Uint8() == 1
  614. // ----- ICQUserNotes -----
  615. case wire.ICQTLVTagsNotesText:
  616. user.ICQInfo.Notes.Notes = tlv.ICQString()
  617. // ----- ICQInterests -----
  618. case wire.ICQTLVTagsInterestsNode: // may appear multiple times
  619. interestTLVs = append(interestTLVs, tlv.Value)
  620. // ----- ICQAffiliations -----
  621. case wire.ICQTLVTagsAffiliationsNode: // may appear multiple times
  622. currentAffTLVs = append(currentAffTLVs, tlv.Value)
  623. case wire.ICQTLVTagsPastInfoNode: // may appear multiple times
  624. pastAffTLVs = append(pastAffTLVs, tlv.Value)
  625. // ----- ICQHomepageCategory -----
  626. case wire.ICQTLVTagsHomepageCategoryKeywords:
  627. var n struct {
  628. Index uint16
  629. Description string `oscar:"len_prefix=uint16,nullterm"`
  630. }
  631. if err := wire.UnmarshalLE(&n, bytes.NewReader(tlv.Value)); err != nil {
  632. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  633. }
  634. user.ICQInfo.HomepageCategory.Index = n.Index
  635. user.ICQInfo.HomepageCategory.Description = n.Description
  636. user.ICQInfo.HomepageCategory.Enabled = true
  637. default:
  638. // Search-only or otherwise unmapped tags
  639. // (UIN, Age, AgeRangeSearch, WhitepagesSearchKeywords,
  640. // SearchOnlineUsersFlag).
  641. s.logger.Debug("ICQ SetICQInfo: ignoring unsupported TLV",
  642. "tag", fmt.Sprintf("0x%04X", tlv.Tag),
  643. "len", len(tlv.Value))
  644. }
  645. }
  646. if len(langTLVs) > 0 {
  647. user.ICQInfo.More.Lang1, user.ICQInfo.More.Lang2, user.ICQInfo.More.Lang3 = 0, 0, 0
  648. for i, v := range langTLVs {
  649. switch i {
  650. case 0:
  651. user.ICQInfo.More.Lang1 = v
  652. case 1:
  653. user.ICQInfo.More.Lang2 = v
  654. case 2:
  655. user.ICQInfo.More.Lang3 = v
  656. }
  657. }
  658. }
  659. if len(interestTLVs) > 0 {
  660. user.ICQInfo.Interests = state.ICQInterests{}
  661. for i, raw := range interestTLVs {
  662. var n struct {
  663. Code uint16
  664. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  665. }
  666. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  667. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  668. }
  669. switch i {
  670. case 0:
  671. user.ICQInfo.Interests.Code1, user.ICQInfo.Interests.Keyword1 = n.Code, n.Keyword
  672. case 1:
  673. user.ICQInfo.Interests.Code2, user.ICQInfo.Interests.Keyword2 = n.Code, n.Keyword
  674. case 2:
  675. user.ICQInfo.Interests.Code3, user.ICQInfo.Interests.Keyword3 = n.Code, n.Keyword
  676. case 3:
  677. user.ICQInfo.Interests.Code4, user.ICQInfo.Interests.Keyword4 = n.Code, n.Keyword
  678. }
  679. }
  680. }
  681. if len(currentAffTLVs) > 0 {
  682. user.ICQInfo.Affiliations.CurrentCode1, user.ICQInfo.Affiliations.CurrentKeyword1 = 0, ""
  683. user.ICQInfo.Affiliations.CurrentCode2, user.ICQInfo.Affiliations.CurrentKeyword2 = 0, ""
  684. user.ICQInfo.Affiliations.CurrentCode3, user.ICQInfo.Affiliations.CurrentKeyword3 = 0, ""
  685. for i, raw := range currentAffTLVs {
  686. var n struct {
  687. Code uint16
  688. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  689. }
  690. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  691. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  692. }
  693. switch i {
  694. case 0:
  695. user.ICQInfo.Affiliations.CurrentCode1, user.ICQInfo.Affiliations.CurrentKeyword1 = n.Code, n.Keyword
  696. case 1:
  697. user.ICQInfo.Affiliations.CurrentCode2, user.ICQInfo.Affiliations.CurrentKeyword2 = n.Code, n.Keyword
  698. case 2:
  699. user.ICQInfo.Affiliations.CurrentCode3, user.ICQInfo.Affiliations.CurrentKeyword3 = n.Code, n.Keyword
  700. }
  701. }
  702. }
  703. if len(pastAffTLVs) > 0 {
  704. user.ICQInfo.Affiliations.PastCode1, user.ICQInfo.Affiliations.PastKeyword1 = 0, ""
  705. user.ICQInfo.Affiliations.PastCode2, user.ICQInfo.Affiliations.PastKeyword2 = 0, ""
  706. user.ICQInfo.Affiliations.PastCode3, user.ICQInfo.Affiliations.PastKeyword3 = 0, ""
  707. for i, raw := range pastAffTLVs {
  708. var n struct {
  709. Code uint16
  710. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  711. }
  712. if err := wire.UnmarshalLE(&n, bytes.NewReader(raw)); err != nil {
  713. return fmt.Errorf("wire.UnmarshalLE: %w", err)
  714. }
  715. switch i {
  716. case 0:
  717. user.ICQInfo.Affiliations.PastCode1, user.ICQInfo.Affiliations.PastKeyword1 = n.Code, n.Keyword
  718. case 1:
  719. user.ICQInfo.Affiliations.PastCode2, user.ICQInfo.Affiliations.PastKeyword2 = n.Code, n.Keyword
  720. case 2:
  721. user.ICQInfo.Affiliations.PastCode3, user.ICQInfo.Affiliations.PastKeyword3 = n.Code, n.Keyword
  722. }
  723. }
  724. }
  725. name := instance.IdentScreenName()
  726. if err := s.userUpdater.SetICQInfo(ctx, name, user.ICQInfo); err != nil {
  727. return err
  728. }
  729. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetFullInfo, inFrame.RequestID)
  730. }
  731. func (s *ICQService) SetICQPhone(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0654_DBQueryMetaReqSetICQPhone, seq uint16) error {
  732. s.logger.Debug("received SetICQPhone request")
  733. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetICQPhone, inFrame.RequestID)
  734. }
  735. func (s *ICQService) SetInterests(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0410_DBQueryMetaReqSetInterests, seq uint16) error {
  736. if len(inBody.Interests) != 4 {
  737. return fmt.Errorf("%w: expected 4 interests", errICQBadRequest)
  738. }
  739. u := state.ICQInterests{
  740. Code1: inBody.Interests[0].Code,
  741. Keyword1: inBody.Interests[0].Keyword,
  742. Code2: inBody.Interests[1].Code,
  743. Keyword2: inBody.Interests[1].Keyword,
  744. Code3: inBody.Interests[2].Code,
  745. Keyword3: inBody.Interests[2].Keyword,
  746. Code4: inBody.Interests[3].Code,
  747. Keyword4: inBody.Interests[3].Keyword,
  748. }
  749. if err := s.userUpdater.SetInterests(ctx, instance.IdentScreenName(), u); err != nil {
  750. return err
  751. }
  752. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetInterests, inFrame.RequestID)
  753. }
  754. func (s *ICQService) SetMoreInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03FD_DBQueryMetaReqSetMoreInfo, seq uint16) error {
  755. u := state.ICQMoreInfo{
  756. Gender: inBody.Gender,
  757. HomePageAddr: inBody.HomePageAddr,
  758. BirthYear: inBody.BirthYear,
  759. BirthMonth: inBody.BirthMonth,
  760. BirthDay: inBody.BirthDay,
  761. Lang1: inBody.Lang1,
  762. Lang2: inBody.Lang2,
  763. Lang3: inBody.Lang3,
  764. }
  765. if err := s.userUpdater.SetMoreInfo(ctx, instance.IdentScreenName(), u); err != nil {
  766. return err
  767. }
  768. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetMoreInfo, inFrame.RequestID)
  769. }
  770. // SetPermissions persists ICQ privacy flags. AuthRequired controls whether other
  771. // users need authorization to add this user; when true, contact pre-authorization
  772. // can allow specific users to add without a new prompt.
  773. func (s *ICQService) SetPermissions(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0424_DBQueryMetaReqSetPermissions, seq uint16) error {
  774. u := state.ICQPermissions{
  775. AuthRequired: inBody.Authorization == 0,
  776. WebAware: inBody.WebAware == 1,
  777. }
  778. if err := s.userUpdater.SetPermissions(ctx, instance.IdentScreenName(), u); err != nil {
  779. return err
  780. }
  781. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetPermissions, inFrame.RequestID)
  782. }
  783. func (s *ICQService) SetUserNotes(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes, seq uint16) error {
  784. u := state.ICQUserNotes{
  785. Notes: inBody.Notes,
  786. }
  787. if err := s.userUpdater.SetUserNotes(ctx, instance.IdentScreenName(), u); err != nil {
  788. return err
  789. }
  790. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetNotes, inFrame.RequestID)
  791. }
  792. func (s *ICQService) SetWorkInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo, seq uint16) error {
  793. icqWorkInfo := state.ICQWorkInfo{
  794. Company: inBody.Company,
  795. Department: inBody.Department,
  796. OccupationCode: inBody.OccupationCode,
  797. Position: inBody.Position,
  798. Address: inBody.Address,
  799. City: inBody.City,
  800. CountryCode: inBody.CountryCode,
  801. Fax: inBody.Fax,
  802. Phone: inBody.Phone,
  803. State: inBody.State,
  804. WebPage: inBody.WebPage,
  805. ZIPCode: inBody.ZIP,
  806. }
  807. if err := s.userUpdater.SetWorkInfo(ctx, instance.IdentScreenName(), icqWorkInfo); err != nil {
  808. return err
  809. }
  810. return s.reqAck(ctx, instance, seq, wire.ICQDBQueryMetaReplySetWorkInfo, inFrame.RequestID)
  811. }
  812. func (s *ICQService) ShortUserInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x04BA_DBQueryMetaReqShortInfo, seq uint16) error {
  813. user, err := s.userFinder.FindByUIN(ctx, inBody.UIN)
  814. if err != nil {
  815. if errors.Is(err, state.ErrNoUser) {
  816. msg := wire.ICQMessageReplyEnvelope{
  817. Message: wire.ICQ_0x07DA_0x0104_DBQueryMetaReplyShortInfo{
  818. ICQMetadata: wire.ICQMetadata{
  819. UIN: instance.UIN(),
  820. ReqType: wire.ICQDBQueryMetaReply,
  821. Seq: seq,
  822. },
  823. ReqSubType: wire.ICQDBQueryMetaReplyShortInfo,
  824. Success: wire.ICQStatusCodeErr,
  825. },
  826. }
  827. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  828. }
  829. return err
  830. }
  831. info := wire.ICQ_0x07DA_0x0104_DBQueryMetaReplyShortInfo{
  832. ICQMetadata: wire.ICQMetadata{
  833. UIN: instance.UIN(),
  834. ReqType: wire.ICQDBQueryMetaReply,
  835. Seq: seq,
  836. },
  837. ReqSubType: wire.ICQDBQueryMetaReplyShortInfo,
  838. Success: wire.ICQStatusCodeOK,
  839. Nickname: user.ICQInfo.Basic.Nickname,
  840. FirstName: user.ICQInfo.Basic.FirstName,
  841. LastName: user.ICQInfo.Basic.LastName,
  842. Email: user.ICQInfo.Basic.EmailAddress,
  843. Gender: uint8(user.ICQInfo.More.Gender),
  844. }
  845. if !user.ICQInfo.Permissions.AuthRequired {
  846. info.Authorization = 1
  847. }
  848. msg := wire.ICQMessageReplyEnvelope{
  849. Message: info,
  850. }
  851. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  852. }
  853. func (s *ICQService) XMLReqData(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.ICQ_0x07D0_0x0898_DBQueryMetaReqXMLReq, seq uint16) error {
  854. msg := wire.ICQMessageReplyEnvelope{
  855. Message: wire.ICQ_0x07DA_0x08A2_DBQueryMetaReplyXMLData{
  856. ICQMetadata: wire.ICQMetadata{
  857. UIN: instance.UIN(),
  858. ReqType: wire.ICQDBQueryMetaReply,
  859. Seq: seq,
  860. },
  861. ReqSubType: wire.ICQDBQueryMetaReplyXMLData,
  862. Success: wire.ICQStatusCodeErr,
  863. },
  864. }
  865. return s.reply(ctx, instance, msg, inFrame.RequestID, 0)
  866. }
  867. func (s *ICQService) userInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  868. userInfo := wire.ICQ_0x07DA_0x00C8_DBQueryMetaReplyBasicInfo{
  869. ICQMetadata: wire.ICQMetadata{
  870. UIN: instance.UIN(),
  871. ReqType: wire.ICQDBQueryMetaReply,
  872. Seq: seq,
  873. },
  874. ReqSubType: wire.ICQDBQueryMetaReplyBasicInfo,
  875. Success: wire.ICQStatusCodeOK,
  876. Nickname: user.ICQInfo.Basic.Nickname,
  877. FirstName: user.ICQInfo.Basic.FirstName,
  878. LastName: user.ICQInfo.Basic.LastName,
  879. Email: user.ICQInfo.Basic.EmailAddress,
  880. City: user.ICQInfo.Basic.City,
  881. State: user.ICQInfo.Basic.State,
  882. Phone: user.ICQInfo.Basic.Phone,
  883. Fax: user.ICQInfo.Basic.Fax,
  884. Address: user.ICQInfo.Basic.Address,
  885. CellPhone: user.ICQInfo.Basic.CellPhone,
  886. ZIP: user.ICQInfo.Basic.ZIPCode,
  887. CountryCode: user.ICQInfo.Basic.CountryCode,
  888. GMTOffset: user.ICQInfo.Basic.GMTOffset,
  889. AuthFlag: 0, // required by default
  890. WebAware: 1,
  891. DCPerms: 0,
  892. }
  893. if !user.ICQInfo.Permissions.AuthRequired {
  894. userInfo.AuthFlag = 1
  895. }
  896. if user.ICQInfo.Permissions.WebAware {
  897. userInfo.WebAware = 1
  898. } else {
  899. userInfo.WebAware = 0
  900. }
  901. if user.ICQInfo.Basic.PublishEmail {
  902. userInfo.PublishEmail = wire.ICQUserFlagPublishEmailYes
  903. } else {
  904. userInfo.PublishEmail = wire.ICQUserFlagPublishEmailNo
  905. }
  906. msg := wire.ICQMessageReplyEnvelope{
  907. Message: userInfo,
  908. }
  909. return s.reply(ctx, instance, msg, requestID, flags)
  910. }
  911. func (s *ICQService) moreUserInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  912. msg := wire.ICQMessageReplyEnvelope{
  913. Message: wire.ICQ_0x07DA_0x00DC_DBQueryMetaReplyMoreInfo{
  914. ICQMetadata: wire.ICQMetadata{
  915. UIN: instance.UIN(),
  916. ReqType: wire.ICQDBQueryMetaReply,
  917. Seq: seq,
  918. },
  919. ReqSubType: wire.ICQDBQueryMetaReplyMoreInfo,
  920. Success: wire.ICQStatusCodeOK,
  921. Age: uint16(user.Age(s.timeNow)),
  922. Gender: uint8(user.ICQInfo.More.Gender),
  923. HomePageAddr: user.ICQInfo.More.HomePageAddr,
  924. BirthYear: user.ICQInfo.More.BirthYear,
  925. BirthMonth: user.ICQInfo.More.BirthMonth,
  926. BirthDay: user.ICQInfo.More.BirthDay,
  927. Lang1: user.ICQInfo.More.Lang1,
  928. Lang2: user.ICQInfo.More.Lang2,
  929. Lang3: user.ICQInfo.More.Lang3,
  930. City: user.ICQInfo.Basic.City,
  931. State: user.ICQInfo.Basic.State,
  932. CountryCode: user.ICQInfo.Basic.CountryCode,
  933. TimeZone: user.ICQInfo.Basic.GMTOffset,
  934. },
  935. }
  936. return s.reply(ctx, instance, msg, requestID, flags)
  937. }
  938. func (s *ICQService) extraEmails(ctx context.Context, instance *state.SessionInstance, requestID uint32, seq uint16, flags uint16) error {
  939. msg := wire.ICQMessageReplyEnvelope{
  940. Message: wire.ICQ_0x07DA_0x00EB_DBQueryMetaReplyExtEmailInfo{
  941. ICQMetadata: wire.ICQMetadata{
  942. UIN: instance.UIN(),
  943. ReqType: wire.ICQDBQueryMetaReply,
  944. Seq: seq,
  945. },
  946. ReqSubType: wire.ICQDBQueryMetaReplyExtEmailInfo,
  947. Success: wire.ICQStatusCodeOK,
  948. },
  949. }
  950. return s.reply(ctx, instance, msg, requestID, flags)
  951. }
  952. func (s *ICQService) homepageCat(ctx context.Context, instance *state.SessionInstance, requestID uint32, seq uint16, flags uint16) error {
  953. msg := wire.ICQMessageReplyEnvelope{
  954. Message: wire.ICQ_0x07DA_0x010E_DBQueryMetaReplyHomePageCat{
  955. ICQMetadata: wire.ICQMetadata{
  956. UIN: instance.UIN(),
  957. ReqType: wire.ICQDBQueryMetaReply,
  958. Seq: seq,
  959. },
  960. ReqSubType: wire.ICQDBQueryMetaReplyHomePageCat,
  961. Success: wire.ICQStatusCodeOK,
  962. },
  963. }
  964. return s.reply(ctx, instance, msg, requestID, flags)
  965. }
  966. func (s *ICQService) workInfo(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  967. msg := wire.ICQMessageReplyEnvelope{
  968. Message: wire.ICQ_0x07DA_0x00D2_DBQueryMetaReplyWorkInfo{
  969. ICQMetadata: wire.ICQMetadata{
  970. UIN: instance.UIN(),
  971. ReqType: wire.ICQDBQueryMetaReply,
  972. Seq: seq,
  973. },
  974. ReqSubType: wire.ICQDBQueryMetaReplyWorkInfo,
  975. Success: wire.ICQStatusCodeOK,
  976. ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo: wire.ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo{
  977. City: user.ICQInfo.Work.City,
  978. State: user.ICQInfo.Work.State,
  979. Phone: user.ICQInfo.Work.Phone,
  980. Fax: user.ICQInfo.Work.Fax,
  981. Address: user.ICQInfo.Work.Address,
  982. ZIP: user.ICQInfo.Work.ZIPCode,
  983. CountryCode: user.ICQInfo.Work.CountryCode,
  984. Company: user.ICQInfo.Work.Company,
  985. Department: user.ICQInfo.Work.Department,
  986. Position: user.ICQInfo.Work.Position,
  987. OccupationCode: user.ICQInfo.Work.OccupationCode,
  988. WebPage: user.ICQInfo.Work.WebPage,
  989. },
  990. },
  991. }
  992. return s.reply(ctx, instance, msg, requestID, flags)
  993. }
  994. func (s *ICQService) notes(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  995. msg := wire.ICQMessageReplyEnvelope{
  996. Message: wire.ICQ_0x07DA_0x00E6_DBQueryMetaReplyNotes{
  997. ICQMetadata: wire.ICQMetadata{
  998. UIN: instance.UIN(),
  999. ReqType: wire.ICQDBQueryMetaReply,
  1000. Seq: seq,
  1001. },
  1002. ReqSubType: wire.ICQDBQueryMetaReplyNotes,
  1003. Success: wire.ICQStatusCodeOK,
  1004. ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes: wire.ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes{
  1005. Notes: user.ICQInfo.Notes.Notes,
  1006. },
  1007. },
  1008. }
  1009. return s.reply(ctx, instance, msg, requestID, flags)
  1010. }
  1011. func (s *ICQService) interests(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  1012. msg := wire.ICQMessageReplyEnvelope{
  1013. Message: wire.ICQ_0x07DA_0x00F0_DBQueryMetaReplyInterests{
  1014. ICQMetadata: wire.ICQMetadata{
  1015. UIN: instance.UIN(),
  1016. ReqType: wire.ICQDBQueryMetaReply,
  1017. Seq: seq,
  1018. },
  1019. ReqSubType: wire.ICQDBQueryMetaReplyInterests,
  1020. Success: wire.ICQStatusCodeOK,
  1021. Interests: []struct {
  1022. Code uint16
  1023. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1024. }{
  1025. {
  1026. Code: user.ICQInfo.Interests.Code1,
  1027. Keyword: user.ICQInfo.Interests.Keyword1,
  1028. },
  1029. {
  1030. Code: user.ICQInfo.Interests.Code2,
  1031. Keyword: user.ICQInfo.Interests.Keyword2,
  1032. },
  1033. {
  1034. Code: user.ICQInfo.Interests.Code3,
  1035. Keyword: user.ICQInfo.Interests.Keyword3,
  1036. },
  1037. {
  1038. Code: user.ICQInfo.Interests.Code4,
  1039. Keyword: user.ICQInfo.Interests.Keyword4,
  1040. },
  1041. },
  1042. },
  1043. }
  1044. return s.reply(ctx, instance, msg, requestID, flags)
  1045. }
  1046. func (s *ICQService) affiliations(ctx context.Context, instance *state.SessionInstance, user state.User, requestID uint32, seq uint16, flags uint16) error {
  1047. msg := wire.ICQMessageReplyEnvelope{
  1048. Message: wire.ICQ_0x07DA_0x00FA_DBQueryMetaReplyAffiliations{
  1049. ICQMetadata: wire.ICQMetadata{
  1050. UIN: instance.UIN(),
  1051. ReqType: wire.ICQDBQueryMetaReply,
  1052. Seq: seq,
  1053. },
  1054. ReqSubType: wire.ICQDBQueryMetaReplyAffiliations,
  1055. Success: wire.ICQStatusCodeOK,
  1056. ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations: wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations{
  1057. PastAffiliations: []struct {
  1058. Code uint16
  1059. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1060. }{
  1061. {
  1062. Code: user.ICQInfo.Affiliations.PastCode1,
  1063. Keyword: user.ICQInfo.Affiliations.PastKeyword1,
  1064. },
  1065. {
  1066. Code: user.ICQInfo.Affiliations.PastCode2,
  1067. Keyword: user.ICQInfo.Affiliations.PastKeyword2,
  1068. },
  1069. {
  1070. Code: user.ICQInfo.Affiliations.PastCode3,
  1071. Keyword: user.ICQInfo.Affiliations.PastKeyword3,
  1072. },
  1073. },
  1074. Affiliations: []struct {
  1075. Code uint16
  1076. Keyword string `oscar:"len_prefix=uint16,nullterm"`
  1077. }{
  1078. {
  1079. Code: user.ICQInfo.Affiliations.CurrentCode1,
  1080. Keyword: user.ICQInfo.Affiliations.CurrentKeyword1,
  1081. },
  1082. {
  1083. Code: user.ICQInfo.Affiliations.CurrentCode2,
  1084. Keyword: user.ICQInfo.Affiliations.CurrentKeyword2,
  1085. },
  1086. {
  1087. Code: user.ICQInfo.Affiliations.CurrentCode3,
  1088. Keyword: user.ICQInfo.Affiliations.CurrentKeyword3,
  1089. },
  1090. },
  1091. },
  1092. },
  1093. }
  1094. return s.reply(ctx, instance, msg, requestID, flags)
  1095. }
  1096. func (s *ICQService) reply(ctx context.Context, instance *state.SessionInstance, message wire.ICQMessageReplyEnvelope, requestID uint32, snacFlags uint16) error {
  1097. s.messageRelayer.RelayToScreenName(ctx, instance.IdentScreenName(), replySNAC(message, requestID, snacFlags))
  1098. return nil
  1099. }
  1100. // replyToSelf answers the instance that made the request rather than every
  1101. // instance of the account.
  1102. func (s *ICQService) replyToSelf(ctx context.Context, instance *state.SessionInstance, message wire.ICQMessageReplyEnvelope, requestID uint32, snacFlags uint16) error {
  1103. s.messageRelayer.RelayToSelf(ctx, instance, replySNAC(message, requestID, snacFlags))
  1104. return nil
  1105. }
  1106. // replySNAC wraps an ICQ metadata payload in a DB reply SNAC.
  1107. func replySNAC(message wire.ICQMessageReplyEnvelope, requestID uint32, snacFlags uint16) wire.SNACMessage {
  1108. return wire.SNACMessage{
  1109. Frame: wire.SNACFrame{
  1110. FoodGroup: wire.ICQ,
  1111. SubGroup: wire.ICQDBReply,
  1112. RequestID: requestID,
  1113. Flags: snacFlags,
  1114. },
  1115. Body: wire.SNAC_0x15_0x02_DBReply{
  1116. TLVRestBlock: wire.TLVRestBlock{
  1117. TLVList: wire.TLVList{
  1118. wire.NewTLVBE(wire.ICQTLVTagsMetadata, message),
  1119. },
  1120. },
  1121. },
  1122. }
  1123. }
  1124. func (s *ICQService) replySearchHit(ctx context.Context, instance *state.SessionInstance, record wire.ICQUserSearchRecord, requestID uint32, last bool, seq uint16) error {
  1125. var resp any
  1126. var more uint16
  1127. if last {
  1128. resp = wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1129. ICQMetadata: wire.ICQMetadata{
  1130. UIN: instance.UIN(),
  1131. ReqType: wire.ICQDBQueryMetaReply,
  1132. Seq: seq,
  1133. },
  1134. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1135. Success: wire.ICQStatusCodeOK,
  1136. Details: record,
  1137. UsersLeft: 0,
  1138. }
  1139. } else {
  1140. resp = wire.ICQ_0x07DA_0x01A4_DBQueryMetaReplyUserFound{
  1141. ICQMetadata: wire.ICQMetadata{
  1142. UIN: instance.UIN(),
  1143. ReqType: wire.ICQDBQueryMetaReply,
  1144. Seq: seq,
  1145. },
  1146. ReqSubType: wire.ICQDBQueryMetaReplyUserFound,
  1147. Success: wire.ICQStatusCodeOK,
  1148. Details: record,
  1149. }
  1150. more = wire.SNACFlagsMoreToCome
  1151. }
  1152. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{Message: resp}, requestID, more)
  1153. }
  1154. func (s *ICQService) reportSearchFailure(ctx context.Context, instance *state.SessionInstance, seq uint16, requestID uint32) error {
  1155. resp := wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1156. ICQMetadata: wire.ICQMetadata{
  1157. UIN: instance.UIN(),
  1158. ReqType: wire.ICQDBQueryMetaReply,
  1159. Seq: seq,
  1160. },
  1161. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1162. Success: wire.ICQStatusCodeErr,
  1163. }
  1164. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{
  1165. Message: resp,
  1166. }, requestID, 0)
  1167. }
  1168. func (s *ICQService) reportNoSearchResults(ctx context.Context, instance *state.SessionInstance, seq uint16, requestID uint32) error {
  1169. resp := wire.ICQ_0x07DA_0x01AE_DBQueryMetaReplyLastUserFound{
  1170. ICQMetadata: wire.ICQMetadata{
  1171. UIN: instance.UIN(),
  1172. ReqType: wire.ICQDBQueryMetaReply,
  1173. Seq: seq,
  1174. },
  1175. ReqSubType: wire.ICQDBQueryMetaReplyLastUserFound,
  1176. Success: wire.ICQStatusCodeFail,
  1177. }
  1178. return s.reply(ctx, instance, wire.ICQMessageReplyEnvelope{
  1179. Message: resp,
  1180. }, requestID, 0)
  1181. }
  1182. func (s *ICQService) reqAck(ctx context.Context, instance *state.SessionInstance, seq uint16, subType uint16, requestID uint32) error {
  1183. msg := wire.ICQMessageReplyEnvelope{
  1184. Message: wire.ICQ_0x07DA_0x00DC_DBQueryMetaReplyMoreInfo{
  1185. ICQMetadata: wire.ICQMetadata{
  1186. UIN: instance.UIN(),
  1187. ReqType: wire.ICQDBQueryMetaReply,
  1188. Seq: seq,
  1189. },
  1190. ReqSubType: subType,
  1191. Success: wire.ICQStatusCodeOK,
  1192. },
  1193. }
  1194. return s.reply(ctx, instance, msg, requestID, 0)
  1195. }
  1196. func (s *ICQService) createResult(res state.User) wire.ICQUserSearchRecord {
  1197. uin, _ := strconv.Atoi(res.IdentScreenName.String())
  1198. searchRecord := wire.ICQUserSearchRecord{
  1199. UIN: uint32(uin),
  1200. Nickname: res.ICQInfo.Basic.Nickname,
  1201. FirstName: res.ICQInfo.Basic.FirstName,
  1202. LastName: res.ICQInfo.Basic.LastName,
  1203. Email: res.ICQInfo.Basic.EmailAddress,
  1204. Gender: uint8(res.ICQInfo.More.Gender),
  1205. Age: res.Age(s.timeNow),
  1206. }
  1207. if !res.ICQInfo.Permissions.AuthRequired {
  1208. searchRecord.Authorization = 1
  1209. }
  1210. userSess := s.sessionRetriever.RetrieveSession(res.IdentScreenName)
  1211. if userSess != nil {
  1212. searchRecord.OnlineStatus = 1
  1213. }
  1214. return searchRecord
  1215. }