user_store.go 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699
  1. package state
  2. import (
  3. "bytes"
  4. "context"
  5. "database/sql"
  6. "embed"
  7. "encoding/hex"
  8. "errors"
  9. "fmt"
  10. "io/fs"
  11. "math"
  12. "net/http"
  13. "net/mail"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/golang-migrate/migrate/v4"
  18. migratesqlite "github.com/golang-migrate/migrate/v4/database/sqlite"
  19. "github.com/golang-migrate/migrate/v4/source/httpfs"
  20. "modernc.org/sqlite"
  21. lib "modernc.org/sqlite/lib"
  22. "github.com/mk6i/open-oscar-server/wire"
  23. )
  24. const offlineInboxLimit = 10
  25. var (
  26. ErrKeywordCategoryExists = errors.New("keyword category already exists")
  27. ErrKeywordCategoryNotFound = errors.New("keyword category not found")
  28. ErrBARTItemExists = errors.New("BART asset already exists")
  29. ErrBARTItemNotFound = errors.New("BART asset not found")
  30. ErrKeywordExists = errors.New("keyword already exists")
  31. ErrKeywordInUse = errors.New("can't delete keyword that is associated with a user")
  32. ErrKeywordNotFound = errors.New("keyword not found")
  33. ErrOfflineInboxFull = errors.New("offline inbox full")
  34. errTooManyCategories = errors.New("there are too many keyword categories")
  35. errTooManyKeywords = errors.New("there are too many keywords")
  36. // ErrICQSearchEmptyCriteria indicates the caller did not provide any search
  37. // constraints. This prevents accidental full-table scans.
  38. ErrICQSearchEmptyCriteria = errors.New("ICQ search criteria is empty")
  39. )
  40. //go:embed migrations/*
  41. var migrations embed.FS
  42. // SQLiteUserStore stores user feedbag (buddy list), profile, and
  43. // authentication credentials information in a SQLite database.
  44. type SQLiteUserStore struct {
  45. db *sql.DB
  46. }
  47. // NewSQLiteUserStore creates a new instance of SQLiteUserStore. If the
  48. // database does not already exist, a new one is created with the required
  49. // schema.
  50. func NewSQLiteUserStore(dbFilePath string) (*SQLiteUserStore, error) {
  51. db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_pragma=foreign_keys=on", dbFilePath))
  52. if err != nil {
  53. return nil, err
  54. }
  55. // Set the maximum number of open connections to 1.
  56. // This is crucial to prevent SQLITE_BUSY errors, which occur when the database
  57. // is locked due to concurrent access. By limiting the number of open connections
  58. // to 1, we ensure that all database operations are serialized, thus avoiding
  59. // any potential locking issues.
  60. db.SetMaxOpenConns(1)
  61. store := &SQLiteUserStore{db: db}
  62. if err := store.runMigrations(); err != nil {
  63. return nil, fmt.Errorf("failed to run migrations: %w", err)
  64. }
  65. return store, nil
  66. }
  67. func (f SQLiteUserStore) runMigrations() error {
  68. migrationFS, err := fs.Sub(migrations, "migrations")
  69. if err != nil {
  70. return fmt.Errorf("failed to prepare migration subdirectory: %v", err)
  71. }
  72. sourceInstance, err := httpfs.New(http.FS(migrationFS), ".")
  73. if err != nil {
  74. return fmt.Errorf("failed to create source instance from embedded filesystem: %v", err)
  75. }
  76. driver, err := migratesqlite.WithInstance(f.db, &migratesqlite.Config{})
  77. if err != nil {
  78. return fmt.Errorf("cannot create database driver: %v", err)
  79. }
  80. m, err := migrate.NewWithInstance("httpfs", sourceInstance, "sqlite", driver)
  81. if err != nil {
  82. return fmt.Errorf("failed to create migrate instance: %v", err)
  83. }
  84. if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
  85. return fmt.Errorf("failed to run migrations: %v", err)
  86. }
  87. return nil
  88. }
  89. func (f SQLiteUserStore) AllUsers(ctx context.Context) ([]User, error) {
  90. q := `SELECT identScreenName, displayScreenName, isICQ, isBot FROM users`
  91. rows, err := f.db.QueryContext(ctx, q)
  92. if err != nil {
  93. return nil, err
  94. }
  95. defer rows.Close()
  96. var users []User
  97. for rows.Next() {
  98. var identSN, displaySN string
  99. var isICQ, isBot bool
  100. if err := rows.Scan(&identSN, &displaySN, &isICQ, &isBot); err != nil {
  101. return nil, err
  102. }
  103. users = append(users, User{
  104. IdentScreenName: NewIdentScreenName(identSN),
  105. DisplayScreenName: DisplayScreenName(displaySN),
  106. IsICQ: isICQ,
  107. IsBot: isBot,
  108. })
  109. }
  110. if err := rows.Err(); err != nil {
  111. return nil, err
  112. }
  113. return users, nil
  114. }
  115. func (f SQLiteUserStore) FindByUIN(ctx context.Context, UIN uint32) (User, error) {
  116. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{strconv.Itoa(int(UIN))})
  117. if err != nil {
  118. return User{}, fmt.Errorf("FindByUIN: %w", err)
  119. }
  120. if len(users) == 0 {
  121. return User{}, ErrNoUser
  122. }
  123. return users[0], nil
  124. }
  125. func (f SQLiteUserStore) FindByICQEmail(ctx context.Context, email string) (User, error) {
  126. users, err := f.queryUsers(ctx, `icq_basicInfo_emailAddress = ?`, []any{email})
  127. if err != nil {
  128. return User{}, fmt.Errorf("FindByICQEmail: %w", err)
  129. }
  130. if len(users) == 0 {
  131. return User{}, ErrNoUser
  132. }
  133. return users[0], nil
  134. }
  135. func (f SQLiteUserStore) FindByAIMEmail(ctx context.Context, email string) (User, error) {
  136. users, err := f.queryUsers(ctx, `emailAddress = ?`, []any{email})
  137. if err != nil {
  138. return User{}, fmt.Errorf("FindByAIMEmail: %w", err)
  139. }
  140. if len(users) == 0 {
  141. return User{}, ErrNoUser
  142. }
  143. return users[0], nil
  144. }
  145. func (f SQLiteUserStore) FindByAIMKeyword(ctx context.Context, keyword string) ([]User, error) {
  146. where := `
  147. (SELECT id FROM aimKeyword WHERE name = ?) IN
  148. (aim_keyword1, aim_keyword2, aim_keyword3, aim_keyword4, aim_keyword5)
  149. `
  150. users, err := f.queryUsers(ctx, where, []any{keyword})
  151. if err != nil {
  152. return nil, err
  153. }
  154. return users, nil
  155. }
  156. func (f SQLiteUserStore) FindByICQName(ctx context.Context, firstName, lastName, nickName string) ([]User, error) {
  157. var args []any
  158. var clauses []string
  159. if firstName != "" {
  160. args = append(args, firstName)
  161. clauses = append(clauses, `LOWER(icq_basicInfo_firstName) = LOWER(?)`)
  162. }
  163. if lastName != "" {
  164. args = append(args, lastName)
  165. clauses = append(clauses, `LOWER(icq_basicInfo_lastName) = LOWER(?)`)
  166. }
  167. if nickName != "" {
  168. args = append(args, nickName)
  169. clauses = append(clauses, `LOWER(icq_basicInfo_nickName) = LOWER(?)`)
  170. }
  171. whereClause := strings.Join(clauses, " AND ")
  172. users, err := f.queryUsers(ctx, whereClause, args)
  173. if err != nil {
  174. return nil, fmt.Errorf("FindByICQName: %w", err)
  175. }
  176. return users, nil
  177. }
  178. func (f SQLiteUserStore) FindByAIMNameAndAddr(ctx context.Context, info AIMNameAndAddr) ([]User, error) {
  179. var args []any
  180. var clauses []string
  181. if info.FirstName != "" {
  182. args = append(args, info.FirstName)
  183. clauses = append(clauses, `LOWER(aim_firstName) = LOWER(?)`)
  184. }
  185. if info.LastName != "" {
  186. args = append(args, info.LastName)
  187. clauses = append(clauses, `LOWER(aim_lastName) = LOWER(?)`)
  188. }
  189. if info.MiddleName != "" {
  190. args = append(args, info.MiddleName)
  191. clauses = append(clauses, `LOWER(aim_middleName) = LOWER(?)`)
  192. }
  193. if info.MaidenName != "" {
  194. args = append(args, info.MaidenName)
  195. clauses = append(clauses, `LOWER(aim_maidenName) = LOWER(?)`)
  196. }
  197. if info.Country != "" {
  198. args = append(args, info.Country)
  199. clauses = append(clauses, `LOWER(aim_country) = LOWER(?)`)
  200. }
  201. if info.State != "" {
  202. args = append(args, info.State)
  203. clauses = append(clauses, `LOWER(aim_state) = LOWER(?)`)
  204. }
  205. if info.City != "" {
  206. args = append(args, info.City)
  207. clauses = append(clauses, `LOWER(aim_city) = LOWER(?)`)
  208. }
  209. if info.NickName != "" {
  210. args = append(args, info.NickName)
  211. clauses = append(clauses, `LOWER(aim_nickName) = LOWER(?)`)
  212. }
  213. if info.ZIPCode != "" {
  214. args = append(args, info.ZIPCode)
  215. clauses = append(clauses, `LOWER(aim_zipCode) = LOWER(?)`)
  216. }
  217. if info.Address != "" {
  218. args = append(args, info.Address)
  219. clauses = append(clauses, `LOWER(aim_address) = LOWER(?)`)
  220. }
  221. whereClause := strings.Join(clauses, " AND ")
  222. users, err := f.queryUsers(ctx, whereClause, args)
  223. if err != nil {
  224. return nil, fmt.Errorf("FindByAIMNameAndAddr: %w", err)
  225. }
  226. return users, nil
  227. }
  228. // icqInterestsWhereClause builds SQL and args for matching a category code and
  229. // keyword(s) against the four stored interest slots (same semantics as
  230. // FindByICQInterests).
  231. func icqInterestsWhereClause(code uint16, keywords []string) (cond string, args []any) {
  232. var clauses []string
  233. for i := 1; i <= 4; i++ {
  234. var subClauses []string
  235. args = append(args, code)
  236. for _, key := range keywords {
  237. subClauses = append(subClauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  238. args = append(args, "%"+key+"%")
  239. }
  240. clauses = append(clauses, fmt.Sprintf("(icq_interests_code%d = ? AND (%s))", i, strings.Join(subClauses, " OR ")))
  241. }
  242. return strings.Join(clauses, " OR "), args
  243. }
  244. // icqAffiliationsWhereClause builds SQL and args for matching a category code and
  245. // keyword(s) across the six stored affiliation slots (three current + three past),
  246. // using the same OR-of-slots pattern as icqInterestsWhereClause.
  247. func icqAffiliationsWhereClause(code uint16, keywords []string, slots []struct{ codeCol, keyCol string }) (cond string, args []any) {
  248. var clauses []string
  249. for _, slot := range slots {
  250. var subClauses []string
  251. args = append(args, code)
  252. for _, key := range keywords {
  253. subClauses = append(subClauses, fmt.Sprintf("%s LIKE ?", slot.keyCol))
  254. args = append(args, "%"+key+"%")
  255. }
  256. clauses = append(clauses, fmt.Sprintf("(%s = ? AND (%s))", slot.codeCol, strings.Join(subClauses, " OR ")))
  257. }
  258. return strings.Join(clauses, " OR "), args
  259. }
  260. func (f SQLiteUserStore) FindByICQInterests(ctx context.Context, code uint16, keywords []string) ([]User, error) {
  261. cond, args := icqInterestsWhereClause(code, keywords)
  262. users, err := f.queryUsers(ctx, cond, args)
  263. if err != nil {
  264. return nil, fmt.Errorf("FindByICQInterests: %w", err)
  265. }
  266. return users, nil
  267. }
  268. func (f SQLiteUserStore) FindByICQKeyword(ctx context.Context, keyword string) ([]User, error) {
  269. var args []any
  270. var clauses []string
  271. for i := 1; i <= 4; i++ {
  272. args = append(args, "%"+keyword+"%")
  273. clauses = append(clauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  274. }
  275. whereClause := strings.Join(clauses, " OR ")
  276. users, err := f.queryUsers(ctx, whereClause, args)
  277. if err != nil {
  278. return nil, fmt.Errorf("FindByICQKeyword: %w", err)
  279. }
  280. return users, nil
  281. }
  282. // ICQUserSearchCriteria represents an AND-combined set of filters for ICQ
  283. // white-pages style searches (SNAC(15,02) / 0x07D0 / 0x055F).
  284. //
  285. // Fields are pointers so callers can distinguish "unset" from "set to the
  286. // zero value". For example, MinAge == nil means no minimum age constraint,
  287. // while MinAge != nil && *MinAge == 0 means the client explicitly set 0.
  288. type ICQUserSearchCriteria struct {
  289. // Identity
  290. UIN *uint32
  291. // Basic name/email (case-insensitive substring matches). When non-nil, the
  292. // string is non-empty.
  293. FirstName *string
  294. LastName *string
  295. NickName *string
  296. Email *string
  297. // Demographics
  298. MinAge *uint16
  299. MaxAge *uint16
  300. Gender *uint8
  301. SpokenLanguage *uint8
  302. // Location / work (non-nil *string fields are non-empty)
  303. City *string
  304. State *string
  305. CountryCode *uint16
  306. Company *string // non-nil => non-empty
  307. Position *string // non-nil => non-empty
  308. DepartmentName *string // non-nil => non-empty
  309. OccupationCode *uint16
  310. // Directory nodes (code + keywords). InterestsCode and InterestsKeywords are
  311. // either both unset or both set; when code is non-nil, keywords must be non-empty.
  312. InterestsCode *uint16
  313. InterestsKeywords []string
  314. // AffiliationsCode and AffiliationsKeywords follow the same pairing rules as
  315. // InterestsCode / InterestsKeywords.
  316. AffiliationsCode *uint16
  317. AffiliationsKeywords []string
  318. // PastAffiliationsCode and PastAffiliationsKeywords follow the same pairing rules as
  319. // InterestsCode / InterestsKeywords.
  320. PastAffiliationsCode *uint16
  321. PastAffiliationsKeywords []string
  322. HomePageCategoryIndex *uint16
  323. HomePageKeywords []string
  324. // Whitepages search keywords string (TLV 0x0226). This is modeled as a
  325. // global keyword across searchable profile fields.
  326. AnyKeyword *string
  327. }
  328. // SearchICQUsers returns ICQ users whose stored profile fields satisfy every
  329. // non-empty constraint in c. Predicates are AND-combined. Only accounts with
  330. // isICQ = 1 are considered.
  331. //
  332. // If c contains no constraints, SearchICQUsers returns [ErrICQSearchEmptyCriteria].
  333. func (f SQLiteUserStore) SearchICQUsers(ctx context.Context, c ICQUserSearchCriteria) ([]User, error) {
  334. var args []any
  335. var clauses []string
  336. appendLike := func(column string, val string) {
  337. args = append(args, "%"+val+"%")
  338. clauses = append(clauses, fmt.Sprintf(`LOWER(%s) LIKE LOWER(?)`, column))
  339. }
  340. if c.UIN != nil {
  341. args = append(args, strconv.Itoa(int(*c.UIN)))
  342. clauses = append(clauses, `identScreenName = ?`)
  343. }
  344. if c.FirstName != nil {
  345. appendLike("icq_basicInfo_firstName", *c.FirstName)
  346. }
  347. if c.LastName != nil {
  348. appendLike("icq_basicInfo_lastName", *c.LastName)
  349. }
  350. if c.NickName != nil {
  351. appendLike("icq_basicInfo_nickName", *c.NickName)
  352. }
  353. if c.Email != nil {
  354. appendLike("icq_basicInfo_emailAddress", *c.Email)
  355. }
  356. if c.Gender != nil {
  357. args = append(args, *c.Gender)
  358. clauses = append(clauses, `icq_moreInfo_gender = ?`)
  359. }
  360. if c.SpokenLanguage != nil {
  361. args = append(args, *c.SpokenLanguage, *c.SpokenLanguage, *c.SpokenLanguage)
  362. clauses = append(clauses, `(icq_moreInfo_lang1 = ? OR icq_moreInfo_lang2 = ? OR icq_moreInfo_lang3 = ?)`)
  363. }
  364. if c.MinAge != nil {
  365. args = append(args, *c.MinAge)
  366. clauses = append(clauses, `(icq_moreInfo_birthYear > 0 AND (CAST(strftime('%Y','now') AS INTEGER) - icq_moreInfo_birthYear) >= ?)`)
  367. }
  368. if c.MaxAge != nil {
  369. args = append(args, *c.MaxAge)
  370. clauses = append(clauses, `(icq_moreInfo_birthYear > 0 AND (CAST(strftime('%Y','now') AS INTEGER) - icq_moreInfo_birthYear) <= ?)`)
  371. }
  372. if c.City != nil {
  373. appendLike("icq_basicInfo_city", *c.City)
  374. }
  375. if c.State != nil {
  376. args = append(args, "%"+*c.State+"%", "%"+*c.State+"%")
  377. clauses = append(clauses, `(LOWER(icq_basicInfo_state) LIKE LOWER(?) OR LOWER(icq_workInfo_state) LIKE LOWER(?))`)
  378. }
  379. if c.CountryCode != nil {
  380. args = append(args, *c.CountryCode, *c.CountryCode)
  381. clauses = append(clauses, `(icq_basicInfo_countryCode = ? OR icq_workInfo_countryCode = ?)`)
  382. }
  383. if c.Company != nil {
  384. appendLike("icq_workInfo_company", *c.Company)
  385. }
  386. if c.DepartmentName != nil {
  387. appendLike("icq_workInfo_department", *c.DepartmentName)
  388. }
  389. if c.Position != nil {
  390. appendLike("icq_workInfo_position", *c.Position)
  391. }
  392. if c.OccupationCode != nil {
  393. args = append(args, *c.OccupationCode)
  394. clauses = append(clauses, `icq_workInfo_occupationCode = ?`)
  395. }
  396. if c.InterestsCode != nil {
  397. intCond, intArgs := icqInterestsWhereClause(*c.InterestsCode, c.InterestsKeywords)
  398. clauses = append(clauses, "("+intCond+")")
  399. args = append(args, intArgs...)
  400. }
  401. if c.AffiliationsCode != nil {
  402. slots := []struct{ codeCol, keyCol string }{
  403. {"icq_affiliations_currentCode1", "icq_affiliations_currentKeyword1"},
  404. {"icq_affiliations_currentCode2", "icq_affiliations_currentKeyword2"},
  405. {"icq_affiliations_currentCode3", "icq_affiliations_currentKeyword3"},
  406. }
  407. affCond, affArgs := icqAffiliationsWhereClause(*c.AffiliationsCode, c.AffiliationsKeywords, slots)
  408. clauses = append(clauses, "("+affCond+")")
  409. args = append(args, affArgs...)
  410. }
  411. if c.PastAffiliationsCode != nil {
  412. slots := []struct{ codeCol, keyCol string }{
  413. {"icq_affiliations_pastCode1", "icq_affiliations_pastKeyword1"},
  414. {"icq_affiliations_pastCode2", "icq_affiliations_pastKeyword2"},
  415. {"icq_affiliations_pastCode3", "icq_affiliations_pastKeyword3"},
  416. }
  417. affCond, affArgs := icqAffiliationsWhereClause(*c.PastAffiliationsCode, c.PastAffiliationsKeywords, slots)
  418. clauses = append(clauses, "("+affCond+")")
  419. args = append(args, affArgs...)
  420. }
  421. if c.HomePageCategoryIndex != nil {
  422. args = append(args, *c.HomePageCategoryIndex)
  423. clauses = append(clauses, `icq_homepageCategory_index = ?`)
  424. }
  425. if len(c.HomePageKeywords) > 0 {
  426. var kwClauses []string
  427. for _, kw := range c.HomePageKeywords {
  428. kwClauses = append(kwClauses, `LOWER(icq_homepageCategory_description) LIKE LOWER(?)`)
  429. args = append(args, "%"+kw+"%")
  430. }
  431. clauses = append(clauses, fmt.Sprintf("(%s)", strings.Join(kwClauses, " OR ")))
  432. }
  433. if c.AnyKeyword != nil {
  434. var kwClauses []string
  435. for i := 1; i <= 4; i++ {
  436. kwClauses = append(kwClauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  437. args = append(args, "%"+*c.AnyKeyword+"%")
  438. }
  439. clauses = append(clauses, fmt.Sprintf("(%s)", strings.Join(kwClauses, " OR ")))
  440. }
  441. if len(clauses) == 0 {
  442. return nil, ErrICQSearchEmptyCriteria
  443. }
  444. clauses = append(clauses, `isICQ = 1`)
  445. whereClause := strings.Join(clauses, " AND ")
  446. users, err := f.queryUsers(ctx, whereClause, args)
  447. if err != nil {
  448. return nil, fmt.Errorf("SearchICQUsers: %w", err)
  449. }
  450. return users, nil
  451. }
  452. func (f SQLiteUserStore) User(ctx context.Context, screenName IdentScreenName) (*User, error) {
  453. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{screenName.String()})
  454. if err != nil {
  455. return nil, fmt.Errorf("User: %w", err)
  456. }
  457. if len(users) == 0 {
  458. return nil, nil
  459. }
  460. return &users[0], nil
  461. }
  462. // RequiresAuthorization reports whether adding owner as a contact by requester
  463. // is still blocked: owner requires authorization and requester does not have a
  464. // pre-authorization grant from owner.
  465. func (f SQLiteUserStore) RequiresAuthorization(ctx context.Context, owner, requester IdentScreenName) (bool, error) {
  466. u, err := f.User(ctx, owner)
  467. if err != nil {
  468. return false, fmt.Errorf("RequiresAuthorization: %w", err)
  469. }
  470. if u == nil || !u.ICQInfo.Permissions.AuthRequired {
  471. return false, nil
  472. }
  473. var one int
  474. err = f.db.QueryRowContext(ctx,
  475. `SELECT 1 FROM contactPreauth WHERE ownerScreenName = ? AND authorizedScreenName = ? LIMIT 1`,
  476. owner.String(), requester.String(),
  477. ).Scan(&one)
  478. if errors.Is(err, sql.ErrNoRows) {
  479. return true, nil
  480. }
  481. if err != nil {
  482. return false, fmt.Errorf("RequiresAuthorization: %w", err)
  483. }
  484. return false, nil
  485. }
  486. // RecordPreAuth records that owner has pre-authorized requester to add owner
  487. // without a further authorization prompt. No-ops when either user is not
  488. // registered. The operation is idempotent.
  489. func (f SQLiteUserStore) RecordPreAuth(ctx context.Context, owner, requester IdentScreenName) error {
  490. _, err := f.db.ExecContext(ctx,
  491. `INSERT OR IGNORE INTO contactPreauth (ownerScreenName, authorizedScreenName, createdAt)
  492. VALUES (?, ?, UNIXEPOCH())`,
  493. owner.String(), requester.String(),
  494. )
  495. if err != nil {
  496. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  497. return nil
  498. }
  499. return fmt.Errorf("RecordPreAuth: %w", err)
  500. }
  501. return nil
  502. }
  503. // HasBuddyAddedNotification reports whether the server has already sent a
  504. // "you were added" notification for requester being added by granter.
  505. func (f SQLiteUserStore) HasBuddyAddedNotification(ctx context.Context, granter, requester IdentScreenName) (bool, error) {
  506. var one int
  507. err := f.db.QueryRowContext(ctx,
  508. `SELECT 1 FROM buddyAddedNotifications WHERE granterScreenName = ? AND requesterScreenName = ? LIMIT 1`,
  509. granter.String(), requester.String(),
  510. ).Scan(&one)
  511. if errors.Is(err, sql.ErrNoRows) {
  512. return false, nil
  513. }
  514. if err != nil {
  515. return false, fmt.Errorf("HasBuddyAddedNotification: %w", err)
  516. }
  517. return true, nil
  518. }
  519. // RecordBuddyAddedNotification records that the server has sent a "you were added"
  520. // notification for requester being added by granter. No-ops when either user is
  521. // not registered. The operation is idempotent.
  522. func (f SQLiteUserStore) RecordBuddyAddedNotification(ctx context.Context, granter, requester IdentScreenName) error {
  523. _, err := f.db.ExecContext(ctx,
  524. `INSERT OR IGNORE INTO buddyAddedNotifications (granterScreenName, requesterScreenName, createdAt) VALUES (?, ?, UNIXEPOCH())`,
  525. granter.String(), requester.String(),
  526. )
  527. if err != nil {
  528. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  529. return nil
  530. }
  531. return fmt.Errorf("RecordBuddyAddedNotification: %w", err)
  532. }
  533. return nil
  534. }
  535. // queryUsers retrieves a list of users from the database based on the
  536. // specified WHERE clause and query parameters. Returns a slice of User objects
  537. // or an error if the query fails.
  538. func (f SQLiteUserStore) queryUsers(ctx context.Context, whereClause string, queryParams []any) ([]User, error) {
  539. q := `
  540. SELECT
  541. identScreenName,
  542. displayScreenName,
  543. emailAddress,
  544. authKey,
  545. strongMD5Pass,
  546. weakMD5Pass,
  547. confirmStatus,
  548. regStatus,
  549. suspendedStatus,
  550. isBot,
  551. isICQ,
  552. icq_affiliations_currentCode1,
  553. icq_affiliations_currentCode2,
  554. icq_affiliations_currentCode3,
  555. icq_affiliations_currentKeyword1,
  556. icq_affiliations_currentKeyword2,
  557. icq_affiliations_currentKeyword3,
  558. icq_affiliations_pastCode1,
  559. icq_affiliations_pastCode2,
  560. icq_affiliations_pastCode3,
  561. icq_affiliations_pastKeyword1,
  562. icq_affiliations_pastKeyword2,
  563. icq_affiliations_pastKeyword3,
  564. icq_basicInfo_address,
  565. icq_basicInfo_cellPhone,
  566. icq_basicInfo_city,
  567. icq_basicInfo_countryCode,
  568. icq_basicInfo_emailAddress,
  569. icq_basicInfo_fax,
  570. icq_basicInfo_firstName,
  571. icq_basicInfo_gmtOffset,
  572. icq_basicInfo_lastName,
  573. icq_basicInfo_nickName,
  574. icq_basicInfo_phone,
  575. icq_basicInfo_publishEmail,
  576. icq_basicInfo_state,
  577. icq_basicInfo_zipCode,
  578. icq_basicInfo_originCity,
  579. icq_basicInfo_originState,
  580. icq_basicInfo_originCountryCode,
  581. icq_interests_code1,
  582. icq_interests_code2,
  583. icq_interests_code3,
  584. icq_interests_code4,
  585. icq_interests_keyword1,
  586. icq_interests_keyword2,
  587. icq_interests_keyword3,
  588. icq_interests_keyword4,
  589. icq_moreInfo_birthDay,
  590. icq_moreInfo_birthMonth,
  591. icq_moreInfo_birthYear,
  592. icq_moreInfo_gender,
  593. icq_moreInfo_homePageAddr,
  594. icq_moreInfo_lang1,
  595. icq_moreInfo_lang2,
  596. icq_moreInfo_lang3,
  597. icq_notes,
  598. icq_permissions_authRequired,
  599. icq_permissions_webAware,
  600. icq_permissions_allowSpam,
  601. icq_workInfo_address,
  602. icq_workInfo_city,
  603. icq_workInfo_company,
  604. icq_workInfo_countryCode,
  605. icq_workInfo_department,
  606. icq_workInfo_fax,
  607. icq_workInfo_occupationCode,
  608. icq_workInfo_phone,
  609. icq_workInfo_position,
  610. icq_workInfo_state,
  611. icq_workInfo_webPage,
  612. icq_workInfo_zipCode,
  613. icq_homepageCategory_enabled,
  614. icq_homepageCategory_index,
  615. icq_homepageCategory_description,
  616. aim_firstName,
  617. aim_lastName,
  618. aim_middleName,
  619. aim_maidenName,
  620. aim_country,
  621. aim_state,
  622. aim_city,
  623. aim_nickName,
  624. aim_zipCode,
  625. aim_address,
  626. tocConfig,
  627. lastWarnUpdate,
  628. lastWarnLevel,
  629. offlineMsgCount
  630. FROM users
  631. WHERE %s
  632. `
  633. q = fmt.Sprintf(q, whereClause)
  634. rows, err := f.db.QueryContext(ctx, q, queryParams...)
  635. if err != nil {
  636. return nil, err
  637. }
  638. defer rows.Close()
  639. var users []User
  640. for rows.Next() {
  641. var u User
  642. var sn string
  643. var lastWarnUpdateUnix int64
  644. err := rows.Scan(
  645. &sn,
  646. &u.DisplayScreenName,
  647. &u.EmailAddress,
  648. &u.AuthKey,
  649. &u.StrongMD5Pass,
  650. &u.WeakMD5Pass,
  651. &u.ConfirmStatus,
  652. &u.RegStatus,
  653. &u.SuspendedStatus,
  654. &u.IsBot,
  655. &u.IsICQ,
  656. &u.ICQInfo.Affiliations.CurrentCode1,
  657. &u.ICQInfo.Affiliations.CurrentCode2,
  658. &u.ICQInfo.Affiliations.CurrentCode3,
  659. &u.ICQInfo.Affiliations.CurrentKeyword1,
  660. &u.ICQInfo.Affiliations.CurrentKeyword2,
  661. &u.ICQInfo.Affiliations.CurrentKeyword3,
  662. &u.ICQInfo.Affiliations.PastCode1,
  663. &u.ICQInfo.Affiliations.PastCode2,
  664. &u.ICQInfo.Affiliations.PastCode3,
  665. &u.ICQInfo.Affiliations.PastKeyword1,
  666. &u.ICQInfo.Affiliations.PastKeyword2,
  667. &u.ICQInfo.Affiliations.PastKeyword3,
  668. &u.ICQInfo.Basic.Address,
  669. &u.ICQInfo.Basic.CellPhone,
  670. &u.ICQInfo.Basic.City,
  671. &u.ICQInfo.Basic.CountryCode,
  672. &u.ICQInfo.Basic.EmailAddress,
  673. &u.ICQInfo.Basic.Fax,
  674. &u.ICQInfo.Basic.FirstName,
  675. &u.ICQInfo.Basic.GMTOffset,
  676. &u.ICQInfo.Basic.LastName,
  677. &u.ICQInfo.Basic.Nickname,
  678. &u.ICQInfo.Basic.Phone,
  679. &u.ICQInfo.Basic.PublishEmail,
  680. &u.ICQInfo.Basic.State,
  681. &u.ICQInfo.Basic.ZIPCode,
  682. &u.ICQInfo.Basic.OriginallyFromCity,
  683. &u.ICQInfo.Basic.OriginallyFromState,
  684. &u.ICQInfo.Basic.OriginallyFromCountryCode,
  685. &u.ICQInfo.Interests.Code1,
  686. &u.ICQInfo.Interests.Code2,
  687. &u.ICQInfo.Interests.Code3,
  688. &u.ICQInfo.Interests.Code4,
  689. &u.ICQInfo.Interests.Keyword1,
  690. &u.ICQInfo.Interests.Keyword2,
  691. &u.ICQInfo.Interests.Keyword3,
  692. &u.ICQInfo.Interests.Keyword4,
  693. &u.ICQInfo.More.BirthDay,
  694. &u.ICQInfo.More.BirthMonth,
  695. &u.ICQInfo.More.BirthYear,
  696. &u.ICQInfo.More.Gender,
  697. &u.ICQInfo.More.HomePageAddr,
  698. &u.ICQInfo.More.Lang1,
  699. &u.ICQInfo.More.Lang2,
  700. &u.ICQInfo.More.Lang3,
  701. &u.ICQInfo.Notes.Notes,
  702. &u.ICQInfo.Permissions.AuthRequired,
  703. &u.ICQInfo.Permissions.WebAware,
  704. &u.ICQInfo.Permissions.AllowSpam,
  705. &u.ICQInfo.Work.Address,
  706. &u.ICQInfo.Work.City,
  707. &u.ICQInfo.Work.Company,
  708. &u.ICQInfo.Work.CountryCode,
  709. &u.ICQInfo.Work.Department,
  710. &u.ICQInfo.Work.Fax,
  711. &u.ICQInfo.Work.OccupationCode,
  712. &u.ICQInfo.Work.Phone,
  713. &u.ICQInfo.Work.Position,
  714. &u.ICQInfo.Work.State,
  715. &u.ICQInfo.Work.WebPage,
  716. &u.ICQInfo.Work.ZIPCode,
  717. &u.ICQInfo.HomepageCategory.Enabled,
  718. &u.ICQInfo.HomepageCategory.Index,
  719. &u.ICQInfo.HomepageCategory.Description,
  720. &u.AIMDirectoryInfo.FirstName,
  721. &u.AIMDirectoryInfo.LastName,
  722. &u.AIMDirectoryInfo.MiddleName,
  723. &u.AIMDirectoryInfo.MaidenName,
  724. &u.AIMDirectoryInfo.Country,
  725. &u.AIMDirectoryInfo.State,
  726. &u.AIMDirectoryInfo.City,
  727. &u.AIMDirectoryInfo.NickName,
  728. &u.AIMDirectoryInfo.ZIPCode,
  729. &u.AIMDirectoryInfo.Address,
  730. &u.TOCConfig,
  731. &lastWarnUpdateUnix,
  732. &u.LastWarnLevel,
  733. &u.OfflineMsgCount,
  734. )
  735. if err != nil {
  736. return nil, err
  737. }
  738. u.IdentScreenName = NewIdentScreenName(sn)
  739. u.LastWarnUpdate = time.Unix(lastWarnUpdateUnix, 0).UTC()
  740. users = append(users, u)
  741. }
  742. if err = rows.Err(); err != nil {
  743. return nil, err
  744. }
  745. return users, nil
  746. }
  747. func (f SQLiteUserStore) InsertUser(ctx context.Context, u User) error {
  748. if u.DisplayScreenName.IsUIN() && !u.IsICQ {
  749. return errors.New("inserting user with UIN and isICQ=false")
  750. }
  751. q := `
  752. INSERT INTO users (identScreenName, displayScreenName, authKey, weakMD5Pass, strongMD5Pass, isICQ, isBot)
  753. VALUES (?, ?, ?, ?, ?, ?, ?)
  754. ON CONFLICT (identScreenName) DO NOTHING
  755. `
  756. result, err := f.db.ExecContext(ctx,
  757. q,
  758. u.IdentScreenName.String(),
  759. u.DisplayScreenName,
  760. u.AuthKey,
  761. u.WeakMD5Pass,
  762. u.StrongMD5Pass,
  763. u.IsICQ,
  764. u.IsBot,
  765. )
  766. if err != nil {
  767. return err
  768. }
  769. rowsAffected, err := result.RowsAffected()
  770. if err != nil {
  771. return err
  772. }
  773. if rowsAffected == 0 {
  774. return ErrDupUser
  775. }
  776. return nil
  777. }
  778. // DeleteUser deletes a user, their feedbag, and every linked-account item in
  779. // other users' feedbags that points to them.
  780. func (f SQLiteUserStore) DeleteUser(ctx context.Context, screenName IdentScreenName) error {
  781. tx, err := f.db.BeginTx(ctx, nil)
  782. if err != nil {
  783. return err
  784. }
  785. defer tx.Rollback()
  786. q := `
  787. DELETE FROM users WHERE identScreenName = ?
  788. `
  789. result, err := tx.ExecContext(ctx, q, screenName.String())
  790. if err != nil {
  791. return err
  792. }
  793. rowsAffected, err := result.RowsAffected()
  794. if err != nil {
  795. return err
  796. }
  797. if rowsAffected == 0 {
  798. return ErrNoUser
  799. }
  800. q = `
  801. DELETE FROM feedbag WHERE classID = ? AND name = ?
  802. `
  803. if _, err := tx.ExecContext(ctx, q, wire.FeedbagClassIdAlInfo, screenName.String()); err != nil {
  804. return err
  805. }
  806. return tx.Commit()
  807. }
  808. func (f SQLiteUserStore) SetUserPassword(ctx context.Context, screenName IdentScreenName, newPassword string) error {
  809. tx, err := f.db.Begin()
  810. if err != nil {
  811. return err
  812. }
  813. defer func() {
  814. if err != nil {
  815. err = errors.Join(err, tx.Rollback())
  816. }
  817. }()
  818. q := `
  819. SELECT
  820. authKey,
  821. isICQ
  822. FROM users
  823. WHERE identScreenName = ?
  824. `
  825. u := User{}
  826. err = tx.QueryRowContext(ctx, q, screenName.String()).Scan(
  827. &u.AuthKey,
  828. &u.IsICQ,
  829. )
  830. if errors.Is(err, sql.ErrNoRows) {
  831. return ErrNoUser
  832. }
  833. if err = u.HashPassword(newPassword); err != nil {
  834. return err
  835. }
  836. q = `
  837. UPDATE users
  838. SET authKey = ?, weakMD5Pass = ?, strongMD5Pass = ?
  839. WHERE identScreenName = ?
  840. `
  841. result, err := tx.ExecContext(ctx, q, u.AuthKey, u.WeakMD5Pass, u.StrongMD5Pass, screenName.String())
  842. if err != nil {
  843. return err
  844. }
  845. rowsAffected, err := result.RowsAffected()
  846. if err != nil {
  847. return err
  848. }
  849. if rowsAffected == 0 {
  850. // it's possible the user didn't change OR the user doesn't exist.
  851. // check if the user exists.
  852. var exists int
  853. err = tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE identScreenName = ?", u.IdentScreenName.String()).Scan(&exists)
  854. if err != nil {
  855. return err // Handle possible SQL errors during the select
  856. }
  857. if exists == 0 {
  858. err = ErrNoUser // User does not exist
  859. return err
  860. }
  861. }
  862. return tx.Commit()
  863. }
  864. func (f SQLiteUserStore) Feedbag(ctx context.Context, screenName IdentScreenName) ([]wire.FeedbagItem, error) {
  865. q := `
  866. SELECT
  867. groupID,
  868. itemID,
  869. classID,
  870. name,
  871. attributes
  872. FROM feedbag
  873. WHERE screenName = ?
  874. `
  875. rows, err := f.db.QueryContext(ctx, q, screenName.String())
  876. if err != nil {
  877. return nil, err
  878. }
  879. defer rows.Close()
  880. var items []wire.FeedbagItem
  881. for rows.Next() {
  882. var item wire.FeedbagItem
  883. var attrs []byte
  884. if err := rows.Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs); err != nil {
  885. return nil, err
  886. }
  887. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  888. return items, err
  889. }
  890. items = append(items, item)
  891. }
  892. return items, nil
  893. }
  894. func (f SQLiteUserStore) FeedbagLastModified(ctx context.Context, screenName IdentScreenName) (time.Time, error) {
  895. var lastModified sql.NullInt64
  896. q := `SELECT MAX(lastModified) FROM feedbag WHERE screenName = ?`
  897. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&lastModified)
  898. return time.Unix(lastModified.Int64, 0), err
  899. }
  900. func (f SQLiteUserStore) FeedbagDelete(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  901. // todo add transaction
  902. q := `DELETE FROM feedbag WHERE screenName = ? AND itemID = ? AND groupID = ?`
  903. for _, item := range items {
  904. if _, err := f.db.ExecContext(ctx, q, screenName.String(), item.ItemID, item.GroupID); err != nil {
  905. return err
  906. }
  907. }
  908. return nil
  909. }
  910. func (f SQLiteUserStore) FeedbagUpsert(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  911. q := `
  912. INSERT INTO feedbag (screenName, groupID, itemID, classID, name, attributes, pdMode, authPending, lastModified)
  913. VALUES (?, ?, ?, ?, ?, ?, ?, ?, UNIXEPOCH())
  914. ON CONFLICT (screenName, groupID, itemID)
  915. DO UPDATE SET classID = excluded.classID,
  916. name = excluded.name,
  917. attributes = excluded.attributes,
  918. pdMode = excluded.pdMode,
  919. authPending = excluded.authPending,
  920. lastModified = UNIXEPOCH()
  921. `
  922. for _, item := range items {
  923. buf := &bytes.Buffer{}
  924. if err := wire.MarshalBE(item.TLVLBlock, buf); err != nil {
  925. return err
  926. }
  927. if item.ClassID == wire.FeedbagClassIdBuddy ||
  928. item.ClassID == wire.FeedbagClassIDPermit ||
  929. item.ClassID == wire.FeedbagClassIdAlInfo ||
  930. item.ClassID == wire.FeedbagClassIDDeny {
  931. // insert screen name identifier
  932. item.Name = NewIdentScreenName(item.Name).String()
  933. }
  934. pdMode := uint8(0)
  935. if item.ClassID == wire.FeedbagClassIdPdinfo {
  936. var hasMode bool
  937. pdMode, hasMode = item.Uint8(wire.FeedbagAttributesPdMode)
  938. if !hasMode {
  939. // by default, QIP sends a PD info item entry with no mode
  940. pdMode = uint8(wire.FeedbagPDModePermitAll)
  941. }
  942. }
  943. authPending := item.ClassID == wire.FeedbagClassIdBuddy &&
  944. item.HasTag(wire.FeedbagAttributesPending)
  945. _, err := f.db.ExecContext(ctx,
  946. q,
  947. screenName.String(),
  948. item.GroupID,
  949. item.ItemID,
  950. item.ClassID,
  951. item.Name,
  952. buf.Bytes(),
  953. pdMode,
  954. authPending)
  955. if err != nil {
  956. return err
  957. }
  958. }
  959. return nil
  960. }
  961. func (f SQLiteUserStore) ClearBuddyListRegistry(ctx context.Context) error {
  962. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode`); err != nil {
  963. return err
  964. }
  965. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList`); err != nil {
  966. return err
  967. }
  968. return nil
  969. }
  970. func (f SQLiteUserStore) RegisterBuddyList(ctx context.Context, user IdentScreenName) error {
  971. q := `
  972. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  973. ON CONFLICT (screenName) DO NOTHING
  974. `
  975. _, err := f.db.ExecContext(ctx, q, user.String(), wire.FeedbagPDModePermitAll)
  976. return err
  977. }
  978. func (f SQLiteUserStore) UnregisterBuddyList(ctx context.Context, user IdentScreenName) error {
  979. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode WHERE screenName = ?`, user.String()); err != nil {
  980. return err
  981. }
  982. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList WHERE me = ?`, user.String()); err != nil {
  983. return err
  984. }
  985. return nil
  986. }
  987. func (f SQLiteUserStore) UseFeedbag(ctx context.Context, screenName IdentScreenName) error {
  988. q := `
  989. INSERT INTO buddyListMode (screenName, useFeedbag)
  990. VALUES (?, ?)
  991. ON CONFLICT (screenName)
  992. DO UPDATE SET clientSidePDMode = 0,
  993. useFeedbag = true
  994. `
  995. _, err := f.db.ExecContext(ctx, q, screenName.String(), true)
  996. return err
  997. }
  998. func (f SQLiteUserStore) SetPDMode(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  999. alreadySet, err := f.isPDModeEqual(ctx, me, pdMode)
  1000. if err != nil {
  1001. return fmt.Errorf("isPDModeEqual: %w", err)
  1002. }
  1003. if alreadySet {
  1004. return nil
  1005. }
  1006. tx, err := f.db.Begin()
  1007. if err != nil {
  1008. return err
  1009. }
  1010. defer func() {
  1011. _ = tx.Rollback()
  1012. }()
  1013. if err := setClientSidePDMode(ctx, tx, me, pdMode); err != nil {
  1014. return fmt.Errorf("setClientSidePDMode: %w", err)
  1015. }
  1016. if err := clearClientSidePDFlags(ctx, tx, me, pdMode); err != nil {
  1017. return fmt.Errorf("clearClientSidePDFlags: %w", err)
  1018. }
  1019. if err := clearBlankClientSideBuddies(ctx, tx, me, pdMode); err != nil {
  1020. return fmt.Errorf("clearBlankClientSideBuddies: %w", err)
  1021. }
  1022. if err := tx.Commit(); err != nil {
  1023. return fmt.Errorf("commit: %w", err)
  1024. }
  1025. return nil
  1026. }
  1027. // isPDModeEqual indicates whether the current permit/deny mode is already set
  1028. // to pdMode.
  1029. func (f SQLiteUserStore) isPDModeEqual(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) (bool, error) {
  1030. q := `
  1031. SELECT true
  1032. FROM buddyListMode
  1033. WHERE screenName = ? AND clientSidePDMode = ?
  1034. `
  1035. var isEqual bool
  1036. err := f.db.QueryRowContext(ctx, q, me.String(), pdMode).Scan(&isEqual)
  1037. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1038. return false, err
  1039. }
  1040. return isEqual, nil
  1041. }
  1042. // setClientSidePDMode sets the permit/deny mode for my client-side buddy list.
  1043. func setClientSidePDMode(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1044. q := `
  1045. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  1046. ON CONFLICT (screenName)
  1047. DO UPDATE SET clientSidePDMode = excluded.clientSidePDMode
  1048. `
  1049. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1050. if err != nil {
  1051. return err
  1052. }
  1053. return nil
  1054. }
  1055. // clearBlankClientSideBuddies removes client-side buddy where all flags
  1056. // (isBuddy, isPermit, isDeny) are false.
  1057. func clearBlankClientSideBuddies(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1058. q := `
  1059. DELETE FROM clientSideBuddyList
  1060. WHERE isBuddy IS FALSE
  1061. AND isPermit IS FALSE
  1062. AND isDeny IS FALSE
  1063. AND me = ?
  1064. `
  1065. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1066. return err
  1067. }
  1068. // clearClientSidePDFlags clears permit/deny flags.
  1069. func clearClientSidePDFlags(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1070. q := `
  1071. UPDATE clientSideBuddyList
  1072. SET isDeny = false, isPermit = false
  1073. WHERE me = ?
  1074. `
  1075. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1076. return err
  1077. }
  1078. func (f SQLiteUserStore) AddBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1079. q := `
  1080. INSERT INTO clientSideBuddyList (me, them, isBuddy)
  1081. VALUES (?, ?, true)
  1082. ON CONFLICT (me, them) DO UPDATE SET isBuddy = true
  1083. `
  1084. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1085. return err
  1086. }
  1087. func (f SQLiteUserStore) RemoveBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1088. q := `
  1089. UPDATE clientSideBuddyList
  1090. SET isBuddy = false
  1091. WHERE me = ?
  1092. AND them = ?
  1093. `
  1094. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1095. return err
  1096. }
  1097. func (f SQLiteUserStore) DenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1098. q := `
  1099. INSERT INTO clientSideBuddyList (me, them, isDeny)
  1100. VALUES (?, ?, 1)
  1101. ON CONFLICT (me, them) DO UPDATE SET isDeny = 1
  1102. `
  1103. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1104. return err
  1105. }
  1106. func (f SQLiteUserStore) RemoveDenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1107. q := `
  1108. UPDATE clientSideBuddyList
  1109. SET isDeny = false
  1110. WHERE me = ?
  1111. AND them = ?
  1112. `
  1113. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1114. return err
  1115. }
  1116. func (f SQLiteUserStore) PermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1117. q := `
  1118. INSERT INTO clientSideBuddyList (me, them, isPermit)
  1119. VALUES (?, ?, 1)
  1120. ON CONFLICT (me, them) DO UPDATE SET isPermit = 1
  1121. `
  1122. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1123. return err
  1124. }
  1125. func (f SQLiteUserStore) RemovePermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1126. q := `
  1127. UPDATE clientSideBuddyList
  1128. SET isPermit = false
  1129. WHERE me = ?
  1130. AND them = ?
  1131. `
  1132. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1133. return err
  1134. }
  1135. func (f SQLiteUserStore) Profile(ctx context.Context, screenName IdentScreenName) (UserProfile, error) {
  1136. q := `
  1137. SELECT IFNULL(body, ''), IFNULL(mimeType, ''), IFNULL(updateTime, 0)
  1138. FROM profile
  1139. WHERE screenName = ?
  1140. `
  1141. var profile UserProfile
  1142. var updateTimeUnix int64
  1143. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&profile.ProfileText, &profile.MIMEType, &updateTimeUnix)
  1144. if errors.Is(err, sql.ErrNoRows) {
  1145. return UserProfile{}, nil
  1146. }
  1147. if err != nil {
  1148. return UserProfile{}, err
  1149. }
  1150. if updateTimeUnix > 0 {
  1151. profile.UpdateTime = time.Unix(updateTimeUnix, 0).UTC()
  1152. }
  1153. return profile, nil
  1154. }
  1155. func (f SQLiteUserStore) SetProfile(ctx context.Context, screenName IdentScreenName, profile UserProfile) error {
  1156. var updateTimeUnix int64
  1157. if !profile.UpdateTime.IsZero() {
  1158. updateTimeUnix = profile.UpdateTime.Unix()
  1159. }
  1160. q := `
  1161. INSERT INTO profile (screenName, body, mimeType, updateTime)
  1162. VALUES (?, ?, ?, ?)
  1163. ON CONFLICT (screenName)
  1164. DO UPDATE SET body = excluded.body,
  1165. mimeType = excluded.mimeType,
  1166. updateTime = excluded.updateTime
  1167. `
  1168. _, err := f.db.ExecContext(ctx, q, screenName.String(), profile.ProfileText, profile.MIMEType, updateTimeUnix)
  1169. return err
  1170. }
  1171. func (f SQLiteUserStore) SetDirectoryInfo(ctx context.Context, screenName IdentScreenName, info AIMNameAndAddr) error {
  1172. q := `
  1173. UPDATE users SET
  1174. aim_firstName = ?,
  1175. aim_lastName = ?,
  1176. aim_middleName = ?,
  1177. aim_maidenName = ?,
  1178. aim_country = ?,
  1179. aim_state = ?,
  1180. aim_city = ?,
  1181. aim_nickName = ?,
  1182. aim_zipCode = ?,
  1183. aim_address = ?
  1184. WHERE identScreenName = ?
  1185. `
  1186. res, err := f.db.ExecContext(ctx,
  1187. q,
  1188. info.FirstName,
  1189. info.LastName,
  1190. info.MiddleName,
  1191. info.MaidenName,
  1192. info.Country,
  1193. info.State,
  1194. info.City,
  1195. info.NickName,
  1196. info.ZIPCode,
  1197. info.Address,
  1198. screenName.String(),
  1199. )
  1200. if err != nil {
  1201. return fmt.Errorf("exec: %w", err)
  1202. }
  1203. c, err := res.RowsAffected()
  1204. if err != nil {
  1205. return fmt.Errorf("rows affected: %w", err)
  1206. }
  1207. if c == 0 {
  1208. return ErrNoUser
  1209. }
  1210. return nil
  1211. }
  1212. func (f SQLiteUserStore) InsertBARTItem(ctx context.Context, hash []byte, blob []byte, itemType uint16) error {
  1213. q := `
  1214. INSERT INTO bartItem (hash, body, type)
  1215. VALUES (?, ?, ?)
  1216. `
  1217. _, err := f.db.ExecContext(ctx, q, hash, blob, itemType)
  1218. if err != nil {
  1219. if liteErr, ok := err.(*sqlite.Error); ok {
  1220. code := liteErr.Code()
  1221. if code == lib.SQLITE_CONSTRAINT_PRIMARYKEY {
  1222. return ErrBARTItemExists
  1223. }
  1224. }
  1225. return err
  1226. }
  1227. return nil
  1228. }
  1229. func (f SQLiteUserStore) BARTItem(ctx context.Context, hash []byte) ([]byte, error) {
  1230. q := `
  1231. SELECT body
  1232. FROM bartItem
  1233. WHERE hash = ?
  1234. `
  1235. var body []byte
  1236. err := f.db.QueryRowContext(ctx, q, hash).Scan(&body)
  1237. if errors.Is(err, sql.ErrNoRows) {
  1238. err = nil
  1239. }
  1240. return body, err
  1241. }
  1242. // BARTItem represents a BART asset with its hash and type.
  1243. type BARTItem struct {
  1244. Hash string
  1245. Type uint16
  1246. }
  1247. func (f SQLiteUserStore) ListBARTItems(ctx context.Context, itemType uint16) ([]BARTItem, error) {
  1248. q := `
  1249. SELECT hash, type
  1250. FROM bartItem
  1251. WHERE type = ?
  1252. `
  1253. rows, err := f.db.QueryContext(ctx, q, itemType)
  1254. if err != nil {
  1255. return nil, err
  1256. }
  1257. defer rows.Close()
  1258. var items []BARTItem
  1259. for rows.Next() {
  1260. var item BARTItem
  1261. var hashBytes []byte
  1262. err := rows.Scan(&hashBytes, &item.Type)
  1263. if err != nil {
  1264. return nil, err
  1265. }
  1266. item.Hash = hex.EncodeToString(hashBytes)
  1267. items = append(items, item)
  1268. }
  1269. if err := rows.Err(); err != nil {
  1270. return nil, err
  1271. }
  1272. return items, nil
  1273. }
  1274. func (f SQLiteUserStore) DeleteBARTItem(ctx context.Context, hash []byte) error {
  1275. q := `
  1276. DELETE FROM bartItem
  1277. WHERE hash = ?
  1278. `
  1279. result, err := f.db.ExecContext(ctx, q, hash)
  1280. if err != nil {
  1281. return err
  1282. }
  1283. rowsAffected, err := result.RowsAffected()
  1284. if err != nil {
  1285. return err
  1286. }
  1287. if rowsAffected == 0 {
  1288. return ErrBARTItemNotFound
  1289. }
  1290. return nil
  1291. }
  1292. func (f SQLiteUserStore) ChatRoomByCookie(ctx context.Context, chatCookie string) (ChatRoom, error) {
  1293. chatRoom := ChatRoom{}
  1294. q := `
  1295. SELECT exchange, name, created, creator
  1296. FROM chatRoom
  1297. WHERE lower(cookie) = lower(?)
  1298. `
  1299. var creator string
  1300. err := f.db.QueryRowContext(ctx, q, chatCookie).Scan(
  1301. &chatRoom.exchange,
  1302. &chatRoom.name,
  1303. &chatRoom.createTime,
  1304. &creator,
  1305. )
  1306. if errors.Is(err, sql.ErrNoRows) {
  1307. err = fmt.Errorf("%w: %s", ErrChatRoomNotFound, chatCookie)
  1308. }
  1309. chatRoom.creator = NewIdentScreenName(creator)
  1310. return chatRoom, err
  1311. }
  1312. func (f SQLiteUserStore) ChatRoomByName(ctx context.Context, exchange uint16, name string) (ChatRoom, error) {
  1313. chatRoom := ChatRoom{
  1314. exchange: exchange,
  1315. }
  1316. q := `
  1317. SELECT name, created, creator
  1318. FROM chatRoom
  1319. WHERE exchange = ? AND lower(name) = lower(?)
  1320. `
  1321. var creator string
  1322. err := f.db.QueryRowContext(ctx, q, exchange, name).Scan(
  1323. &chatRoom.name,
  1324. &chatRoom.createTime,
  1325. &creator,
  1326. )
  1327. if errors.Is(err, sql.ErrNoRows) {
  1328. err = ErrChatRoomNotFound
  1329. }
  1330. chatRoom.creator = NewIdentScreenName(creator)
  1331. return chatRoom, err
  1332. }
  1333. func (f SQLiteUserStore) CreateChatRoom(ctx context.Context, chatRoom *ChatRoom) error {
  1334. chatRoom.createTime = time.Now().UTC()
  1335. q := `
  1336. INSERT INTO chatRoom (cookie, exchange, name, created, creator)
  1337. VALUES (?, ?, ?, ?, ?)
  1338. `
  1339. _, err := f.db.ExecContext(ctx,
  1340. q,
  1341. chatRoom.Cookie(),
  1342. chatRoom.Exchange(),
  1343. chatRoom.Name(),
  1344. chatRoom.createTime,
  1345. chatRoom.Creator().String(),
  1346. )
  1347. if err != nil {
  1348. if strings.Contains(err.Error(), "constraint failed") {
  1349. err = ErrDupChatRoom
  1350. }
  1351. err = fmt.Errorf("CreateChatRoom: %w", err)
  1352. }
  1353. return err
  1354. }
  1355. func (f SQLiteUserStore) AllChatRooms(ctx context.Context, exchange uint16) ([]ChatRoom, error) {
  1356. q := `
  1357. SELECT created, creator, name
  1358. FROM chatRoom
  1359. WHERE exchange = ?
  1360. ORDER BY created ASC
  1361. `
  1362. rows, err := f.db.QueryContext(ctx, q, exchange)
  1363. if err != nil {
  1364. return nil, err
  1365. }
  1366. defer rows.Close()
  1367. var users []ChatRoom
  1368. for rows.Next() {
  1369. cr := ChatRoom{
  1370. exchange: exchange,
  1371. }
  1372. var creator string
  1373. if err := rows.Scan(&cr.createTime, &creator, &cr.name); err != nil {
  1374. return nil, err
  1375. }
  1376. cr.creator = NewIdentScreenName(creator)
  1377. users = append(users, cr)
  1378. }
  1379. if err := rows.Err(); err != nil {
  1380. return nil, err
  1381. }
  1382. return users, nil
  1383. }
  1384. func (f SQLiteUserStore) DeleteChatRooms(ctx context.Context, exchange uint16, names []string) error {
  1385. if len(names) == 0 {
  1386. return nil
  1387. }
  1388. // Build the query with placeholders for each name
  1389. placeholders := make([]string, len(names))
  1390. args := make([]interface{}, 0, len(names)+1)
  1391. args = append(args, exchange)
  1392. for i, name := range names {
  1393. placeholders[i] = "?"
  1394. args = append(args, name)
  1395. }
  1396. q := fmt.Sprintf(`
  1397. DELETE FROM chatRoom
  1398. WHERE exchange = ? AND name IN (%s)
  1399. `, strings.Join(placeholders, ","))
  1400. _, err := f.db.ExecContext(ctx, q, args...)
  1401. if err != nil {
  1402. return fmt.Errorf("DeleteChatRooms: %w", err)
  1403. }
  1404. return nil
  1405. }
  1406. func (f SQLiteUserStore) UpdateDisplayScreenName(ctx context.Context, displayScreenName DisplayScreenName) error {
  1407. q := `
  1408. UPDATE users
  1409. SET displayScreenName = ?
  1410. WHERE identScreenName = ?
  1411. `
  1412. _, err := f.db.ExecContext(ctx, q, displayScreenName.String(), displayScreenName.IdentScreenName().String())
  1413. return err
  1414. }
  1415. func (f SQLiteUserStore) UpdateEmailAddress(ctx context.Context, screenName IdentScreenName, emailAddress *mail.Address) error {
  1416. q := `
  1417. UPDATE users
  1418. SET emailAddress = ?
  1419. WHERE identScreenName = ?
  1420. `
  1421. _, err := f.db.ExecContext(ctx, q, emailAddress.Address, screenName.String())
  1422. return err
  1423. }
  1424. func (f SQLiteUserStore) EmailAddress(ctx context.Context, screenName IdentScreenName) (*mail.Address, error) {
  1425. q := `
  1426. SELECT emailAddress
  1427. FROM users
  1428. WHERE identScreenName = ?
  1429. `
  1430. var emailAddress string
  1431. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&emailAddress)
  1432. // username isn't found for some reason
  1433. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1434. return nil, err
  1435. }
  1436. e, err := mail.ParseAddress(emailAddress)
  1437. if err != nil {
  1438. return nil, fmt.Errorf("%w: %w", ErrNoEmailAddress, err)
  1439. }
  1440. return e, nil
  1441. }
  1442. func (f SQLiteUserStore) UpdateRegStatus(ctx context.Context, screenName IdentScreenName, regStatus uint16) error {
  1443. q := `
  1444. UPDATE users
  1445. SET regStatus = ?
  1446. WHERE identScreenName = ?
  1447. `
  1448. _, err := f.db.ExecContext(ctx, q, regStatus, screenName.String())
  1449. return err
  1450. }
  1451. func (f SQLiteUserStore) RegStatus(ctx context.Context, screenName IdentScreenName) (uint16, error) {
  1452. q := `
  1453. SELECT regStatus
  1454. FROM users
  1455. WHERE identScreenName = ?
  1456. `
  1457. var regStatus uint16
  1458. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&regStatus)
  1459. // username isn't found for some reason
  1460. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1461. return 0, err
  1462. }
  1463. return regStatus, nil
  1464. }
  1465. func (f SQLiteUserStore) UpdateConfirmStatus(ctx context.Context, screenName IdentScreenName, confirmStatus bool) error {
  1466. q := `
  1467. UPDATE users
  1468. SET confirmStatus = ?
  1469. WHERE identScreenName = ?
  1470. `
  1471. _, err := f.db.ExecContext(ctx, q, confirmStatus, screenName.String())
  1472. return err
  1473. }
  1474. func (f SQLiteUserStore) ConfirmStatus(ctx context.Context, screenName IdentScreenName) (bool, error) {
  1475. q := `
  1476. SELECT confirmStatus
  1477. FROM users
  1478. WHERE identScreenName = ?
  1479. `
  1480. var confirmStatus bool
  1481. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&confirmStatus)
  1482. // username isn't found for some reason
  1483. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1484. return false, err
  1485. }
  1486. return confirmStatus, nil
  1487. }
  1488. func (f SQLiteUserStore) UpdateSuspendedStatus(ctx context.Context, suspendedStatus uint16, screenName IdentScreenName) error {
  1489. q := `
  1490. UPDATE users
  1491. SET suspendedStatus = ?
  1492. WHERE identScreenName = ?
  1493. `
  1494. _, err := f.db.ExecContext(ctx, q, suspendedStatus, screenName.String())
  1495. return err
  1496. }
  1497. func (f SQLiteUserStore) SetBotStatus(ctx context.Context, isBot bool, screenName IdentScreenName) error {
  1498. q := `
  1499. UPDATE users
  1500. SET isBot = ?
  1501. WHERE identScreenName = ?
  1502. `
  1503. _, err := f.db.ExecContext(ctx, q, isBot, screenName.String())
  1504. return err
  1505. }
  1506. func (f SQLiteUserStore) SetWorkInfo(ctx context.Context, name IdentScreenName, data ICQWorkInfo) error {
  1507. q := `
  1508. UPDATE users SET
  1509. icq_workInfo_company = ?,
  1510. icq_workInfo_department = ?,
  1511. icq_workInfo_occupationCode = ?,
  1512. icq_workInfo_position = ?,
  1513. icq_workInfo_address = ?,
  1514. icq_workInfo_city = ?,
  1515. icq_workInfo_countryCode = ?,
  1516. icq_workInfo_fax = ?,
  1517. icq_workInfo_phone = ?,
  1518. icq_workInfo_state = ?,
  1519. icq_workInfo_webPage = ?,
  1520. icq_workInfo_zipCode = ?
  1521. WHERE identScreenName = ?
  1522. `
  1523. res, err := f.db.ExecContext(ctx,
  1524. q,
  1525. data.Company,
  1526. data.Department,
  1527. data.OccupationCode,
  1528. data.Position,
  1529. data.Address,
  1530. data.City,
  1531. data.CountryCode,
  1532. data.Fax,
  1533. data.Phone,
  1534. data.State,
  1535. data.WebPage,
  1536. data.ZIPCode,
  1537. name.String(),
  1538. )
  1539. if err != nil {
  1540. return fmt.Errorf("exec: %w", err)
  1541. }
  1542. c, err := res.RowsAffected()
  1543. if err != nil {
  1544. return fmt.Errorf("rows affected: %w", err)
  1545. }
  1546. if c == 0 {
  1547. return ErrNoUser
  1548. }
  1549. return nil
  1550. }
  1551. func (f SQLiteUserStore) SetPermissions(ctx context.Context, name IdentScreenName, data ICQPermissions) error {
  1552. q := `
  1553. UPDATE users SET
  1554. icq_permissions_authRequired = ?,
  1555. icq_permissions_webAware = ?,
  1556. icq_permissions_allowSpam = ?
  1557. WHERE identScreenName = ?
  1558. `
  1559. res, err := f.db.ExecContext(ctx,
  1560. q,
  1561. data.AuthRequired,
  1562. data.WebAware,
  1563. data.AllowSpam,
  1564. name.String(),
  1565. )
  1566. if err != nil {
  1567. return fmt.Errorf("exec: %w", err)
  1568. }
  1569. c, err := res.RowsAffected()
  1570. if err != nil {
  1571. return fmt.Errorf("rows affected: %w", err)
  1572. }
  1573. if c == 0 {
  1574. return ErrNoUser
  1575. }
  1576. return nil
  1577. }
  1578. // SetHomepageCategory updates the user's homepage category information.
  1579. // This is used by the V5 META_SET_HPCAT (0x0442) command.
  1580. // From iserverd db_users_sethpagecat_info() - updates user's homepage category.
  1581. func (f SQLiteUserStore) SetHomepageCategory(ctx context.Context, name IdentScreenName, data ICQHomepageCategory) error {
  1582. q := `
  1583. UPDATE users SET
  1584. icq_homepageCategory_enabled = ?,
  1585. icq_homepageCategory_index = ?,
  1586. icq_homepageCategory_description = ?
  1587. WHERE identScreenName = ?
  1588. `
  1589. res, err := f.db.ExecContext(ctx,
  1590. q,
  1591. data.Enabled,
  1592. data.Index,
  1593. data.Description,
  1594. name.String(),
  1595. )
  1596. if err != nil {
  1597. return fmt.Errorf("exec: %w", err)
  1598. }
  1599. c, err := res.RowsAffected()
  1600. if err != nil {
  1601. return fmt.Errorf("rows affected: %w", err)
  1602. }
  1603. if c == 0 {
  1604. return ErrNoUser
  1605. }
  1606. return nil
  1607. }
  1608. func (f SQLiteUserStore) SetMoreInfo(ctx context.Context, name IdentScreenName, data ICQMoreInfo) error {
  1609. q := `
  1610. UPDATE users SET
  1611. icq_moreInfo_birthDay = ?,
  1612. icq_moreInfo_birthMonth = ?,
  1613. icq_moreInfo_birthYear = ?,
  1614. icq_moreInfo_gender = ?,
  1615. icq_moreInfo_homePageAddr = ?,
  1616. icq_moreInfo_lang1 = ?,
  1617. icq_moreInfo_lang2 = ?,
  1618. icq_moreInfo_lang3 = ?
  1619. WHERE identScreenName = ?
  1620. `
  1621. res, err := f.db.ExecContext(ctx,
  1622. q,
  1623. data.BirthDay,
  1624. data.BirthMonth,
  1625. data.BirthYear,
  1626. data.Gender,
  1627. data.HomePageAddr,
  1628. data.Lang1,
  1629. data.Lang2,
  1630. data.Lang3,
  1631. name.String(),
  1632. )
  1633. if err != nil {
  1634. return fmt.Errorf("exec: %w", err)
  1635. }
  1636. c, err := res.RowsAffected()
  1637. if err != nil {
  1638. return fmt.Errorf("rows affected: %w", err)
  1639. }
  1640. if c == 0 {
  1641. return ErrNoUser
  1642. }
  1643. return nil
  1644. }
  1645. func (f SQLiteUserStore) SetUserNotes(ctx context.Context, name IdentScreenName, data ICQUserNotes) error {
  1646. q := `
  1647. UPDATE users
  1648. SET icq_notes = ?
  1649. WHERE identScreenName = ?
  1650. `
  1651. res, err := f.db.ExecContext(ctx,
  1652. q,
  1653. data.Notes,
  1654. name.String(),
  1655. )
  1656. if err != nil {
  1657. return fmt.Errorf("exec: %w", err)
  1658. }
  1659. c, err := res.RowsAffected()
  1660. if err != nil {
  1661. return fmt.Errorf("rows affected: %w", err)
  1662. }
  1663. if c == 0 {
  1664. return ErrNoUser
  1665. }
  1666. return nil
  1667. }
  1668. func (f SQLiteUserStore) SetInterests(ctx context.Context, name IdentScreenName, data ICQInterests) error {
  1669. q := `
  1670. UPDATE users SET
  1671. icq_interests_code1 = ?,
  1672. icq_interests_keyword1 = ?,
  1673. icq_interests_code2 = ?,
  1674. icq_interests_keyword2 = ?,
  1675. icq_interests_code3 = ?,
  1676. icq_interests_keyword3 = ?,
  1677. icq_interests_code4 = ?,
  1678. icq_interests_keyword4 = ?
  1679. WHERE identScreenName = ?
  1680. `
  1681. res, err := f.db.ExecContext(ctx,
  1682. q,
  1683. data.Code1,
  1684. data.Keyword1,
  1685. data.Code2,
  1686. data.Keyword2,
  1687. data.Code3,
  1688. data.Keyword3,
  1689. data.Code4,
  1690. data.Keyword4,
  1691. name.String(),
  1692. )
  1693. if err != nil {
  1694. return fmt.Errorf("exec: %w", err)
  1695. }
  1696. c, err := res.RowsAffected()
  1697. if err != nil {
  1698. return fmt.Errorf("rows affected: %w", err)
  1699. }
  1700. if c == 0 {
  1701. return ErrNoUser
  1702. }
  1703. return nil
  1704. }
  1705. func (f SQLiteUserStore) SetAffiliations(ctx context.Context, name IdentScreenName, data ICQAffiliations) error {
  1706. q := `
  1707. UPDATE users SET
  1708. icq_affiliations_currentCode1 = ?,
  1709. icq_affiliations_currentKeyword1 = ?,
  1710. icq_affiliations_currentCode2 = ?,
  1711. icq_affiliations_currentKeyword2 = ?,
  1712. icq_affiliations_currentCode3 = ?,
  1713. icq_affiliations_currentKeyword3 = ?,
  1714. icq_affiliations_pastCode1 = ?,
  1715. icq_affiliations_pastKeyword1 = ?,
  1716. icq_affiliations_pastCode2 = ?,
  1717. icq_affiliations_pastKeyword2 = ?,
  1718. icq_affiliations_pastCode3 = ?,
  1719. icq_affiliations_pastKeyword3 = ?
  1720. WHERE identScreenName = ?
  1721. `
  1722. res, err := f.db.ExecContext(ctx,
  1723. q,
  1724. data.CurrentCode1,
  1725. data.CurrentKeyword1,
  1726. data.CurrentCode2,
  1727. data.CurrentKeyword2,
  1728. data.CurrentCode3,
  1729. data.CurrentKeyword3,
  1730. data.PastCode1,
  1731. data.PastKeyword1,
  1732. data.PastCode2,
  1733. data.PastKeyword2,
  1734. data.PastCode3,
  1735. data.PastKeyword3,
  1736. name.String(),
  1737. )
  1738. if err != nil {
  1739. return fmt.Errorf("exec: %w", err)
  1740. }
  1741. c, err := res.RowsAffected()
  1742. if err != nil {
  1743. return fmt.Errorf("rows affected: %w", err)
  1744. }
  1745. if c == 0 {
  1746. return ErrNoUser
  1747. }
  1748. return nil
  1749. }
  1750. func (f SQLiteUserStore) SetBasicInfo(ctx context.Context, name IdentScreenName, data ICQBasicInfo) error {
  1751. q := `
  1752. UPDATE users SET
  1753. icq_basicInfo_cellPhone = ?,
  1754. icq_basicInfo_countryCode = ?,
  1755. icq_basicInfo_emailAddress = ?,
  1756. icq_basicInfo_firstName = ?,
  1757. icq_basicInfo_gmtOffset = ?,
  1758. icq_basicInfo_address = ?,
  1759. icq_basicInfo_city = ?,
  1760. icq_basicInfo_fax = ?,
  1761. icq_basicInfo_phone = ?,
  1762. icq_basicInfo_state = ?,
  1763. icq_basicInfo_lastName = ?,
  1764. icq_basicInfo_nickName = ?,
  1765. icq_basicInfo_publishEmail = ?,
  1766. icq_basicInfo_zipCode = ?
  1767. WHERE identScreenName = ?
  1768. `
  1769. res, err := f.db.ExecContext(ctx,
  1770. q,
  1771. data.CellPhone,
  1772. data.CountryCode,
  1773. data.EmailAddress,
  1774. data.FirstName,
  1775. data.GMTOffset,
  1776. data.Address,
  1777. data.City,
  1778. data.Fax,
  1779. data.Phone,
  1780. data.State,
  1781. data.LastName,
  1782. data.Nickname,
  1783. data.PublishEmail,
  1784. data.ZIPCode,
  1785. name.String(),
  1786. )
  1787. if err != nil {
  1788. return fmt.Errorf("exec: %w", err)
  1789. }
  1790. c, err := res.RowsAffected()
  1791. if err != nil {
  1792. return fmt.Errorf("rows affected: %w", err)
  1793. }
  1794. if c == 0 {
  1795. return ErrNoUser
  1796. }
  1797. return nil
  1798. }
  1799. // SetICQInfo updates all ICQ profile columns on the user row in one UPDATE.
  1800. // Keep this column list aligned with SetMoreInfo, SetWorkInfo, SetPermissions,
  1801. // SetUserNotes, SetInterests, SetAffiliations, and SetHomepageCategory. Basic-info
  1802. // columns that are not on SetBasicInfo (e.g. originally-from) live only here.
  1803. func (f SQLiteUserStore) SetICQInfo(ctx context.Context, name IdentScreenName, info ICQInfo) error {
  1804. q := `
  1805. UPDATE users SET
  1806. icq_basicInfo_cellPhone = ?,
  1807. icq_basicInfo_countryCode = ?,
  1808. icq_basicInfo_emailAddress = ?,
  1809. icq_basicInfo_firstName = ?,
  1810. icq_basicInfo_gmtOffset = ?,
  1811. icq_basicInfo_address = ?,
  1812. icq_basicInfo_city = ?,
  1813. icq_basicInfo_fax = ?,
  1814. icq_basicInfo_phone = ?,
  1815. icq_basicInfo_state = ?,
  1816. icq_basicInfo_lastName = ?,
  1817. icq_basicInfo_nickName = ?,
  1818. icq_basicInfo_publishEmail = ?,
  1819. icq_basicInfo_zipCode = ?,
  1820. icq_basicInfo_originCity = ?,
  1821. icq_basicInfo_originState = ?,
  1822. icq_basicInfo_originCountryCode = ?,
  1823. icq_moreInfo_birthDay = ?,
  1824. icq_moreInfo_birthMonth = ?,
  1825. icq_moreInfo_birthYear = ?,
  1826. icq_moreInfo_gender = ?,
  1827. icq_moreInfo_homePageAddr = ?,
  1828. icq_moreInfo_lang1 = ?,
  1829. icq_moreInfo_lang2 = ?,
  1830. icq_moreInfo_lang3 = ?,
  1831. icq_workInfo_company = ?,
  1832. icq_workInfo_department = ?,
  1833. icq_workInfo_occupationCode = ?,
  1834. icq_workInfo_position = ?,
  1835. icq_workInfo_address = ?,
  1836. icq_workInfo_city = ?,
  1837. icq_workInfo_countryCode = ?,
  1838. icq_workInfo_fax = ?,
  1839. icq_workInfo_phone = ?,
  1840. icq_workInfo_state = ?,
  1841. icq_workInfo_webPage = ?,
  1842. icq_workInfo_zipCode = ?,
  1843. icq_permissions_authRequired = ?,
  1844. icq_permissions_webAware = ?,
  1845. icq_permissions_allowSpam = ?,
  1846. icq_notes = ?,
  1847. icq_interests_code1 = ?,
  1848. icq_interests_keyword1 = ?,
  1849. icq_interests_code2 = ?,
  1850. icq_interests_keyword2 = ?,
  1851. icq_interests_code3 = ?,
  1852. icq_interests_keyword3 = ?,
  1853. icq_interests_code4 = ?,
  1854. icq_interests_keyword4 = ?,
  1855. icq_affiliations_currentCode1 = ?,
  1856. icq_affiliations_currentKeyword1 = ?,
  1857. icq_affiliations_currentCode2 = ?,
  1858. icq_affiliations_currentKeyword2 = ?,
  1859. icq_affiliations_currentCode3 = ?,
  1860. icq_affiliations_currentKeyword3 = ?,
  1861. icq_affiliations_pastCode1 = ?,
  1862. icq_affiliations_pastKeyword1 = ?,
  1863. icq_affiliations_pastCode2 = ?,
  1864. icq_affiliations_pastKeyword2 = ?,
  1865. icq_affiliations_pastCode3 = ?,
  1866. icq_affiliations_pastKeyword3 = ?,
  1867. icq_homepageCategory_enabled = ?,
  1868. icq_homepageCategory_index = ?,
  1869. icq_homepageCategory_description = ?
  1870. WHERE identScreenName = ?
  1871. `
  1872. res, err := f.db.ExecContext(ctx, q,
  1873. info.Basic.CellPhone,
  1874. info.Basic.CountryCode,
  1875. info.Basic.EmailAddress,
  1876. info.Basic.FirstName,
  1877. info.Basic.GMTOffset,
  1878. info.Basic.Address,
  1879. info.Basic.City,
  1880. info.Basic.Fax,
  1881. info.Basic.Phone,
  1882. info.Basic.State,
  1883. info.Basic.LastName,
  1884. info.Basic.Nickname,
  1885. info.Basic.PublishEmail,
  1886. info.Basic.ZIPCode,
  1887. info.Basic.OriginallyFromCity,
  1888. info.Basic.OriginallyFromState,
  1889. info.Basic.OriginallyFromCountryCode,
  1890. info.More.BirthDay,
  1891. info.More.BirthMonth,
  1892. info.More.BirthYear,
  1893. info.More.Gender,
  1894. info.More.HomePageAddr,
  1895. info.More.Lang1,
  1896. info.More.Lang2,
  1897. info.More.Lang3,
  1898. info.Work.Company,
  1899. info.Work.Department,
  1900. info.Work.OccupationCode,
  1901. info.Work.Position,
  1902. info.Work.Address,
  1903. info.Work.City,
  1904. info.Work.CountryCode,
  1905. info.Work.Fax,
  1906. info.Work.Phone,
  1907. info.Work.State,
  1908. info.Work.WebPage,
  1909. info.Work.ZIPCode,
  1910. info.Permissions.AuthRequired,
  1911. info.Permissions.WebAware,
  1912. info.Permissions.AllowSpam,
  1913. info.Notes.Notes,
  1914. info.Interests.Code1,
  1915. info.Interests.Keyword1,
  1916. info.Interests.Code2,
  1917. info.Interests.Keyword2,
  1918. info.Interests.Code3,
  1919. info.Interests.Keyword3,
  1920. info.Interests.Code4,
  1921. info.Interests.Keyword4,
  1922. info.Affiliations.CurrentCode1,
  1923. info.Affiliations.CurrentKeyword1,
  1924. info.Affiliations.CurrentCode2,
  1925. info.Affiliations.CurrentKeyword2,
  1926. info.Affiliations.CurrentCode3,
  1927. info.Affiliations.CurrentKeyword3,
  1928. info.Affiliations.PastCode1,
  1929. info.Affiliations.PastKeyword1,
  1930. info.Affiliations.PastCode2,
  1931. info.Affiliations.PastKeyword2,
  1932. info.Affiliations.PastCode3,
  1933. info.Affiliations.PastKeyword3,
  1934. info.HomepageCategory.Enabled,
  1935. info.HomepageCategory.Index,
  1936. info.HomepageCategory.Description,
  1937. name.String(),
  1938. )
  1939. if err != nil {
  1940. return fmt.Errorf("exec: %w", err)
  1941. }
  1942. c, err := res.RowsAffected()
  1943. if err != nil {
  1944. return fmt.Errorf("rows affected: %w", err)
  1945. }
  1946. if c == 0 {
  1947. return ErrNoUser
  1948. }
  1949. return nil
  1950. }
  1951. func (f SQLiteUserStore) SaveMessage(ctx context.Context, offlineMessage OfflineMessage) (newCount int, err error) {
  1952. buf := &bytes.Buffer{}
  1953. if err := wire.MarshalBE(offlineMessage.Message, buf); err != nil {
  1954. return 0, fmt.Errorf("marshal: %w", err)
  1955. }
  1956. var tx *sql.Tx
  1957. tx, err = f.db.BeginTx(ctx, nil)
  1958. if err != nil {
  1959. return 0, fmt.Errorf("begin tx: %w", err)
  1960. }
  1961. defer func() {
  1962. if err != nil {
  1963. _ = tx.Rollback()
  1964. }
  1965. }()
  1966. const countQuery = `
  1967. SELECT COUNT(1)
  1968. FROM offlineMessage
  1969. WHERE sender = ? AND recipient = ?
  1970. `
  1971. var currentCount int
  1972. if err = tx.QueryRowContext(
  1973. ctx,
  1974. countQuery,
  1975. offlineMessage.Sender.String(),
  1976. offlineMessage.Recipient.String(),
  1977. ).Scan(&currentCount); err != nil {
  1978. return 0, fmt.Errorf("count: %w", err)
  1979. }
  1980. if currentCount >= offlineInboxLimit {
  1981. err = ErrOfflineInboxFull
  1982. return 0, err
  1983. }
  1984. q := `
  1985. INSERT INTO offlineMessage (sender, recipient, message, sent)
  1986. VALUES (?, ?, ?, ?)
  1987. `
  1988. if _, err = tx.ExecContext(ctx,
  1989. q,
  1990. offlineMessage.Sender.String(),
  1991. offlineMessage.Recipient.String(),
  1992. buf.Bytes(),
  1993. offlineMessage.Sent,
  1994. ); err != nil {
  1995. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1996. err = ErrNoUser
  1997. } else {
  1998. err = fmt.Errorf("insert: %w", err)
  1999. }
  2000. return 0, err
  2001. }
  2002. newCount = currentCount + 1
  2003. updateQuery := `
  2004. UPDATE users
  2005. SET offlineMsgCount = ?
  2006. WHERE identScreenName = ?
  2007. `
  2008. _, err = tx.ExecContext(ctx,
  2009. updateQuery,
  2010. newCount,
  2011. offlineMessage.Recipient.String(),
  2012. )
  2013. if err != nil {
  2014. return 0, fmt.Errorf("update offlineMsgCount: %w", err)
  2015. }
  2016. if err = tx.Commit(); err != nil {
  2017. return 0, fmt.Errorf("commit: %w", err)
  2018. }
  2019. return newCount, nil
  2020. }
  2021. func (f SQLiteUserStore) RetrieveMessages(ctx context.Context, recip IdentScreenName) ([]OfflineMessage, error) {
  2022. q := `
  2023. SELECT
  2024. sender,
  2025. message,
  2026. sent
  2027. FROM offlineMessage
  2028. WHERE recipient = ?
  2029. `
  2030. rows, err := f.db.QueryContext(ctx, q, recip.String())
  2031. if err != nil {
  2032. return nil, err
  2033. }
  2034. defer rows.Close()
  2035. var messages []OfflineMessage
  2036. for rows.Next() {
  2037. var sender string
  2038. var buf []byte
  2039. var sent time.Time
  2040. if err := rows.Scan(&sender, &buf, &sent); err != nil {
  2041. return nil, err
  2042. }
  2043. var msg wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
  2044. if err := wire.UnmarshalBE(&msg, bytes.NewBuffer(buf)); err != nil {
  2045. return nil, fmt.Errorf("unmarshal: %w", err)
  2046. }
  2047. messages = append(messages, OfflineMessage{
  2048. Sender: NewIdentScreenName(sender),
  2049. Recipient: recip,
  2050. Message: msg,
  2051. Sent: sent,
  2052. })
  2053. }
  2054. if err := rows.Err(); err != nil {
  2055. return nil, err
  2056. }
  2057. return messages, nil
  2058. }
  2059. func (f SQLiteUserStore) DeleteMessages(ctx context.Context, recip IdentScreenName) error {
  2060. q := `
  2061. DELETE FROM offlineMessage WHERE recipient = ?
  2062. `
  2063. _, err := f.db.ExecContext(ctx, q, recip.String())
  2064. return err
  2065. }
  2066. func (f SQLiteUserStore) BuddyIconMetadata(ctx context.Context, screenName IdentScreenName) (*wire.BARTID, error) {
  2067. q := `
  2068. SELECT
  2069. groupID,
  2070. itemID,
  2071. classID,
  2072. name,
  2073. attributes
  2074. FROM feedBag
  2075. WHERE screenname = ? AND name = ? AND classID = ?
  2076. `
  2077. var item wire.FeedbagItem
  2078. var attrs []byte
  2079. err := f.db.QueryRowContext(ctx, q, screenName.String(), wire.BARTTypesBuddyIcon, wire.FeedbagClassIdBart).Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs)
  2080. if errors.Is(err, sql.ErrNoRows) {
  2081. return nil, nil
  2082. }
  2083. if err != nil {
  2084. return nil, err
  2085. }
  2086. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  2087. return nil, err
  2088. }
  2089. b, hasBuf := item.Bytes(wire.FeedbagAttributesBartInfo)
  2090. if !hasBuf {
  2091. return nil, errors.New("unable to extract icon payload")
  2092. }
  2093. bartInfo := wire.BARTInfo{}
  2094. if err := wire.UnmarshalBE(&bartInfo, bytes.NewBuffer(b)); err != nil {
  2095. return nil, err
  2096. }
  2097. return &wire.BARTID{
  2098. Type: wire.BARTTypesBuddyIcon,
  2099. BARTInfo: wire.BARTInfo{
  2100. Flags: bartInfo.Flags,
  2101. Hash: bartInfo.Hash,
  2102. },
  2103. }, nil
  2104. }
  2105. func (f SQLiteUserStore) SetKeywords(ctx context.Context, screenName IdentScreenName, keywords [5]string) error {
  2106. q := `
  2107. WITH interests AS (SELECT CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword1,
  2108. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword2,
  2109. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword3,
  2110. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword4,
  2111. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword5
  2112. FROM aimKeyword
  2113. WHERE name IN (?, ?, ?, ?, ?))
  2114. UPDATE users
  2115. SET aim_keyword1 = (SELECT aim_keyword1 FROM interests WHERE aim_keyword1 IS NOT NULL),
  2116. aim_keyword2 = (SELECT aim_keyword2 FROM interests WHERE aim_keyword2 IS NOT NULL),
  2117. aim_keyword3 = (SELECT aim_keyword3 FROM interests WHERE aim_keyword3 IS NOT NULL),
  2118. aim_keyword4 = (SELECT aim_keyword4 FROM interests WHERE aim_keyword4 IS NOT NULL),
  2119. aim_keyword5 = (SELECT aim_keyword5 FROM interests WHERE aim_keyword5 IS NOT NULL)
  2120. WHERE identScreenName = ?
  2121. `
  2122. _, err := f.db.ExecContext(ctx, q,
  2123. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  2124. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  2125. screenName.String())
  2126. return err
  2127. }
  2128. func (f SQLiteUserStore) Categories(ctx context.Context) ([]Category, error) {
  2129. q := `SELECT id, name FROM aimKeywordCategory ORDER BY name`
  2130. rows, err := f.db.QueryContext(ctx, q)
  2131. if err != nil {
  2132. return nil, err
  2133. }
  2134. defer rows.Close()
  2135. var categories []Category
  2136. for rows.Next() {
  2137. category := Category{}
  2138. if err := rows.Scan(&category.ID, &category.Name); err != nil {
  2139. return nil, err
  2140. }
  2141. categories = append(categories, category)
  2142. }
  2143. if err := rows.Err(); err != nil {
  2144. return nil, err
  2145. }
  2146. return categories, nil
  2147. }
  2148. func (f SQLiteUserStore) CreateCategory(ctx context.Context, name string) (Category, error) {
  2149. tx, err := f.db.Begin()
  2150. if err != nil {
  2151. return Category{}, err
  2152. }
  2153. defer tx.Rollback()
  2154. q := `INSERT INTO aimKeywordCategory (name) VALUES (?)`
  2155. res, err := tx.ExecContext(ctx, q, name)
  2156. if err != nil {
  2157. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  2158. err = ErrKeywordCategoryExists
  2159. }
  2160. return Category{}, err
  2161. }
  2162. id, err := res.LastInsertId()
  2163. if err != nil {
  2164. return Category{}, err
  2165. }
  2166. if id > math.MaxUint8 {
  2167. return Category{}, errTooManyCategories
  2168. }
  2169. if err := tx.Commit(); err != nil {
  2170. return Category{}, err
  2171. }
  2172. return Category{
  2173. ID: uint8(id),
  2174. Name: name,
  2175. }, nil
  2176. }
  2177. func (f SQLiteUserStore) DeleteCategory(ctx context.Context, categoryID uint8) error {
  2178. q := `DELETE FROM aimKeywordCategory WHERE id = ?`
  2179. res, err := f.db.ExecContext(ctx, q, categoryID)
  2180. if err != nil {
  2181. // Check if the error is a foreign key constraint violation
  2182. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2183. return ErrKeywordInUse
  2184. }
  2185. }
  2186. c, err := res.RowsAffected()
  2187. if err != nil {
  2188. return err
  2189. }
  2190. if c == 0 {
  2191. return ErrKeywordCategoryNotFound
  2192. }
  2193. return nil
  2194. }
  2195. func (f SQLiteUserStore) KeywordsByCategory(ctx context.Context, categoryID uint8) ([]Keyword, error) {
  2196. q := `SELECT id, name FROM aimKeyword WHERE parent = ? ORDER BY name`
  2197. if categoryID == 0 {
  2198. q = `SELECT id, name FROM aimKeyword WHERE parent IS NULL ORDER BY name`
  2199. }
  2200. rows, err := f.db.QueryContext(ctx, q, categoryID)
  2201. if err != nil {
  2202. return nil, err
  2203. }
  2204. defer rows.Close()
  2205. var keywords []Keyword
  2206. for rows.Next() {
  2207. keyword := Keyword{}
  2208. if err := rows.Scan(&keyword.ID, &keyword.Name); err != nil {
  2209. return nil, err
  2210. }
  2211. keywords = append(keywords, keyword)
  2212. }
  2213. if err := rows.Err(); err != nil {
  2214. return nil, err
  2215. }
  2216. if len(keywords) == 0 {
  2217. var exists int
  2218. err = f.db.QueryRow("SELECT COUNT(*) FROM aimKeywordCategory WHERE id = ?", categoryID).Scan(&exists)
  2219. if err != nil {
  2220. return nil, err
  2221. }
  2222. if exists == 0 {
  2223. return nil, ErrKeywordCategoryNotFound
  2224. }
  2225. }
  2226. return keywords, nil
  2227. }
  2228. func (f SQLiteUserStore) CreateKeyword(ctx context.Context, name string, categoryID uint8) (Keyword, error) {
  2229. tx, err := f.db.Begin()
  2230. if err != nil {
  2231. return Keyword{}, err
  2232. }
  2233. defer tx.Rollback()
  2234. q := `INSERT INTO aimKeyword (name, parent) VALUES (?, ?)`
  2235. var parent interface{} = nil
  2236. if categoryID != 0 {
  2237. parent = categoryID
  2238. }
  2239. res, err := tx.ExecContext(ctx, q, name, parent)
  2240. if err != nil {
  2241. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  2242. err = ErrKeywordExists
  2243. } else if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2244. err = ErrKeywordCategoryNotFound
  2245. }
  2246. return Keyword{}, err
  2247. }
  2248. id, err := res.LastInsertId()
  2249. if err != nil {
  2250. return Keyword{}, err
  2251. }
  2252. if id > math.MaxUint8 {
  2253. return Keyword{}, errTooManyKeywords
  2254. }
  2255. if err := tx.Commit(); err != nil {
  2256. return Keyword{}, err
  2257. }
  2258. return Keyword{
  2259. ID: uint8(id),
  2260. Name: name,
  2261. }, nil
  2262. }
  2263. func (f SQLiteUserStore) DeleteKeyword(ctx context.Context, id uint8) error {
  2264. q := `DELETE FROM aimKeyword WHERE id = ?`
  2265. res, err := f.db.ExecContext(ctx, q, id)
  2266. if err != nil {
  2267. // Check if the error is a foreign key constraint violation
  2268. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2269. return ErrKeywordInUse
  2270. }
  2271. }
  2272. c, err := res.RowsAffected()
  2273. if err != nil {
  2274. return err
  2275. }
  2276. if c == 0 {
  2277. return ErrKeywordNotFound
  2278. }
  2279. return nil
  2280. }
  2281. // InterestList returns a list of keywords grouped by category used to render
  2282. // the AIM directory interests list. The list is made up of 3 types of elements:
  2283. //
  2284. // Categories
  2285. //
  2286. // ID: The category ID
  2287. // Cookie: The category name
  2288. // Type: [wire.ODirKeywordCategory]
  2289. //
  2290. // Keywords
  2291. //
  2292. // ID: The parent category ID
  2293. // Cookie: The keyword name
  2294. // Type: [wire.ODirKeyword]
  2295. //
  2296. // Top-level Keywords
  2297. //
  2298. // ID: 0 (does not have a parent category)
  2299. // Cookie: The keyword name
  2300. // Type: [wire.ODirKeyword]
  2301. //
  2302. // Keywords are grouped contiguously by category and preceded by the category
  2303. // name. Top-level keywords appear by themselves. Categories and top-level
  2304. // keywords are sorted alphabetically. Keyword groups are sorted alphabetically.
  2305. //
  2306. // Conceptually, the list looks like this:
  2307. //
  2308. // > Animals (top-level keyword, id=0)
  2309. // > Artificial Intelligence (keyword, id=3)
  2310. // > Cybersecurity (keyword, id=3)
  2311. // > Music (category, id=1)
  2312. // > Jazz (keyword, id=1)
  2313. // > Rock (keyword, id=1)
  2314. // > Sports (category, id=2)
  2315. // > Basketball (keyword, id=2)
  2316. // > Soccer (keyword, id=2)
  2317. // > Tennis (keyword, id=2)
  2318. // > Technology (category, id=3)
  2319. // > Zoology (top-level keyword, id=0)
  2320. func (f SQLiteUserStore) InterestList(ctx context.Context) ([]wire.ODirKeywordListItem, error) {
  2321. q := `
  2322. WITH categories AS (
  2323. SELECT
  2324. name AS grouping,
  2325. id,
  2326. 0 AS sortPrio,
  2327. name
  2328. FROM aimKeywordCategory
  2329. UNION
  2330. SELECT
  2331. IFNULL(akc.name, ak.name) AS grouping,
  2332. IFNULL(ak.parent, 0) AS id,
  2333. CASE WHEN ak.parent IS NULL THEN 1 ELSE 2 END AS sortPrio,
  2334. ak.name
  2335. FROM aimKeyword ak
  2336. LEFT JOIN aimKeywordCategory akc ON akc.id = ak.parent
  2337. ORDER BY 1, 3, 4
  2338. )
  2339. SELECT
  2340. id,
  2341. sortPrio,
  2342. name
  2343. FROM categories
  2344. `
  2345. rows, err := f.db.QueryContext(ctx, q)
  2346. if err != nil {
  2347. return nil, err
  2348. }
  2349. defer rows.Close()
  2350. var list []wire.ODirKeywordListItem
  2351. for rows.Next() {
  2352. msg := wire.ODirKeywordListItem{}
  2353. var sortPrio int
  2354. if err := rows.Scan(&msg.ID, &sortPrio, &msg.Name); err != nil {
  2355. return nil, err
  2356. }
  2357. switch sortPrio {
  2358. case 0:
  2359. msg.Type = wire.ODirKeywordCategory
  2360. case 1, 2:
  2361. msg.Type = wire.ODirKeyword
  2362. }
  2363. list = append(list, msg)
  2364. }
  2365. if err := rows.Err(); err != nil {
  2366. return nil, err
  2367. }
  2368. return list, nil
  2369. }
  2370. func (f SQLiteUserStore) SetTOCConfig(ctx context.Context, user IdentScreenName, config string) error {
  2371. q := `
  2372. UPDATE users
  2373. SET tocConfig = ?
  2374. WHERE identScreenName = ?
  2375. `
  2376. res, err := f.db.ExecContext(ctx,
  2377. q,
  2378. config,
  2379. user.String(),
  2380. )
  2381. if err != nil {
  2382. return fmt.Errorf("exec: %w", err)
  2383. }
  2384. c, err := res.RowsAffected()
  2385. if err != nil {
  2386. return fmt.Errorf("rows affected: %w", err)
  2387. }
  2388. if c == 0 {
  2389. return ErrNoUser
  2390. }
  2391. return nil
  2392. }
  2393. // SetWarnLevel updates the last warn update time and warning level for a user.
  2394. func (f SQLiteUserStore) SetWarnLevel(ctx context.Context, user IdentScreenName, lastWarnUpdate time.Time, lastWarnLevel uint16) error {
  2395. q := `
  2396. UPDATE users
  2397. SET lastWarnUpdate = ?, lastWarnLevel = ?
  2398. WHERE identScreenName = ?
  2399. `
  2400. res, err := f.db.ExecContext(ctx,
  2401. q,
  2402. lastWarnUpdate.Unix(),
  2403. lastWarnLevel,
  2404. user.String(),
  2405. )
  2406. if err != nil {
  2407. return fmt.Errorf("exec: %w", err)
  2408. }
  2409. c, err := res.RowsAffected()
  2410. if err != nil {
  2411. return fmt.Errorf("rows affected: %w", err)
  2412. }
  2413. if c == 0 {
  2414. return ErrNoUser
  2415. }
  2416. return nil
  2417. }
  2418. // SetOfflineMsgCount updates the offline message count for a user.
  2419. func (f SQLiteUserStore) SetOfflineMsgCount(ctx context.Context, screenName IdentScreenName, count int) error {
  2420. q := `
  2421. UPDATE users
  2422. SET offlineMsgCount = ?
  2423. WHERE identScreenName = ?
  2424. `
  2425. res, err := f.db.ExecContext(ctx,
  2426. q,
  2427. count,
  2428. screenName.String(),
  2429. )
  2430. if err != nil {
  2431. return fmt.Errorf("exec: %w", err)
  2432. }
  2433. c, err := res.RowsAffected()
  2434. if err != nil {
  2435. return fmt.Errorf("rows affected: %w", err)
  2436. }
  2437. if c == 0 {
  2438. return ErrNoUser
  2439. }
  2440. return nil
  2441. }