user_store.go 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684
  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. func (f SQLiteUserStore) DeleteUser(ctx context.Context, screenName IdentScreenName) error {
  779. q := `
  780. DELETE FROM users WHERE identScreenName = ?
  781. `
  782. result, err := f.db.ExecContext(ctx, q, screenName.String())
  783. if err != nil {
  784. return err
  785. }
  786. rowsAffected, err := result.RowsAffected()
  787. if err != nil {
  788. return err
  789. }
  790. if rowsAffected == 0 {
  791. return ErrNoUser
  792. }
  793. return nil
  794. }
  795. func (f SQLiteUserStore) SetUserPassword(ctx context.Context, screenName IdentScreenName, newPassword string) error {
  796. tx, err := f.db.Begin()
  797. if err != nil {
  798. return err
  799. }
  800. defer func() {
  801. if err != nil {
  802. err = errors.Join(err, tx.Rollback())
  803. }
  804. }()
  805. q := `
  806. SELECT
  807. authKey,
  808. isICQ
  809. FROM users
  810. WHERE identScreenName = ?
  811. `
  812. u := User{}
  813. err = tx.QueryRowContext(ctx, q, screenName.String()).Scan(
  814. &u.AuthKey,
  815. &u.IsICQ,
  816. )
  817. if errors.Is(err, sql.ErrNoRows) {
  818. return ErrNoUser
  819. }
  820. if err = u.HashPassword(newPassword); err != nil {
  821. return err
  822. }
  823. q = `
  824. UPDATE users
  825. SET authKey = ?, weakMD5Pass = ?, strongMD5Pass = ?
  826. WHERE identScreenName = ?
  827. `
  828. result, err := tx.ExecContext(ctx, q, u.AuthKey, u.WeakMD5Pass, u.StrongMD5Pass, screenName.String())
  829. if err != nil {
  830. return err
  831. }
  832. rowsAffected, err := result.RowsAffected()
  833. if err != nil {
  834. return err
  835. }
  836. if rowsAffected == 0 {
  837. // it's possible the user didn't change OR the user doesn't exist.
  838. // check if the user exists.
  839. var exists int
  840. err = tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE identScreenName = ?", u.IdentScreenName.String()).Scan(&exists)
  841. if err != nil {
  842. return err // Handle possible SQL errors during the select
  843. }
  844. if exists == 0 {
  845. err = ErrNoUser // User does not exist
  846. return err
  847. }
  848. }
  849. return tx.Commit()
  850. }
  851. func (f SQLiteUserStore) Feedbag(ctx context.Context, screenName IdentScreenName) ([]wire.FeedbagItem, error) {
  852. q := `
  853. SELECT
  854. groupID,
  855. itemID,
  856. classID,
  857. name,
  858. attributes
  859. FROM feedbag
  860. WHERE screenName = ?
  861. `
  862. rows, err := f.db.QueryContext(ctx, q, screenName.String())
  863. if err != nil {
  864. return nil, err
  865. }
  866. defer rows.Close()
  867. var items []wire.FeedbagItem
  868. for rows.Next() {
  869. var item wire.FeedbagItem
  870. var attrs []byte
  871. if err := rows.Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs); err != nil {
  872. return nil, err
  873. }
  874. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  875. return items, err
  876. }
  877. items = append(items, item)
  878. }
  879. return items, nil
  880. }
  881. func (f SQLiteUserStore) FeedbagLastModified(ctx context.Context, screenName IdentScreenName) (time.Time, error) {
  882. var lastModified sql.NullInt64
  883. q := `SELECT MAX(lastModified) FROM feedbag WHERE screenName = ?`
  884. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&lastModified)
  885. return time.Unix(lastModified.Int64, 0), err
  886. }
  887. func (f SQLiteUserStore) FeedbagDelete(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  888. // todo add transaction
  889. q := `DELETE FROM feedbag WHERE screenName = ? AND itemID = ? AND groupID = ?`
  890. for _, item := range items {
  891. if _, err := f.db.ExecContext(ctx, q, screenName.String(), item.ItemID, item.GroupID); err != nil {
  892. return err
  893. }
  894. }
  895. return nil
  896. }
  897. func (f SQLiteUserStore) FeedbagUpsert(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  898. q := `
  899. INSERT INTO feedbag (screenName, groupID, itemID, classID, name, attributes, pdMode, authPending, lastModified)
  900. VALUES (?, ?, ?, ?, ?, ?, ?, ?, UNIXEPOCH())
  901. ON CONFLICT (screenName, groupID, itemID)
  902. DO UPDATE SET classID = excluded.classID,
  903. name = excluded.name,
  904. attributes = excluded.attributes,
  905. pdMode = excluded.pdMode,
  906. authPending = excluded.authPending,
  907. lastModified = UNIXEPOCH()
  908. `
  909. for _, item := range items {
  910. buf := &bytes.Buffer{}
  911. if err := wire.MarshalBE(item.TLVLBlock, buf); err != nil {
  912. return err
  913. }
  914. if item.ClassID == wire.FeedbagClassIdBuddy ||
  915. item.ClassID == wire.FeedbagClassIDPermit ||
  916. item.ClassID == wire.FeedbagClassIdAlInfo ||
  917. item.ClassID == wire.FeedbagClassIDDeny {
  918. // insert screen name identifier
  919. item.Name = NewIdentScreenName(item.Name).String()
  920. }
  921. pdMode := uint8(0)
  922. if item.ClassID == wire.FeedbagClassIdPdinfo {
  923. var hasMode bool
  924. pdMode, hasMode = item.Uint8(wire.FeedbagAttributesPdMode)
  925. if !hasMode {
  926. // by default, QIP sends a PD info item entry with no mode
  927. pdMode = uint8(wire.FeedbagPDModePermitAll)
  928. }
  929. }
  930. authPending := item.ClassID == wire.FeedbagClassIdBuddy &&
  931. item.HasTag(wire.FeedbagAttributesPending)
  932. _, err := f.db.ExecContext(ctx,
  933. q,
  934. screenName.String(),
  935. item.GroupID,
  936. item.ItemID,
  937. item.ClassID,
  938. item.Name,
  939. buf.Bytes(),
  940. pdMode,
  941. authPending)
  942. if err != nil {
  943. return err
  944. }
  945. }
  946. return nil
  947. }
  948. func (f SQLiteUserStore) ClearBuddyListRegistry(ctx context.Context) error {
  949. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode`); err != nil {
  950. return err
  951. }
  952. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList`); err != nil {
  953. return err
  954. }
  955. return nil
  956. }
  957. func (f SQLiteUserStore) RegisterBuddyList(ctx context.Context, user IdentScreenName) error {
  958. q := `
  959. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  960. ON CONFLICT (screenName) DO NOTHING
  961. `
  962. _, err := f.db.ExecContext(ctx, q, user.String(), wire.FeedbagPDModePermitAll)
  963. return err
  964. }
  965. func (f SQLiteUserStore) UnregisterBuddyList(ctx context.Context, user IdentScreenName) error {
  966. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode WHERE screenName = ?`, user.String()); err != nil {
  967. return err
  968. }
  969. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList WHERE me = ?`, user.String()); err != nil {
  970. return err
  971. }
  972. return nil
  973. }
  974. func (f SQLiteUserStore) UseFeedbag(ctx context.Context, screenName IdentScreenName) error {
  975. q := `
  976. INSERT INTO buddyListMode (screenName, useFeedbag)
  977. VALUES (?, ?)
  978. ON CONFLICT (screenName)
  979. DO UPDATE SET clientSidePDMode = 0,
  980. useFeedbag = true
  981. `
  982. _, err := f.db.ExecContext(ctx, q, screenName.String(), true)
  983. return err
  984. }
  985. func (f SQLiteUserStore) SetPDMode(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  986. alreadySet, err := f.isPDModeEqual(ctx, me, pdMode)
  987. if err != nil {
  988. return fmt.Errorf("isPDModeEqual: %w", err)
  989. }
  990. if alreadySet {
  991. return nil
  992. }
  993. tx, err := f.db.Begin()
  994. if err != nil {
  995. return err
  996. }
  997. defer func() {
  998. _ = tx.Rollback()
  999. }()
  1000. if err := setClientSidePDMode(ctx, tx, me, pdMode); err != nil {
  1001. return fmt.Errorf("setClientSidePDMode: %w", err)
  1002. }
  1003. if err := clearClientSidePDFlags(ctx, tx, me, pdMode); err != nil {
  1004. return fmt.Errorf("clearClientSidePDFlags: %w", err)
  1005. }
  1006. if err := clearBlankClientSideBuddies(ctx, tx, me, pdMode); err != nil {
  1007. return fmt.Errorf("clearBlankClientSideBuddies: %w", err)
  1008. }
  1009. if err := tx.Commit(); err != nil {
  1010. return fmt.Errorf("commit: %w", err)
  1011. }
  1012. return nil
  1013. }
  1014. // isPDModeEqual indicates whether the current permit/deny mode is already set
  1015. // to pdMode.
  1016. func (f SQLiteUserStore) isPDModeEqual(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) (bool, error) {
  1017. q := `
  1018. SELECT true
  1019. FROM buddyListMode
  1020. WHERE screenName = ? AND clientSidePDMode = ?
  1021. `
  1022. var isEqual bool
  1023. err := f.db.QueryRowContext(ctx, q, me.String(), pdMode).Scan(&isEqual)
  1024. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1025. return false, err
  1026. }
  1027. return isEqual, nil
  1028. }
  1029. // setClientSidePDMode sets the permit/deny mode for my client-side buddy list.
  1030. func setClientSidePDMode(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1031. q := `
  1032. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  1033. ON CONFLICT (screenName)
  1034. DO UPDATE SET clientSidePDMode = excluded.clientSidePDMode
  1035. `
  1036. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1037. if err != nil {
  1038. return err
  1039. }
  1040. return nil
  1041. }
  1042. // clearBlankClientSideBuddies removes client-side buddy where all flags
  1043. // (isBuddy, isPermit, isDeny) are false.
  1044. func clearBlankClientSideBuddies(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1045. q := `
  1046. DELETE FROM clientSideBuddyList
  1047. WHERE isBuddy IS FALSE
  1048. AND isPermit IS FALSE
  1049. AND isDeny IS FALSE
  1050. AND me = ?
  1051. `
  1052. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1053. return err
  1054. }
  1055. // clearClientSidePDFlags clears permit/deny flags.
  1056. func clearClientSidePDFlags(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  1057. q := `
  1058. UPDATE clientSideBuddyList
  1059. SET isDeny = false, isPermit = false
  1060. WHERE me = ?
  1061. `
  1062. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  1063. return err
  1064. }
  1065. func (f SQLiteUserStore) AddBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1066. q := `
  1067. INSERT INTO clientSideBuddyList (me, them, isBuddy)
  1068. VALUES (?, ?, true)
  1069. ON CONFLICT (me, them) DO UPDATE SET isBuddy = true
  1070. `
  1071. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1072. return err
  1073. }
  1074. func (f SQLiteUserStore) RemoveBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1075. q := `
  1076. UPDATE clientSideBuddyList
  1077. SET isBuddy = false
  1078. WHERE me = ?
  1079. AND them = ?
  1080. `
  1081. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1082. return err
  1083. }
  1084. func (f SQLiteUserStore) DenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1085. q := `
  1086. INSERT INTO clientSideBuddyList (me, them, isDeny)
  1087. VALUES (?, ?, 1)
  1088. ON CONFLICT (me, them) DO UPDATE SET isDeny = 1
  1089. `
  1090. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1091. return err
  1092. }
  1093. func (f SQLiteUserStore) RemoveDenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1094. q := `
  1095. UPDATE clientSideBuddyList
  1096. SET isDeny = false
  1097. WHERE me = ?
  1098. AND them = ?
  1099. `
  1100. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1101. return err
  1102. }
  1103. func (f SQLiteUserStore) PermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1104. q := `
  1105. INSERT INTO clientSideBuddyList (me, them, isPermit)
  1106. VALUES (?, ?, 1)
  1107. ON CONFLICT (me, them) DO UPDATE SET isPermit = 1
  1108. `
  1109. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1110. return err
  1111. }
  1112. func (f SQLiteUserStore) RemovePermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  1113. q := `
  1114. UPDATE clientSideBuddyList
  1115. SET isPermit = false
  1116. WHERE me = ?
  1117. AND them = ?
  1118. `
  1119. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  1120. return err
  1121. }
  1122. func (f SQLiteUserStore) Profile(ctx context.Context, screenName IdentScreenName) (UserProfile, error) {
  1123. q := `
  1124. SELECT IFNULL(body, ''), IFNULL(mimeType, ''), IFNULL(updateTime, 0)
  1125. FROM profile
  1126. WHERE screenName = ?
  1127. `
  1128. var profile UserProfile
  1129. var updateTimeUnix int64
  1130. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&profile.ProfileText, &profile.MIMEType, &updateTimeUnix)
  1131. if errors.Is(err, sql.ErrNoRows) {
  1132. return UserProfile{}, nil
  1133. }
  1134. if err != nil {
  1135. return UserProfile{}, err
  1136. }
  1137. if updateTimeUnix > 0 {
  1138. profile.UpdateTime = time.Unix(updateTimeUnix, 0).UTC()
  1139. }
  1140. return profile, nil
  1141. }
  1142. func (f SQLiteUserStore) SetProfile(ctx context.Context, screenName IdentScreenName, profile UserProfile) error {
  1143. var updateTimeUnix int64
  1144. if !profile.UpdateTime.IsZero() {
  1145. updateTimeUnix = profile.UpdateTime.Unix()
  1146. }
  1147. q := `
  1148. INSERT INTO profile (screenName, body, mimeType, updateTime)
  1149. VALUES (?, ?, ?, ?)
  1150. ON CONFLICT (screenName)
  1151. DO UPDATE SET body = excluded.body,
  1152. mimeType = excluded.mimeType,
  1153. updateTime = excluded.updateTime
  1154. `
  1155. _, err := f.db.ExecContext(ctx, q, screenName.String(), profile.ProfileText, profile.MIMEType, updateTimeUnix)
  1156. return err
  1157. }
  1158. func (f SQLiteUserStore) SetDirectoryInfo(ctx context.Context, screenName IdentScreenName, info AIMNameAndAddr) error {
  1159. q := `
  1160. UPDATE users SET
  1161. aim_firstName = ?,
  1162. aim_lastName = ?,
  1163. aim_middleName = ?,
  1164. aim_maidenName = ?,
  1165. aim_country = ?,
  1166. aim_state = ?,
  1167. aim_city = ?,
  1168. aim_nickName = ?,
  1169. aim_zipCode = ?,
  1170. aim_address = ?
  1171. WHERE identScreenName = ?
  1172. `
  1173. res, err := f.db.ExecContext(ctx,
  1174. q,
  1175. info.FirstName,
  1176. info.LastName,
  1177. info.MiddleName,
  1178. info.MaidenName,
  1179. info.Country,
  1180. info.State,
  1181. info.City,
  1182. info.NickName,
  1183. info.ZIPCode,
  1184. info.Address,
  1185. screenName.String(),
  1186. )
  1187. if err != nil {
  1188. return fmt.Errorf("exec: %w", err)
  1189. }
  1190. c, err := res.RowsAffected()
  1191. if err != nil {
  1192. return fmt.Errorf("rows affected: %w", err)
  1193. }
  1194. if c == 0 {
  1195. return ErrNoUser
  1196. }
  1197. return nil
  1198. }
  1199. func (f SQLiteUserStore) InsertBARTItem(ctx context.Context, hash []byte, blob []byte, itemType uint16) error {
  1200. q := `
  1201. INSERT INTO bartItem (hash, body, type)
  1202. VALUES (?, ?, ?)
  1203. `
  1204. _, err := f.db.ExecContext(ctx, q, hash, blob, itemType)
  1205. if err != nil {
  1206. if liteErr, ok := err.(*sqlite.Error); ok {
  1207. code := liteErr.Code()
  1208. if code == lib.SQLITE_CONSTRAINT_PRIMARYKEY {
  1209. return ErrBARTItemExists
  1210. }
  1211. }
  1212. return err
  1213. }
  1214. return nil
  1215. }
  1216. func (f SQLiteUserStore) BARTItem(ctx context.Context, hash []byte) ([]byte, error) {
  1217. q := `
  1218. SELECT body
  1219. FROM bartItem
  1220. WHERE hash = ?
  1221. `
  1222. var body []byte
  1223. err := f.db.QueryRowContext(ctx, q, hash).Scan(&body)
  1224. if errors.Is(err, sql.ErrNoRows) {
  1225. err = nil
  1226. }
  1227. return body, err
  1228. }
  1229. // BARTItem represents a BART asset with its hash and type.
  1230. type BARTItem struct {
  1231. Hash string
  1232. Type uint16
  1233. }
  1234. func (f SQLiteUserStore) ListBARTItems(ctx context.Context, itemType uint16) ([]BARTItem, error) {
  1235. q := `
  1236. SELECT hash, type
  1237. FROM bartItem
  1238. WHERE type = ?
  1239. `
  1240. rows, err := f.db.QueryContext(ctx, q, itemType)
  1241. if err != nil {
  1242. return nil, err
  1243. }
  1244. defer rows.Close()
  1245. var items []BARTItem
  1246. for rows.Next() {
  1247. var item BARTItem
  1248. var hashBytes []byte
  1249. err := rows.Scan(&hashBytes, &item.Type)
  1250. if err != nil {
  1251. return nil, err
  1252. }
  1253. item.Hash = hex.EncodeToString(hashBytes)
  1254. items = append(items, item)
  1255. }
  1256. if err := rows.Err(); err != nil {
  1257. return nil, err
  1258. }
  1259. return items, nil
  1260. }
  1261. func (f SQLiteUserStore) DeleteBARTItem(ctx context.Context, hash []byte) error {
  1262. q := `
  1263. DELETE FROM bartItem
  1264. WHERE hash = ?
  1265. `
  1266. result, err := f.db.ExecContext(ctx, q, hash)
  1267. if err != nil {
  1268. return err
  1269. }
  1270. rowsAffected, err := result.RowsAffected()
  1271. if err != nil {
  1272. return err
  1273. }
  1274. if rowsAffected == 0 {
  1275. return ErrBARTItemNotFound
  1276. }
  1277. return nil
  1278. }
  1279. func (f SQLiteUserStore) ChatRoomByCookie(ctx context.Context, chatCookie string) (ChatRoom, error) {
  1280. chatRoom := ChatRoom{}
  1281. q := `
  1282. SELECT exchange, name, created, creator
  1283. FROM chatRoom
  1284. WHERE lower(cookie) = lower(?)
  1285. `
  1286. var creator string
  1287. err := f.db.QueryRowContext(ctx, q, chatCookie).Scan(
  1288. &chatRoom.exchange,
  1289. &chatRoom.name,
  1290. &chatRoom.createTime,
  1291. &creator,
  1292. )
  1293. if errors.Is(err, sql.ErrNoRows) {
  1294. err = fmt.Errorf("%w: %s", ErrChatRoomNotFound, chatCookie)
  1295. }
  1296. chatRoom.creator = NewIdentScreenName(creator)
  1297. return chatRoom, err
  1298. }
  1299. func (f SQLiteUserStore) ChatRoomByName(ctx context.Context, exchange uint16, name string) (ChatRoom, error) {
  1300. chatRoom := ChatRoom{
  1301. exchange: exchange,
  1302. }
  1303. q := `
  1304. SELECT name, created, creator
  1305. FROM chatRoom
  1306. WHERE exchange = ? AND lower(name) = lower(?)
  1307. `
  1308. var creator string
  1309. err := f.db.QueryRowContext(ctx, q, exchange, name).Scan(
  1310. &chatRoom.name,
  1311. &chatRoom.createTime,
  1312. &creator,
  1313. )
  1314. if errors.Is(err, sql.ErrNoRows) {
  1315. err = ErrChatRoomNotFound
  1316. }
  1317. chatRoom.creator = NewIdentScreenName(creator)
  1318. return chatRoom, err
  1319. }
  1320. func (f SQLiteUserStore) CreateChatRoom(ctx context.Context, chatRoom *ChatRoom) error {
  1321. chatRoom.createTime = time.Now().UTC()
  1322. q := `
  1323. INSERT INTO chatRoom (cookie, exchange, name, created, creator)
  1324. VALUES (?, ?, ?, ?, ?)
  1325. `
  1326. _, err := f.db.ExecContext(ctx,
  1327. q,
  1328. chatRoom.Cookie(),
  1329. chatRoom.Exchange(),
  1330. chatRoom.Name(),
  1331. chatRoom.createTime,
  1332. chatRoom.Creator().String(),
  1333. )
  1334. if err != nil {
  1335. if strings.Contains(err.Error(), "constraint failed") {
  1336. err = ErrDupChatRoom
  1337. }
  1338. err = fmt.Errorf("CreateChatRoom: %w", err)
  1339. }
  1340. return err
  1341. }
  1342. func (f SQLiteUserStore) AllChatRooms(ctx context.Context, exchange uint16) ([]ChatRoom, error) {
  1343. q := `
  1344. SELECT created, creator, name
  1345. FROM chatRoom
  1346. WHERE exchange = ?
  1347. ORDER BY created ASC
  1348. `
  1349. rows, err := f.db.QueryContext(ctx, q, exchange)
  1350. if err != nil {
  1351. return nil, err
  1352. }
  1353. defer rows.Close()
  1354. var users []ChatRoom
  1355. for rows.Next() {
  1356. cr := ChatRoom{
  1357. exchange: exchange,
  1358. }
  1359. var creator string
  1360. if err := rows.Scan(&cr.createTime, &creator, &cr.name); err != nil {
  1361. return nil, err
  1362. }
  1363. cr.creator = NewIdentScreenName(creator)
  1364. users = append(users, cr)
  1365. }
  1366. if err := rows.Err(); err != nil {
  1367. return nil, err
  1368. }
  1369. return users, nil
  1370. }
  1371. func (f SQLiteUserStore) DeleteChatRooms(ctx context.Context, exchange uint16, names []string) error {
  1372. if len(names) == 0 {
  1373. return nil
  1374. }
  1375. // Build the query with placeholders for each name
  1376. placeholders := make([]string, len(names))
  1377. args := make([]interface{}, 0, len(names)+1)
  1378. args = append(args, exchange)
  1379. for i, name := range names {
  1380. placeholders[i] = "?"
  1381. args = append(args, name)
  1382. }
  1383. q := fmt.Sprintf(`
  1384. DELETE FROM chatRoom
  1385. WHERE exchange = ? AND name IN (%s)
  1386. `, strings.Join(placeholders, ","))
  1387. _, err := f.db.ExecContext(ctx, q, args...)
  1388. if err != nil {
  1389. return fmt.Errorf("DeleteChatRooms: %w", err)
  1390. }
  1391. return nil
  1392. }
  1393. func (f SQLiteUserStore) UpdateDisplayScreenName(ctx context.Context, displayScreenName DisplayScreenName) error {
  1394. q := `
  1395. UPDATE users
  1396. SET displayScreenName = ?
  1397. WHERE identScreenName = ?
  1398. `
  1399. _, err := f.db.ExecContext(ctx, q, displayScreenName.String(), displayScreenName.IdentScreenName().String())
  1400. return err
  1401. }
  1402. func (f SQLiteUserStore) UpdateEmailAddress(ctx context.Context, screenName IdentScreenName, emailAddress *mail.Address) error {
  1403. q := `
  1404. UPDATE users
  1405. SET emailAddress = ?
  1406. WHERE identScreenName = ?
  1407. `
  1408. _, err := f.db.ExecContext(ctx, q, emailAddress.Address, screenName.String())
  1409. return err
  1410. }
  1411. func (f SQLiteUserStore) EmailAddress(ctx context.Context, screenName IdentScreenName) (*mail.Address, error) {
  1412. q := `
  1413. SELECT emailAddress
  1414. FROM users
  1415. WHERE identScreenName = ?
  1416. `
  1417. var emailAddress string
  1418. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&emailAddress)
  1419. // username isn't found for some reason
  1420. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1421. return nil, err
  1422. }
  1423. e, err := mail.ParseAddress(emailAddress)
  1424. if err != nil {
  1425. return nil, fmt.Errorf("%w: %w", ErrNoEmailAddress, err)
  1426. }
  1427. return e, nil
  1428. }
  1429. func (f SQLiteUserStore) UpdateRegStatus(ctx context.Context, screenName IdentScreenName, regStatus uint16) error {
  1430. q := `
  1431. UPDATE users
  1432. SET regStatus = ?
  1433. WHERE identScreenName = ?
  1434. `
  1435. _, err := f.db.ExecContext(ctx, q, regStatus, screenName.String())
  1436. return err
  1437. }
  1438. func (f SQLiteUserStore) RegStatus(ctx context.Context, screenName IdentScreenName) (uint16, error) {
  1439. q := `
  1440. SELECT regStatus
  1441. FROM users
  1442. WHERE identScreenName = ?
  1443. `
  1444. var regStatus uint16
  1445. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&regStatus)
  1446. // username isn't found for some reason
  1447. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1448. return 0, err
  1449. }
  1450. return regStatus, nil
  1451. }
  1452. func (f SQLiteUserStore) UpdateConfirmStatus(ctx context.Context, screenName IdentScreenName, confirmStatus bool) error {
  1453. q := `
  1454. UPDATE users
  1455. SET confirmStatus = ?
  1456. WHERE identScreenName = ?
  1457. `
  1458. _, err := f.db.ExecContext(ctx, q, confirmStatus, screenName.String())
  1459. return err
  1460. }
  1461. func (f SQLiteUserStore) ConfirmStatus(ctx context.Context, screenName IdentScreenName) (bool, error) {
  1462. q := `
  1463. SELECT confirmStatus
  1464. FROM users
  1465. WHERE identScreenName = ?
  1466. `
  1467. var confirmStatus bool
  1468. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&confirmStatus)
  1469. // username isn't found for some reason
  1470. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1471. return false, err
  1472. }
  1473. return confirmStatus, nil
  1474. }
  1475. func (f SQLiteUserStore) UpdateSuspendedStatus(ctx context.Context, suspendedStatus uint16, screenName IdentScreenName) error {
  1476. q := `
  1477. UPDATE users
  1478. SET suspendedStatus = ?
  1479. WHERE identScreenName = ?
  1480. `
  1481. _, err := f.db.ExecContext(ctx, q, suspendedStatus, screenName.String())
  1482. return err
  1483. }
  1484. func (f SQLiteUserStore) SetBotStatus(ctx context.Context, isBot bool, screenName IdentScreenName) error {
  1485. q := `
  1486. UPDATE users
  1487. SET isBot = ?
  1488. WHERE identScreenName = ?
  1489. `
  1490. _, err := f.db.ExecContext(ctx, q, isBot, screenName.String())
  1491. return err
  1492. }
  1493. func (f SQLiteUserStore) SetWorkInfo(ctx context.Context, name IdentScreenName, data ICQWorkInfo) error {
  1494. q := `
  1495. UPDATE users SET
  1496. icq_workInfo_company = ?,
  1497. icq_workInfo_department = ?,
  1498. icq_workInfo_occupationCode = ?,
  1499. icq_workInfo_position = ?,
  1500. icq_workInfo_address = ?,
  1501. icq_workInfo_city = ?,
  1502. icq_workInfo_countryCode = ?,
  1503. icq_workInfo_fax = ?,
  1504. icq_workInfo_phone = ?,
  1505. icq_workInfo_state = ?,
  1506. icq_workInfo_webPage = ?,
  1507. icq_workInfo_zipCode = ?
  1508. WHERE identScreenName = ?
  1509. `
  1510. res, err := f.db.ExecContext(ctx,
  1511. q,
  1512. data.Company,
  1513. data.Department,
  1514. data.OccupationCode,
  1515. data.Position,
  1516. data.Address,
  1517. data.City,
  1518. data.CountryCode,
  1519. data.Fax,
  1520. data.Phone,
  1521. data.State,
  1522. data.WebPage,
  1523. data.ZIPCode,
  1524. name.String(),
  1525. )
  1526. if err != nil {
  1527. return fmt.Errorf("exec: %w", err)
  1528. }
  1529. c, err := res.RowsAffected()
  1530. if err != nil {
  1531. return fmt.Errorf("rows affected: %w", err)
  1532. }
  1533. if c == 0 {
  1534. return ErrNoUser
  1535. }
  1536. return nil
  1537. }
  1538. func (f SQLiteUserStore) SetPermissions(ctx context.Context, name IdentScreenName, data ICQPermissions) error {
  1539. q := `
  1540. UPDATE users SET
  1541. icq_permissions_authRequired = ?,
  1542. icq_permissions_webAware = ?,
  1543. icq_permissions_allowSpam = ?
  1544. WHERE identScreenName = ?
  1545. `
  1546. res, err := f.db.ExecContext(ctx,
  1547. q,
  1548. data.AuthRequired,
  1549. data.WebAware,
  1550. data.AllowSpam,
  1551. name.String(),
  1552. )
  1553. if err != nil {
  1554. return fmt.Errorf("exec: %w", err)
  1555. }
  1556. c, err := res.RowsAffected()
  1557. if err != nil {
  1558. return fmt.Errorf("rows affected: %w", err)
  1559. }
  1560. if c == 0 {
  1561. return ErrNoUser
  1562. }
  1563. return nil
  1564. }
  1565. // SetHomepageCategory updates the user's homepage category information.
  1566. // This is used by the V5 META_SET_HPCAT (0x0442) command.
  1567. // From iserverd db_users_sethpagecat_info() - updates user's homepage category.
  1568. func (f SQLiteUserStore) SetHomepageCategory(ctx context.Context, name IdentScreenName, data ICQHomepageCategory) error {
  1569. q := `
  1570. UPDATE users SET
  1571. icq_homepageCategory_enabled = ?,
  1572. icq_homepageCategory_index = ?,
  1573. icq_homepageCategory_description = ?
  1574. WHERE identScreenName = ?
  1575. `
  1576. res, err := f.db.ExecContext(ctx,
  1577. q,
  1578. data.Enabled,
  1579. data.Index,
  1580. data.Description,
  1581. name.String(),
  1582. )
  1583. if err != nil {
  1584. return fmt.Errorf("exec: %w", err)
  1585. }
  1586. c, err := res.RowsAffected()
  1587. if err != nil {
  1588. return fmt.Errorf("rows affected: %w", err)
  1589. }
  1590. if c == 0 {
  1591. return ErrNoUser
  1592. }
  1593. return nil
  1594. }
  1595. func (f SQLiteUserStore) SetMoreInfo(ctx context.Context, name IdentScreenName, data ICQMoreInfo) error {
  1596. q := `
  1597. UPDATE users SET
  1598. icq_moreInfo_birthDay = ?,
  1599. icq_moreInfo_birthMonth = ?,
  1600. icq_moreInfo_birthYear = ?,
  1601. icq_moreInfo_gender = ?,
  1602. icq_moreInfo_homePageAddr = ?,
  1603. icq_moreInfo_lang1 = ?,
  1604. icq_moreInfo_lang2 = ?,
  1605. icq_moreInfo_lang3 = ?
  1606. WHERE identScreenName = ?
  1607. `
  1608. res, err := f.db.ExecContext(ctx,
  1609. q,
  1610. data.BirthDay,
  1611. data.BirthMonth,
  1612. data.BirthYear,
  1613. data.Gender,
  1614. data.HomePageAddr,
  1615. data.Lang1,
  1616. data.Lang2,
  1617. data.Lang3,
  1618. name.String(),
  1619. )
  1620. if err != nil {
  1621. return fmt.Errorf("exec: %w", err)
  1622. }
  1623. c, err := res.RowsAffected()
  1624. if err != nil {
  1625. return fmt.Errorf("rows affected: %w", err)
  1626. }
  1627. if c == 0 {
  1628. return ErrNoUser
  1629. }
  1630. return nil
  1631. }
  1632. func (f SQLiteUserStore) SetUserNotes(ctx context.Context, name IdentScreenName, data ICQUserNotes) error {
  1633. q := `
  1634. UPDATE users
  1635. SET icq_notes = ?
  1636. WHERE identScreenName = ?
  1637. `
  1638. res, err := f.db.ExecContext(ctx,
  1639. q,
  1640. data.Notes,
  1641. name.String(),
  1642. )
  1643. if err != nil {
  1644. return fmt.Errorf("exec: %w", err)
  1645. }
  1646. c, err := res.RowsAffected()
  1647. if err != nil {
  1648. return fmt.Errorf("rows affected: %w", err)
  1649. }
  1650. if c == 0 {
  1651. return ErrNoUser
  1652. }
  1653. return nil
  1654. }
  1655. func (f SQLiteUserStore) SetInterests(ctx context.Context, name IdentScreenName, data ICQInterests) error {
  1656. q := `
  1657. UPDATE users SET
  1658. icq_interests_code1 = ?,
  1659. icq_interests_keyword1 = ?,
  1660. icq_interests_code2 = ?,
  1661. icq_interests_keyword2 = ?,
  1662. icq_interests_code3 = ?,
  1663. icq_interests_keyword3 = ?,
  1664. icq_interests_code4 = ?,
  1665. icq_interests_keyword4 = ?
  1666. WHERE identScreenName = ?
  1667. `
  1668. res, err := f.db.ExecContext(ctx,
  1669. q,
  1670. data.Code1,
  1671. data.Keyword1,
  1672. data.Code2,
  1673. data.Keyword2,
  1674. data.Code3,
  1675. data.Keyword3,
  1676. data.Code4,
  1677. data.Keyword4,
  1678. name.String(),
  1679. )
  1680. if err != nil {
  1681. return fmt.Errorf("exec: %w", err)
  1682. }
  1683. c, err := res.RowsAffected()
  1684. if err != nil {
  1685. return fmt.Errorf("rows affected: %w", err)
  1686. }
  1687. if c == 0 {
  1688. return ErrNoUser
  1689. }
  1690. return nil
  1691. }
  1692. func (f SQLiteUserStore) SetAffiliations(ctx context.Context, name IdentScreenName, data ICQAffiliations) error {
  1693. q := `
  1694. UPDATE users SET
  1695. icq_affiliations_currentCode1 = ?,
  1696. icq_affiliations_currentKeyword1 = ?,
  1697. icq_affiliations_currentCode2 = ?,
  1698. icq_affiliations_currentKeyword2 = ?,
  1699. icq_affiliations_currentCode3 = ?,
  1700. icq_affiliations_currentKeyword3 = ?,
  1701. icq_affiliations_pastCode1 = ?,
  1702. icq_affiliations_pastKeyword1 = ?,
  1703. icq_affiliations_pastCode2 = ?,
  1704. icq_affiliations_pastKeyword2 = ?,
  1705. icq_affiliations_pastCode3 = ?,
  1706. icq_affiliations_pastKeyword3 = ?
  1707. WHERE identScreenName = ?
  1708. `
  1709. res, err := f.db.ExecContext(ctx,
  1710. q,
  1711. data.CurrentCode1,
  1712. data.CurrentKeyword1,
  1713. data.CurrentCode2,
  1714. data.CurrentKeyword2,
  1715. data.CurrentCode3,
  1716. data.CurrentKeyword3,
  1717. data.PastCode1,
  1718. data.PastKeyword1,
  1719. data.PastCode2,
  1720. data.PastKeyword2,
  1721. data.PastCode3,
  1722. data.PastKeyword3,
  1723. name.String(),
  1724. )
  1725. if err != nil {
  1726. return fmt.Errorf("exec: %w", err)
  1727. }
  1728. c, err := res.RowsAffected()
  1729. if err != nil {
  1730. return fmt.Errorf("rows affected: %w", err)
  1731. }
  1732. if c == 0 {
  1733. return ErrNoUser
  1734. }
  1735. return nil
  1736. }
  1737. func (f SQLiteUserStore) SetBasicInfo(ctx context.Context, name IdentScreenName, data ICQBasicInfo) error {
  1738. q := `
  1739. UPDATE users SET
  1740. icq_basicInfo_cellPhone = ?,
  1741. icq_basicInfo_countryCode = ?,
  1742. icq_basicInfo_emailAddress = ?,
  1743. icq_basicInfo_firstName = ?,
  1744. icq_basicInfo_gmtOffset = ?,
  1745. icq_basicInfo_address = ?,
  1746. icq_basicInfo_city = ?,
  1747. icq_basicInfo_fax = ?,
  1748. icq_basicInfo_phone = ?,
  1749. icq_basicInfo_state = ?,
  1750. icq_basicInfo_lastName = ?,
  1751. icq_basicInfo_nickName = ?,
  1752. icq_basicInfo_publishEmail = ?,
  1753. icq_basicInfo_zipCode = ?
  1754. WHERE identScreenName = ?
  1755. `
  1756. res, err := f.db.ExecContext(ctx,
  1757. q,
  1758. data.CellPhone,
  1759. data.CountryCode,
  1760. data.EmailAddress,
  1761. data.FirstName,
  1762. data.GMTOffset,
  1763. data.Address,
  1764. data.City,
  1765. data.Fax,
  1766. data.Phone,
  1767. data.State,
  1768. data.LastName,
  1769. data.Nickname,
  1770. data.PublishEmail,
  1771. data.ZIPCode,
  1772. name.String(),
  1773. )
  1774. if err != nil {
  1775. return fmt.Errorf("exec: %w", err)
  1776. }
  1777. c, err := res.RowsAffected()
  1778. if err != nil {
  1779. return fmt.Errorf("rows affected: %w", err)
  1780. }
  1781. if c == 0 {
  1782. return ErrNoUser
  1783. }
  1784. return nil
  1785. }
  1786. // SetICQInfo updates all ICQ profile columns on the user row in one UPDATE.
  1787. // Keep this column list aligned with SetMoreInfo, SetWorkInfo, SetPermissions,
  1788. // SetUserNotes, SetInterests, SetAffiliations, and SetHomepageCategory. Basic-info
  1789. // columns that are not on SetBasicInfo (e.g. originally-from) live only here.
  1790. func (f SQLiteUserStore) SetICQInfo(ctx context.Context, name IdentScreenName, info ICQInfo) error {
  1791. q := `
  1792. UPDATE users SET
  1793. icq_basicInfo_cellPhone = ?,
  1794. icq_basicInfo_countryCode = ?,
  1795. icq_basicInfo_emailAddress = ?,
  1796. icq_basicInfo_firstName = ?,
  1797. icq_basicInfo_gmtOffset = ?,
  1798. icq_basicInfo_address = ?,
  1799. icq_basicInfo_city = ?,
  1800. icq_basicInfo_fax = ?,
  1801. icq_basicInfo_phone = ?,
  1802. icq_basicInfo_state = ?,
  1803. icq_basicInfo_lastName = ?,
  1804. icq_basicInfo_nickName = ?,
  1805. icq_basicInfo_publishEmail = ?,
  1806. icq_basicInfo_zipCode = ?,
  1807. icq_basicInfo_originCity = ?,
  1808. icq_basicInfo_originState = ?,
  1809. icq_basicInfo_originCountryCode = ?,
  1810. icq_moreInfo_birthDay = ?,
  1811. icq_moreInfo_birthMonth = ?,
  1812. icq_moreInfo_birthYear = ?,
  1813. icq_moreInfo_gender = ?,
  1814. icq_moreInfo_homePageAddr = ?,
  1815. icq_moreInfo_lang1 = ?,
  1816. icq_moreInfo_lang2 = ?,
  1817. icq_moreInfo_lang3 = ?,
  1818. icq_workInfo_company = ?,
  1819. icq_workInfo_department = ?,
  1820. icq_workInfo_occupationCode = ?,
  1821. icq_workInfo_position = ?,
  1822. icq_workInfo_address = ?,
  1823. icq_workInfo_city = ?,
  1824. icq_workInfo_countryCode = ?,
  1825. icq_workInfo_fax = ?,
  1826. icq_workInfo_phone = ?,
  1827. icq_workInfo_state = ?,
  1828. icq_workInfo_webPage = ?,
  1829. icq_workInfo_zipCode = ?,
  1830. icq_permissions_authRequired = ?,
  1831. icq_permissions_webAware = ?,
  1832. icq_permissions_allowSpam = ?,
  1833. icq_notes = ?,
  1834. icq_interests_code1 = ?,
  1835. icq_interests_keyword1 = ?,
  1836. icq_interests_code2 = ?,
  1837. icq_interests_keyword2 = ?,
  1838. icq_interests_code3 = ?,
  1839. icq_interests_keyword3 = ?,
  1840. icq_interests_code4 = ?,
  1841. icq_interests_keyword4 = ?,
  1842. icq_affiliations_currentCode1 = ?,
  1843. icq_affiliations_currentKeyword1 = ?,
  1844. icq_affiliations_currentCode2 = ?,
  1845. icq_affiliations_currentKeyword2 = ?,
  1846. icq_affiliations_currentCode3 = ?,
  1847. icq_affiliations_currentKeyword3 = ?,
  1848. icq_affiliations_pastCode1 = ?,
  1849. icq_affiliations_pastKeyword1 = ?,
  1850. icq_affiliations_pastCode2 = ?,
  1851. icq_affiliations_pastKeyword2 = ?,
  1852. icq_affiliations_pastCode3 = ?,
  1853. icq_affiliations_pastKeyword3 = ?,
  1854. icq_homepageCategory_enabled = ?,
  1855. icq_homepageCategory_index = ?,
  1856. icq_homepageCategory_description = ?
  1857. WHERE identScreenName = ?
  1858. `
  1859. res, err := f.db.ExecContext(ctx, q,
  1860. info.Basic.CellPhone,
  1861. info.Basic.CountryCode,
  1862. info.Basic.EmailAddress,
  1863. info.Basic.FirstName,
  1864. info.Basic.GMTOffset,
  1865. info.Basic.Address,
  1866. info.Basic.City,
  1867. info.Basic.Fax,
  1868. info.Basic.Phone,
  1869. info.Basic.State,
  1870. info.Basic.LastName,
  1871. info.Basic.Nickname,
  1872. info.Basic.PublishEmail,
  1873. info.Basic.ZIPCode,
  1874. info.Basic.OriginallyFromCity,
  1875. info.Basic.OriginallyFromState,
  1876. info.Basic.OriginallyFromCountryCode,
  1877. info.More.BirthDay,
  1878. info.More.BirthMonth,
  1879. info.More.BirthYear,
  1880. info.More.Gender,
  1881. info.More.HomePageAddr,
  1882. info.More.Lang1,
  1883. info.More.Lang2,
  1884. info.More.Lang3,
  1885. info.Work.Company,
  1886. info.Work.Department,
  1887. info.Work.OccupationCode,
  1888. info.Work.Position,
  1889. info.Work.Address,
  1890. info.Work.City,
  1891. info.Work.CountryCode,
  1892. info.Work.Fax,
  1893. info.Work.Phone,
  1894. info.Work.State,
  1895. info.Work.WebPage,
  1896. info.Work.ZIPCode,
  1897. info.Permissions.AuthRequired,
  1898. info.Permissions.WebAware,
  1899. info.Permissions.AllowSpam,
  1900. info.Notes.Notes,
  1901. info.Interests.Code1,
  1902. info.Interests.Keyword1,
  1903. info.Interests.Code2,
  1904. info.Interests.Keyword2,
  1905. info.Interests.Code3,
  1906. info.Interests.Keyword3,
  1907. info.Interests.Code4,
  1908. info.Interests.Keyword4,
  1909. info.Affiliations.CurrentCode1,
  1910. info.Affiliations.CurrentKeyword1,
  1911. info.Affiliations.CurrentCode2,
  1912. info.Affiliations.CurrentKeyword2,
  1913. info.Affiliations.CurrentCode3,
  1914. info.Affiliations.CurrentKeyword3,
  1915. info.Affiliations.PastCode1,
  1916. info.Affiliations.PastKeyword1,
  1917. info.Affiliations.PastCode2,
  1918. info.Affiliations.PastKeyword2,
  1919. info.Affiliations.PastCode3,
  1920. info.Affiliations.PastKeyword3,
  1921. info.HomepageCategory.Enabled,
  1922. info.HomepageCategory.Index,
  1923. info.HomepageCategory.Description,
  1924. name.String(),
  1925. )
  1926. if err != nil {
  1927. return fmt.Errorf("exec: %w", err)
  1928. }
  1929. c, err := res.RowsAffected()
  1930. if err != nil {
  1931. return fmt.Errorf("rows affected: %w", err)
  1932. }
  1933. if c == 0 {
  1934. return ErrNoUser
  1935. }
  1936. return nil
  1937. }
  1938. func (f SQLiteUserStore) SaveMessage(ctx context.Context, offlineMessage OfflineMessage) (newCount int, err error) {
  1939. buf := &bytes.Buffer{}
  1940. if err := wire.MarshalBE(offlineMessage.Message, buf); err != nil {
  1941. return 0, fmt.Errorf("marshal: %w", err)
  1942. }
  1943. var tx *sql.Tx
  1944. tx, err = f.db.BeginTx(ctx, nil)
  1945. if err != nil {
  1946. return 0, fmt.Errorf("begin tx: %w", err)
  1947. }
  1948. defer func() {
  1949. if err != nil {
  1950. _ = tx.Rollback()
  1951. }
  1952. }()
  1953. const countQuery = `
  1954. SELECT COUNT(1)
  1955. FROM offlineMessage
  1956. WHERE sender = ? AND recipient = ?
  1957. `
  1958. var currentCount int
  1959. if err = tx.QueryRowContext(
  1960. ctx,
  1961. countQuery,
  1962. offlineMessage.Sender.String(),
  1963. offlineMessage.Recipient.String(),
  1964. ).Scan(&currentCount); err != nil {
  1965. return 0, fmt.Errorf("count: %w", err)
  1966. }
  1967. if currentCount >= offlineInboxLimit {
  1968. err = ErrOfflineInboxFull
  1969. return 0, err
  1970. }
  1971. q := `
  1972. INSERT INTO offlineMessage (sender, recipient, message, sent)
  1973. VALUES (?, ?, ?, ?)
  1974. `
  1975. if _, err = tx.ExecContext(ctx,
  1976. q,
  1977. offlineMessage.Sender.String(),
  1978. offlineMessage.Recipient.String(),
  1979. buf.Bytes(),
  1980. offlineMessage.Sent,
  1981. ); err != nil {
  1982. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1983. err = ErrNoUser
  1984. } else {
  1985. err = fmt.Errorf("insert: %w", err)
  1986. }
  1987. return 0, err
  1988. }
  1989. newCount = currentCount + 1
  1990. updateQuery := `
  1991. UPDATE users
  1992. SET offlineMsgCount = ?
  1993. WHERE identScreenName = ?
  1994. `
  1995. _, err = tx.ExecContext(ctx,
  1996. updateQuery,
  1997. newCount,
  1998. offlineMessage.Recipient.String(),
  1999. )
  2000. if err != nil {
  2001. return 0, fmt.Errorf("update offlineMsgCount: %w", err)
  2002. }
  2003. if err = tx.Commit(); err != nil {
  2004. return 0, fmt.Errorf("commit: %w", err)
  2005. }
  2006. return newCount, nil
  2007. }
  2008. func (f SQLiteUserStore) RetrieveMessages(ctx context.Context, recip IdentScreenName) ([]OfflineMessage, error) {
  2009. q := `
  2010. SELECT
  2011. sender,
  2012. message,
  2013. sent
  2014. FROM offlineMessage
  2015. WHERE recipient = ?
  2016. `
  2017. rows, err := f.db.QueryContext(ctx, q, recip.String())
  2018. if err != nil {
  2019. return nil, err
  2020. }
  2021. defer rows.Close()
  2022. var messages []OfflineMessage
  2023. for rows.Next() {
  2024. var sender string
  2025. var buf []byte
  2026. var sent time.Time
  2027. if err := rows.Scan(&sender, &buf, &sent); err != nil {
  2028. return nil, err
  2029. }
  2030. var msg wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
  2031. if err := wire.UnmarshalBE(&msg, bytes.NewBuffer(buf)); err != nil {
  2032. return nil, fmt.Errorf("unmarshal: %w", err)
  2033. }
  2034. messages = append(messages, OfflineMessage{
  2035. Sender: NewIdentScreenName(sender),
  2036. Recipient: recip,
  2037. Message: msg,
  2038. Sent: sent,
  2039. })
  2040. }
  2041. if err := rows.Err(); err != nil {
  2042. return nil, err
  2043. }
  2044. return messages, nil
  2045. }
  2046. func (f SQLiteUserStore) DeleteMessages(ctx context.Context, recip IdentScreenName) error {
  2047. q := `
  2048. DELETE FROM offlineMessage WHERE recipient = ?
  2049. `
  2050. _, err := f.db.ExecContext(ctx, q, recip.String())
  2051. return err
  2052. }
  2053. func (f SQLiteUserStore) BuddyIconMetadata(ctx context.Context, screenName IdentScreenName) (*wire.BARTID, error) {
  2054. q := `
  2055. SELECT
  2056. groupID,
  2057. itemID,
  2058. classID,
  2059. name,
  2060. attributes
  2061. FROM feedBag
  2062. WHERE screenname = ? AND name = ? AND classID = ?
  2063. `
  2064. var item wire.FeedbagItem
  2065. var attrs []byte
  2066. err := f.db.QueryRowContext(ctx, q, screenName.String(), wire.BARTTypesBuddyIcon, wire.FeedbagClassIdBart).Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs)
  2067. if errors.Is(err, sql.ErrNoRows) {
  2068. return nil, nil
  2069. }
  2070. if err != nil {
  2071. return nil, err
  2072. }
  2073. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  2074. return nil, err
  2075. }
  2076. b, hasBuf := item.Bytes(wire.FeedbagAttributesBartInfo)
  2077. if !hasBuf {
  2078. return nil, errors.New("unable to extract icon payload")
  2079. }
  2080. bartInfo := wire.BARTInfo{}
  2081. if err := wire.UnmarshalBE(&bartInfo, bytes.NewBuffer(b)); err != nil {
  2082. return nil, err
  2083. }
  2084. return &wire.BARTID{
  2085. Type: wire.BARTTypesBuddyIcon,
  2086. BARTInfo: wire.BARTInfo{
  2087. Flags: bartInfo.Flags,
  2088. Hash: bartInfo.Hash,
  2089. },
  2090. }, nil
  2091. }
  2092. func (f SQLiteUserStore) SetKeywords(ctx context.Context, screenName IdentScreenName, keywords [5]string) error {
  2093. q := `
  2094. WITH interests AS (SELECT CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword1,
  2095. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword2,
  2096. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword3,
  2097. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword4,
  2098. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword5
  2099. FROM aimKeyword
  2100. WHERE name IN (?, ?, ?, ?, ?))
  2101. UPDATE users
  2102. SET aim_keyword1 = (SELECT aim_keyword1 FROM interests WHERE aim_keyword1 IS NOT NULL),
  2103. aim_keyword2 = (SELECT aim_keyword2 FROM interests WHERE aim_keyword2 IS NOT NULL),
  2104. aim_keyword3 = (SELECT aim_keyword3 FROM interests WHERE aim_keyword3 IS NOT NULL),
  2105. aim_keyword4 = (SELECT aim_keyword4 FROM interests WHERE aim_keyword4 IS NOT NULL),
  2106. aim_keyword5 = (SELECT aim_keyword5 FROM interests WHERE aim_keyword5 IS NOT NULL)
  2107. WHERE identScreenName = ?
  2108. `
  2109. _, err := f.db.ExecContext(ctx, q,
  2110. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  2111. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  2112. screenName.String())
  2113. return err
  2114. }
  2115. func (f SQLiteUserStore) Categories(ctx context.Context) ([]Category, error) {
  2116. q := `SELECT id, name FROM aimKeywordCategory ORDER BY name`
  2117. rows, err := f.db.QueryContext(ctx, q)
  2118. if err != nil {
  2119. return nil, err
  2120. }
  2121. defer rows.Close()
  2122. var categories []Category
  2123. for rows.Next() {
  2124. category := Category{}
  2125. if err := rows.Scan(&category.ID, &category.Name); err != nil {
  2126. return nil, err
  2127. }
  2128. categories = append(categories, category)
  2129. }
  2130. if err := rows.Err(); err != nil {
  2131. return nil, err
  2132. }
  2133. return categories, nil
  2134. }
  2135. func (f SQLiteUserStore) CreateCategory(ctx context.Context, name string) (Category, error) {
  2136. tx, err := f.db.Begin()
  2137. if err != nil {
  2138. return Category{}, err
  2139. }
  2140. defer tx.Rollback()
  2141. q := `INSERT INTO aimKeywordCategory (name) VALUES (?)`
  2142. res, err := tx.ExecContext(ctx, q, name)
  2143. if err != nil {
  2144. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  2145. err = ErrKeywordCategoryExists
  2146. }
  2147. return Category{}, err
  2148. }
  2149. id, err := res.LastInsertId()
  2150. if err != nil {
  2151. return Category{}, err
  2152. }
  2153. if id > math.MaxUint8 {
  2154. return Category{}, errTooManyCategories
  2155. }
  2156. if err := tx.Commit(); err != nil {
  2157. return Category{}, err
  2158. }
  2159. return Category{
  2160. ID: uint8(id),
  2161. Name: name,
  2162. }, nil
  2163. }
  2164. func (f SQLiteUserStore) DeleteCategory(ctx context.Context, categoryID uint8) error {
  2165. q := `DELETE FROM aimKeywordCategory WHERE id = ?`
  2166. res, err := f.db.ExecContext(ctx, q, categoryID)
  2167. if err != nil {
  2168. // Check if the error is a foreign key constraint violation
  2169. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2170. return ErrKeywordInUse
  2171. }
  2172. }
  2173. c, err := res.RowsAffected()
  2174. if err != nil {
  2175. return err
  2176. }
  2177. if c == 0 {
  2178. return ErrKeywordCategoryNotFound
  2179. }
  2180. return nil
  2181. }
  2182. func (f SQLiteUserStore) KeywordsByCategory(ctx context.Context, categoryID uint8) ([]Keyword, error) {
  2183. q := `SELECT id, name FROM aimKeyword WHERE parent = ? ORDER BY name`
  2184. if categoryID == 0 {
  2185. q = `SELECT id, name FROM aimKeyword WHERE parent IS NULL ORDER BY name`
  2186. }
  2187. rows, err := f.db.QueryContext(ctx, q, categoryID)
  2188. if err != nil {
  2189. return nil, err
  2190. }
  2191. defer rows.Close()
  2192. var keywords []Keyword
  2193. for rows.Next() {
  2194. keyword := Keyword{}
  2195. if err := rows.Scan(&keyword.ID, &keyword.Name); err != nil {
  2196. return nil, err
  2197. }
  2198. keywords = append(keywords, keyword)
  2199. }
  2200. if err := rows.Err(); err != nil {
  2201. return nil, err
  2202. }
  2203. if len(keywords) == 0 {
  2204. var exists int
  2205. err = f.db.QueryRow("SELECT COUNT(*) FROM aimKeywordCategory WHERE id = ?", categoryID).Scan(&exists)
  2206. if err != nil {
  2207. return nil, err
  2208. }
  2209. if exists == 0 {
  2210. return nil, ErrKeywordCategoryNotFound
  2211. }
  2212. }
  2213. return keywords, nil
  2214. }
  2215. func (f SQLiteUserStore) CreateKeyword(ctx context.Context, name string, categoryID uint8) (Keyword, error) {
  2216. tx, err := f.db.Begin()
  2217. if err != nil {
  2218. return Keyword{}, err
  2219. }
  2220. defer tx.Rollback()
  2221. q := `INSERT INTO aimKeyword (name, parent) VALUES (?, ?)`
  2222. var parent interface{} = nil
  2223. if categoryID != 0 {
  2224. parent = categoryID
  2225. }
  2226. res, err := tx.ExecContext(ctx, q, name, parent)
  2227. if err != nil {
  2228. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  2229. err = ErrKeywordExists
  2230. } else if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2231. err = ErrKeywordCategoryNotFound
  2232. }
  2233. return Keyword{}, err
  2234. }
  2235. id, err := res.LastInsertId()
  2236. if err != nil {
  2237. return Keyword{}, err
  2238. }
  2239. if id > math.MaxUint8 {
  2240. return Keyword{}, errTooManyKeywords
  2241. }
  2242. if err := tx.Commit(); err != nil {
  2243. return Keyword{}, err
  2244. }
  2245. return Keyword{
  2246. ID: uint8(id),
  2247. Name: name,
  2248. }, nil
  2249. }
  2250. func (f SQLiteUserStore) DeleteKeyword(ctx context.Context, id uint8) error {
  2251. q := `DELETE FROM aimKeyword WHERE id = ?`
  2252. res, err := f.db.ExecContext(ctx, q, id)
  2253. if err != nil {
  2254. // Check if the error is a foreign key constraint violation
  2255. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  2256. return ErrKeywordInUse
  2257. }
  2258. }
  2259. c, err := res.RowsAffected()
  2260. if err != nil {
  2261. return err
  2262. }
  2263. if c == 0 {
  2264. return ErrKeywordNotFound
  2265. }
  2266. return nil
  2267. }
  2268. // InterestList returns a list of keywords grouped by category used to render
  2269. // the AIM directory interests list. The list is made up of 3 types of elements:
  2270. //
  2271. // Categories
  2272. //
  2273. // ID: The category ID
  2274. // Cookie: The category name
  2275. // Type: [wire.ODirKeywordCategory]
  2276. //
  2277. // Keywords
  2278. //
  2279. // ID: The parent category ID
  2280. // Cookie: The keyword name
  2281. // Type: [wire.ODirKeyword]
  2282. //
  2283. // Top-level Keywords
  2284. //
  2285. // ID: 0 (does not have a parent category)
  2286. // Cookie: The keyword name
  2287. // Type: [wire.ODirKeyword]
  2288. //
  2289. // Keywords are grouped contiguously by category and preceded by the category
  2290. // name. Top-level keywords appear by themselves. Categories and top-level
  2291. // keywords are sorted alphabetically. Keyword groups are sorted alphabetically.
  2292. //
  2293. // Conceptually, the list looks like this:
  2294. //
  2295. // > Animals (top-level keyword, id=0)
  2296. // > Artificial Intelligence (keyword, id=3)
  2297. // > Cybersecurity (keyword, id=3)
  2298. // > Music (category, id=1)
  2299. // > Jazz (keyword, id=1)
  2300. // > Rock (keyword, id=1)
  2301. // > Sports (category, id=2)
  2302. // > Basketball (keyword, id=2)
  2303. // > Soccer (keyword, id=2)
  2304. // > Tennis (keyword, id=2)
  2305. // > Technology (category, id=3)
  2306. // > Zoology (top-level keyword, id=0)
  2307. func (f SQLiteUserStore) InterestList(ctx context.Context) ([]wire.ODirKeywordListItem, error) {
  2308. q := `
  2309. WITH categories AS (
  2310. SELECT
  2311. name AS grouping,
  2312. id,
  2313. 0 AS sortPrio,
  2314. name
  2315. FROM aimKeywordCategory
  2316. UNION
  2317. SELECT
  2318. IFNULL(akc.name, ak.name) AS grouping,
  2319. IFNULL(ak.parent, 0) AS id,
  2320. CASE WHEN ak.parent IS NULL THEN 1 ELSE 2 END AS sortPrio,
  2321. ak.name
  2322. FROM aimKeyword ak
  2323. LEFT JOIN aimKeywordCategory akc ON akc.id = ak.parent
  2324. ORDER BY 1, 3, 4
  2325. )
  2326. SELECT
  2327. id,
  2328. sortPrio,
  2329. name
  2330. FROM categories
  2331. `
  2332. rows, err := f.db.QueryContext(ctx, q)
  2333. if err != nil {
  2334. return nil, err
  2335. }
  2336. defer rows.Close()
  2337. var list []wire.ODirKeywordListItem
  2338. for rows.Next() {
  2339. msg := wire.ODirKeywordListItem{}
  2340. var sortPrio int
  2341. if err := rows.Scan(&msg.ID, &sortPrio, &msg.Name); err != nil {
  2342. return nil, err
  2343. }
  2344. switch sortPrio {
  2345. case 0:
  2346. msg.Type = wire.ODirKeywordCategory
  2347. case 1, 2:
  2348. msg.Type = wire.ODirKeyword
  2349. }
  2350. list = append(list, msg)
  2351. }
  2352. if err := rows.Err(); err != nil {
  2353. return nil, err
  2354. }
  2355. return list, nil
  2356. }
  2357. func (f SQLiteUserStore) SetTOCConfig(ctx context.Context, user IdentScreenName, config string) error {
  2358. q := `
  2359. UPDATE users
  2360. SET tocConfig = ?
  2361. WHERE identScreenName = ?
  2362. `
  2363. res, err := f.db.ExecContext(ctx,
  2364. q,
  2365. config,
  2366. user.String(),
  2367. )
  2368. if err != nil {
  2369. return fmt.Errorf("exec: %w", err)
  2370. }
  2371. c, err := res.RowsAffected()
  2372. if err != nil {
  2373. return fmt.Errorf("rows affected: %w", err)
  2374. }
  2375. if c == 0 {
  2376. return ErrNoUser
  2377. }
  2378. return nil
  2379. }
  2380. // SetWarnLevel updates the last warn update time and warning level for a user.
  2381. func (f SQLiteUserStore) SetWarnLevel(ctx context.Context, user IdentScreenName, lastWarnUpdate time.Time, lastWarnLevel uint16) error {
  2382. q := `
  2383. UPDATE users
  2384. SET lastWarnUpdate = ?, lastWarnLevel = ?
  2385. WHERE identScreenName = ?
  2386. `
  2387. res, err := f.db.ExecContext(ctx,
  2388. q,
  2389. lastWarnUpdate.Unix(),
  2390. lastWarnLevel,
  2391. user.String(),
  2392. )
  2393. if err != nil {
  2394. return fmt.Errorf("exec: %w", err)
  2395. }
  2396. c, err := res.RowsAffected()
  2397. if err != nil {
  2398. return fmt.Errorf("rows affected: %w", err)
  2399. }
  2400. if c == 0 {
  2401. return ErrNoUser
  2402. }
  2403. return nil
  2404. }
  2405. // SetOfflineMsgCount updates the offline message count for a user.
  2406. func (f SQLiteUserStore) SetOfflineMsgCount(ctx context.Context, screenName IdentScreenName, count int) error {
  2407. q := `
  2408. UPDATE users
  2409. SET offlineMsgCount = ?
  2410. WHERE identScreenName = ?
  2411. `
  2412. res, err := f.db.ExecContext(ctx,
  2413. q,
  2414. count,
  2415. screenName.String(),
  2416. )
  2417. if err != nil {
  2418. return fmt.Errorf("exec: %w", err)
  2419. }
  2420. c, err := res.RowsAffected()
  2421. if err != nil {
  2422. return fmt.Errorf("rows affected: %w", err)
  2423. }
  2424. if c == 0 {
  2425. return ErrNoUser
  2426. }
  2427. return nil
  2428. }