user_store.go 70 KB

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