user_store.go 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164
  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. )
  37. //go:embed migrations/*
  38. var migrations embed.FS
  39. // SQLiteUserStore stores user feedbag (buddy list), profile, and
  40. // authentication credentials information in a SQLite database.
  41. type SQLiteUserStore struct {
  42. db *sql.DB
  43. }
  44. // NewSQLiteUserStore creates a new instance of SQLiteUserStore. If the
  45. // database does not already exist, a new one is created with the required
  46. // schema.
  47. func NewSQLiteUserStore(dbFilePath string) (*SQLiteUserStore, error) {
  48. db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_pragma=foreign_keys=on", dbFilePath))
  49. if err != nil {
  50. return nil, err
  51. }
  52. // Set the maximum number of open connections to 1.
  53. // This is crucial to prevent SQLITE_BUSY errors, which occur when the database
  54. // is locked due to concurrent access. By limiting the number of open connections
  55. // to 1, we ensure that all database operations are serialized, thus avoiding
  56. // any potential locking issues.
  57. db.SetMaxOpenConns(1)
  58. store := &SQLiteUserStore{db: db}
  59. if err := store.runMigrations(); err != nil {
  60. return nil, fmt.Errorf("failed to run migrations: %w", err)
  61. }
  62. return store, nil
  63. }
  64. func (f SQLiteUserStore) runMigrations() error {
  65. migrationFS, err := fs.Sub(migrations, "migrations")
  66. if err != nil {
  67. return fmt.Errorf("failed to prepare migration subdirectory: %v", err)
  68. }
  69. sourceInstance, err := httpfs.New(http.FS(migrationFS), ".")
  70. if err != nil {
  71. return fmt.Errorf("failed to create source instance from embedded filesystem: %v", err)
  72. }
  73. driver, err := migratesqlite.WithInstance(f.db, &migratesqlite.Config{})
  74. if err != nil {
  75. return fmt.Errorf("cannot create database driver: %v", err)
  76. }
  77. m, err := migrate.NewWithInstance("httpfs", sourceInstance, "sqlite", driver)
  78. if err != nil {
  79. return fmt.Errorf("failed to create migrate instance: %v", err)
  80. }
  81. if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
  82. return fmt.Errorf("failed to run migrations: %v", err)
  83. }
  84. return nil
  85. }
  86. func (f SQLiteUserStore) AllUsers(ctx context.Context) ([]User, error) {
  87. q := `SELECT identScreenName, displayScreenName, isICQ, isBot FROM users`
  88. rows, err := f.db.QueryContext(ctx, q)
  89. if err != nil {
  90. return nil, err
  91. }
  92. defer rows.Close()
  93. var users []User
  94. for rows.Next() {
  95. var identSN, displaySN string
  96. var isICQ, isBot bool
  97. if err := rows.Scan(&identSN, &displaySN, &isICQ, &isBot); err != nil {
  98. return nil, err
  99. }
  100. users = append(users, User{
  101. IdentScreenName: NewIdentScreenName(identSN),
  102. DisplayScreenName: DisplayScreenName(displaySN),
  103. IsICQ: isICQ,
  104. IsBot: isBot,
  105. })
  106. }
  107. if err := rows.Err(); err != nil {
  108. return nil, err
  109. }
  110. return users, nil
  111. }
  112. func (f SQLiteUserStore) FindByUIN(ctx context.Context, UIN uint32) (User, error) {
  113. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{strconv.Itoa(int(UIN))})
  114. if err != nil {
  115. return User{}, fmt.Errorf("FindByUIN: %w", err)
  116. }
  117. if len(users) == 0 {
  118. return User{}, ErrNoUser
  119. }
  120. return users[0], nil
  121. }
  122. func (f SQLiteUserStore) FindByICQEmail(ctx context.Context, email string) (User, error) {
  123. users, err := f.queryUsers(ctx, `icq_basicInfo_emailAddress = ?`, []any{email})
  124. if err != nil {
  125. return User{}, fmt.Errorf("FindByICQEmail: %w", err)
  126. }
  127. if len(users) == 0 {
  128. return User{}, ErrNoUser
  129. }
  130. return users[0], nil
  131. }
  132. func (f SQLiteUserStore) FindByAIMEmail(ctx context.Context, email string) (User, error) {
  133. users, err := f.queryUsers(ctx, `emailAddress = ?`, []any{email})
  134. if err != nil {
  135. return User{}, fmt.Errorf("FindByAIMEmail: %w", err)
  136. }
  137. if len(users) == 0 {
  138. return User{}, ErrNoUser
  139. }
  140. return users[0], nil
  141. }
  142. func (f SQLiteUserStore) FindByAIMKeyword(ctx context.Context, keyword string) ([]User, error) {
  143. where := `
  144. (SELECT id FROM aimKeyword WHERE name = ?) IN
  145. (aim_keyword1, aim_keyword2, aim_keyword3, aim_keyword4, aim_keyword5)
  146. `
  147. users, err := f.queryUsers(ctx, where, []any{keyword})
  148. if err != nil {
  149. return nil, err
  150. }
  151. return users, nil
  152. }
  153. func (f SQLiteUserStore) FindByICQName(ctx context.Context, firstName, lastName, nickName string) ([]User, error) {
  154. var args []any
  155. var clauses []string
  156. if firstName != "" {
  157. args = append(args, firstName)
  158. clauses = append(clauses, `LOWER(icq_basicInfo_firstName) = LOWER(?)`)
  159. }
  160. if lastName != "" {
  161. args = append(args, lastName)
  162. clauses = append(clauses, `LOWER(icq_basicInfo_lastName) = LOWER(?)`)
  163. }
  164. if nickName != "" {
  165. args = append(args, nickName)
  166. clauses = append(clauses, `LOWER(icq_basicInfo_nickName) = LOWER(?)`)
  167. }
  168. whereClause := strings.Join(clauses, " AND ")
  169. users, err := f.queryUsers(ctx, whereClause, args)
  170. if err != nil {
  171. err = fmt.Errorf("FindByICQName: %w", err)
  172. }
  173. return users, nil
  174. }
  175. func (f SQLiteUserStore) FindByAIMNameAndAddr(ctx context.Context, info AIMNameAndAddr) ([]User, error) {
  176. var args []any
  177. var clauses []string
  178. if info.FirstName != "" {
  179. args = append(args, info.FirstName)
  180. clauses = append(clauses, `LOWER(aim_firstName) = LOWER(?)`)
  181. }
  182. if info.LastName != "" {
  183. args = append(args, info.LastName)
  184. clauses = append(clauses, `LOWER(aim_lastName) = LOWER(?)`)
  185. }
  186. if info.MiddleName != "" {
  187. args = append(args, info.MiddleName)
  188. clauses = append(clauses, `LOWER(aim_middleName) = LOWER(?)`)
  189. }
  190. if info.MaidenName != "" {
  191. args = append(args, info.MaidenName)
  192. clauses = append(clauses, `LOWER(aim_maidenName) = LOWER(?)`)
  193. }
  194. if info.Country != "" {
  195. args = append(args, info.Country)
  196. clauses = append(clauses, `LOWER(aim_country) = LOWER(?)`)
  197. }
  198. if info.State != "" {
  199. args = append(args, info.State)
  200. clauses = append(clauses, `LOWER(aim_state) = LOWER(?)`)
  201. }
  202. if info.City != "" {
  203. args = append(args, info.City)
  204. clauses = append(clauses, `LOWER(aim_city) = LOWER(?)`)
  205. }
  206. if info.NickName != "" {
  207. args = append(args, info.NickName)
  208. clauses = append(clauses, `LOWER(aim_nickName) = LOWER(?)`)
  209. }
  210. if info.ZIPCode != "" {
  211. args = append(args, info.ZIPCode)
  212. clauses = append(clauses, `LOWER(aim_zipCode) = LOWER(?)`)
  213. }
  214. if info.Address != "" {
  215. args = append(args, info.Address)
  216. clauses = append(clauses, `LOWER(aim_address) = LOWER(?)`)
  217. }
  218. whereClause := strings.Join(clauses, " AND ")
  219. users, err := f.queryUsers(ctx, whereClause, args)
  220. if err != nil {
  221. err = fmt.Errorf("FindByAIMNameAndAddr: %w", err)
  222. }
  223. return users, nil
  224. }
  225. func (f SQLiteUserStore) FindByICQInterests(ctx context.Context, code uint16, keywords []string) ([]User, error) {
  226. var args []any
  227. var clauses []string
  228. for i := 1; i <= 4; i++ {
  229. var subClauses []string
  230. args = append(args, code)
  231. for _, key := range keywords {
  232. subClauses = append(subClauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  233. args = append(args, "%"+key+"%")
  234. }
  235. clauses = append(clauses, fmt.Sprintf("(icq_interests_code%d = ? AND (%s))", i, strings.Join(subClauses, " OR ")))
  236. }
  237. cond := strings.Join(clauses, " OR ")
  238. users, err := f.queryUsers(ctx, cond, args)
  239. if err != nil {
  240. err = fmt.Errorf("FindByICQInterests: %w", err)
  241. }
  242. return users, nil
  243. }
  244. func (f SQLiteUserStore) FindByICQKeyword(ctx context.Context, keyword string) ([]User, error) {
  245. var args []any
  246. var clauses []string
  247. for i := 1; i <= 4; i++ {
  248. args = append(args, "%"+keyword+"%")
  249. clauses = append(clauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  250. }
  251. whereClause := strings.Join(clauses, " OR ")
  252. users, err := f.queryUsers(ctx, whereClause, args)
  253. if err != nil {
  254. err = fmt.Errorf("FindByICQKeyword: %w", err)
  255. }
  256. return users, nil
  257. }
  258. func (f SQLiteUserStore) User(ctx context.Context, screenName IdentScreenName) (*User, error) {
  259. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{screenName.String()})
  260. if err != nil {
  261. return nil, fmt.Errorf("User: %w", err)
  262. }
  263. if len(users) == 0 {
  264. return nil, nil
  265. }
  266. return &users[0], nil
  267. }
  268. // queryUsers retrieves a list of users from the database based on the
  269. // specified WHERE clause and query parameters. Returns a slice of User objects
  270. // or an error if the query fails.
  271. func (f SQLiteUserStore) queryUsers(ctx context.Context, whereClause string, queryParams []any) ([]User, error) {
  272. q := `
  273. SELECT
  274. identScreenName,
  275. displayScreenName,
  276. emailAddress,
  277. authKey,
  278. strongMD5Pass,
  279. weakMD5Pass,
  280. confirmStatus,
  281. regStatus,
  282. suspendedStatus,
  283. isBot,
  284. isICQ,
  285. icq_affiliations_currentCode1,
  286. icq_affiliations_currentCode2,
  287. icq_affiliations_currentCode3,
  288. icq_affiliations_currentKeyword1,
  289. icq_affiliations_currentKeyword2,
  290. icq_affiliations_currentKeyword3,
  291. icq_affiliations_pastCode1,
  292. icq_affiliations_pastCode2,
  293. icq_affiliations_pastCode3,
  294. icq_affiliations_pastKeyword1,
  295. icq_affiliations_pastKeyword2,
  296. icq_affiliations_pastKeyword3,
  297. icq_basicInfo_address,
  298. icq_basicInfo_cellPhone,
  299. icq_basicInfo_city,
  300. icq_basicInfo_countryCode,
  301. icq_basicInfo_emailAddress,
  302. icq_basicInfo_fax,
  303. icq_basicInfo_firstName,
  304. icq_basicInfo_gmtOffset,
  305. icq_basicInfo_lastName,
  306. icq_basicInfo_nickName,
  307. icq_basicInfo_phone,
  308. icq_basicInfo_publishEmail,
  309. icq_basicInfo_state,
  310. icq_basicInfo_zipCode,
  311. icq_interests_code1,
  312. icq_interests_code2,
  313. icq_interests_code3,
  314. icq_interests_code4,
  315. icq_interests_keyword1,
  316. icq_interests_keyword2,
  317. icq_interests_keyword3,
  318. icq_interests_keyword4,
  319. icq_moreInfo_birthDay,
  320. icq_moreInfo_birthMonth,
  321. icq_moreInfo_birthYear,
  322. icq_moreInfo_gender,
  323. icq_moreInfo_homePageAddr,
  324. icq_moreInfo_lang1,
  325. icq_moreInfo_lang2,
  326. icq_moreInfo_lang3,
  327. icq_notes,
  328. icq_permissions_authRequired,
  329. icq_workInfo_address,
  330. icq_workInfo_city,
  331. icq_workInfo_company,
  332. icq_workInfo_countryCode,
  333. icq_workInfo_department,
  334. icq_workInfo_fax,
  335. icq_workInfo_occupationCode,
  336. icq_workInfo_phone,
  337. icq_workInfo_position,
  338. icq_workInfo_state,
  339. icq_workInfo_webPage,
  340. icq_workInfo_zipCode,
  341. aim_firstName,
  342. aim_lastName,
  343. aim_middleName,
  344. aim_maidenName,
  345. aim_country,
  346. aim_state,
  347. aim_city,
  348. aim_nickName,
  349. aim_zipCode,
  350. aim_address,
  351. tocConfig,
  352. lastWarnUpdate,
  353. lastWarnLevel,
  354. offlineMsgCount
  355. FROM users
  356. WHERE %s
  357. `
  358. q = fmt.Sprintf(q, whereClause)
  359. rows, err := f.db.QueryContext(ctx, q, queryParams...)
  360. if err != nil {
  361. return nil, err
  362. }
  363. defer rows.Close()
  364. var users []User
  365. for rows.Next() {
  366. var u User
  367. var sn string
  368. var lastWarnUpdateUnix int64
  369. err := rows.Scan(
  370. &sn,
  371. &u.DisplayScreenName,
  372. &u.EmailAddress,
  373. &u.AuthKey,
  374. &u.StrongMD5Pass,
  375. &u.WeakMD5Pass,
  376. &u.ConfirmStatus,
  377. &u.RegStatus,
  378. &u.SuspendedStatus,
  379. &u.IsBot,
  380. &u.IsICQ,
  381. &u.ICQAffiliations.CurrentCode1,
  382. &u.ICQAffiliations.CurrentCode2,
  383. &u.ICQAffiliations.CurrentCode3,
  384. &u.ICQAffiliations.CurrentKeyword1,
  385. &u.ICQAffiliations.CurrentKeyword2,
  386. &u.ICQAffiliations.CurrentKeyword3,
  387. &u.ICQAffiliations.PastCode1,
  388. &u.ICQAffiliations.PastCode2,
  389. &u.ICQAffiliations.PastCode3,
  390. &u.ICQAffiliations.PastKeyword1,
  391. &u.ICQAffiliations.PastKeyword2,
  392. &u.ICQAffiliations.PastKeyword3,
  393. &u.ICQBasicInfo.Address,
  394. &u.ICQBasicInfo.CellPhone,
  395. &u.ICQBasicInfo.City,
  396. &u.ICQBasicInfo.CountryCode,
  397. &u.ICQBasicInfo.EmailAddress,
  398. &u.ICQBasicInfo.Fax,
  399. &u.ICQBasicInfo.FirstName,
  400. &u.ICQBasicInfo.GMTOffset,
  401. &u.ICQBasicInfo.LastName,
  402. &u.ICQBasicInfo.Nickname,
  403. &u.ICQBasicInfo.Phone,
  404. &u.ICQBasicInfo.PublishEmail,
  405. &u.ICQBasicInfo.State,
  406. &u.ICQBasicInfo.ZIPCode,
  407. &u.ICQInterests.Code1,
  408. &u.ICQInterests.Code2,
  409. &u.ICQInterests.Code3,
  410. &u.ICQInterests.Code4,
  411. &u.ICQInterests.Keyword1,
  412. &u.ICQInterests.Keyword2,
  413. &u.ICQInterests.Keyword3,
  414. &u.ICQInterests.Keyword4,
  415. &u.ICQMoreInfo.BirthDay,
  416. &u.ICQMoreInfo.BirthMonth,
  417. &u.ICQMoreInfo.BirthYear,
  418. &u.ICQMoreInfo.Gender,
  419. &u.ICQMoreInfo.HomePageAddr,
  420. &u.ICQMoreInfo.Lang1,
  421. &u.ICQMoreInfo.Lang2,
  422. &u.ICQMoreInfo.Lang3,
  423. &u.ICQNotes.Notes,
  424. &u.ICQPermissions.AuthRequired,
  425. &u.ICQWorkInfo.Address,
  426. &u.ICQWorkInfo.City,
  427. &u.ICQWorkInfo.Company,
  428. &u.ICQWorkInfo.CountryCode,
  429. &u.ICQWorkInfo.Department,
  430. &u.ICQWorkInfo.Fax,
  431. &u.ICQWorkInfo.OccupationCode,
  432. &u.ICQWorkInfo.Phone,
  433. &u.ICQWorkInfo.Position,
  434. &u.ICQWorkInfo.State,
  435. &u.ICQWorkInfo.WebPage,
  436. &u.ICQWorkInfo.ZIPCode,
  437. &u.AIMDirectoryInfo.FirstName,
  438. &u.AIMDirectoryInfo.LastName,
  439. &u.AIMDirectoryInfo.MiddleName,
  440. &u.AIMDirectoryInfo.MaidenName,
  441. &u.AIMDirectoryInfo.Country,
  442. &u.AIMDirectoryInfo.State,
  443. &u.AIMDirectoryInfo.City,
  444. &u.AIMDirectoryInfo.NickName,
  445. &u.AIMDirectoryInfo.ZIPCode,
  446. &u.AIMDirectoryInfo.Address,
  447. &u.TOCConfig,
  448. &lastWarnUpdateUnix,
  449. &u.LastWarnLevel,
  450. &u.OfflineMsgCount,
  451. )
  452. if err != nil {
  453. return nil, err
  454. }
  455. u.IdentScreenName = NewIdentScreenName(sn)
  456. u.LastWarnUpdate = time.Unix(lastWarnUpdateUnix, 0).UTC()
  457. users = append(users, u)
  458. }
  459. if err = rows.Err(); err != nil {
  460. return nil, err
  461. }
  462. return users, nil
  463. }
  464. func (f SQLiteUserStore) InsertUser(ctx context.Context, u User) error {
  465. if u.DisplayScreenName.IsUIN() && !u.IsICQ {
  466. return errors.New("inserting user with UIN and isICQ=false")
  467. }
  468. q := `
  469. INSERT INTO users (identScreenName, displayScreenName, authKey, weakMD5Pass, strongMD5Pass, isICQ, isBot)
  470. VALUES (?, ?, ?, ?, ?, ?, ?)
  471. ON CONFLICT (identScreenName) DO NOTHING
  472. `
  473. result, err := f.db.ExecContext(ctx,
  474. q,
  475. u.IdentScreenName.String(),
  476. u.DisplayScreenName,
  477. u.AuthKey,
  478. u.WeakMD5Pass,
  479. u.StrongMD5Pass,
  480. u.IsICQ,
  481. u.IsBot,
  482. )
  483. if err != nil {
  484. return err
  485. }
  486. rowsAffected, err := result.RowsAffected()
  487. if err != nil {
  488. return err
  489. }
  490. if rowsAffected == 0 {
  491. return ErrDupUser
  492. }
  493. return nil
  494. }
  495. func (f SQLiteUserStore) DeleteUser(ctx context.Context, screenName IdentScreenName) error {
  496. q := `
  497. DELETE FROM users WHERE identScreenName = ?
  498. `
  499. result, err := f.db.ExecContext(ctx, q, screenName.String())
  500. if err != nil {
  501. return err
  502. }
  503. rowsAffected, err := result.RowsAffected()
  504. if err != nil {
  505. return err
  506. }
  507. if rowsAffected == 0 {
  508. return ErrNoUser
  509. }
  510. return nil
  511. }
  512. func (f SQLiteUserStore) SetUserPassword(ctx context.Context, screenName IdentScreenName, newPassword string) error {
  513. tx, err := f.db.Begin()
  514. if err != nil {
  515. return err
  516. }
  517. defer func() {
  518. if err != nil {
  519. err = errors.Join(err, tx.Rollback())
  520. }
  521. }()
  522. q := `
  523. SELECT
  524. authKey,
  525. isICQ
  526. FROM users
  527. WHERE identScreenName = ?
  528. `
  529. u := User{}
  530. err = tx.QueryRowContext(ctx, q, screenName.String()).Scan(
  531. &u.AuthKey,
  532. &u.IsICQ,
  533. )
  534. if errors.Is(err, sql.ErrNoRows) {
  535. return ErrNoUser
  536. }
  537. if err = u.HashPassword(newPassword); err != nil {
  538. return err
  539. }
  540. q = `
  541. UPDATE users
  542. SET authKey = ?, weakMD5Pass = ?, strongMD5Pass = ?
  543. WHERE identScreenName = ?
  544. `
  545. result, err := tx.ExecContext(ctx, q, u.AuthKey, u.WeakMD5Pass, u.StrongMD5Pass, screenName.String())
  546. if err != nil {
  547. return err
  548. }
  549. rowsAffected, err := result.RowsAffected()
  550. if err != nil {
  551. return err
  552. }
  553. if rowsAffected == 0 {
  554. // it's possible the user didn't change OR the user doesn't exist.
  555. // check if the user exists.
  556. var exists int
  557. err = tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE identScreenName = ?", u.IdentScreenName.String()).Scan(&exists)
  558. if err != nil {
  559. return err // Handle possible SQL errors during the select
  560. }
  561. if exists == 0 {
  562. err = ErrNoUser // User does not exist
  563. return err
  564. }
  565. }
  566. return tx.Commit()
  567. }
  568. func (f SQLiteUserStore) Feedbag(ctx context.Context, screenName IdentScreenName) ([]wire.FeedbagItem, error) {
  569. q := `
  570. SELECT
  571. groupID,
  572. itemID,
  573. classID,
  574. name,
  575. attributes
  576. FROM feedbag
  577. WHERE screenName = ?
  578. `
  579. rows, err := f.db.QueryContext(ctx, q, screenName.String())
  580. if err != nil {
  581. return nil, err
  582. }
  583. defer rows.Close()
  584. var items []wire.FeedbagItem
  585. for rows.Next() {
  586. var item wire.FeedbagItem
  587. var attrs []byte
  588. if err := rows.Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs); err != nil {
  589. return nil, err
  590. }
  591. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  592. return items, err
  593. }
  594. items = append(items, item)
  595. }
  596. return items, nil
  597. }
  598. func (f SQLiteUserStore) FeedbagLastModified(ctx context.Context, screenName IdentScreenName) (time.Time, error) {
  599. var lastModified sql.NullInt64
  600. q := `SELECT MAX(lastModified) FROM feedbag WHERE screenName = ?`
  601. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&lastModified)
  602. return time.Unix(lastModified.Int64, 0), err
  603. }
  604. func (f SQLiteUserStore) FeedbagDelete(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  605. // todo add transaction
  606. q := `DELETE FROM feedbag WHERE screenName = ? AND itemID = ?`
  607. for _, item := range items {
  608. if _, err := f.db.ExecContext(ctx, q, screenName.String(), item.ItemID); err != nil {
  609. return err
  610. }
  611. }
  612. return nil
  613. }
  614. func (f SQLiteUserStore) FeedbagUpsert(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  615. q := `
  616. INSERT INTO feedbag (screenName, groupID, itemID, classID, name, attributes, pdMode, lastModified)
  617. VALUES (?, ?, ?, ?, ?, ?, ?, UNIXEPOCH())
  618. ON CONFLICT (screenName, groupID, itemID)
  619. DO UPDATE SET classID = excluded.classID,
  620. name = excluded.name,
  621. attributes = excluded.attributes,
  622. pdMode = excluded.pdMode,
  623. lastModified = UNIXEPOCH()
  624. `
  625. for _, item := range items {
  626. buf := &bytes.Buffer{}
  627. if err := wire.MarshalBE(item.TLVLBlock, buf); err != nil {
  628. return err
  629. }
  630. if item.ClassID == wire.FeedbagClassIdBuddy ||
  631. item.ClassID == wire.FeedbagClassIDPermit ||
  632. item.ClassID == wire.FeedbagClassIDDeny {
  633. // insert screen name identifier
  634. item.Name = NewIdentScreenName(item.Name).String()
  635. }
  636. pdMode := uint8(0)
  637. if item.ClassID == wire.FeedbagClassIdPdinfo {
  638. var hasMode bool
  639. pdMode, hasMode = item.Uint8(wire.FeedbagAttributesPdMode)
  640. if !hasMode {
  641. // by default, QIP sends a PD info item entry with no mode
  642. pdMode = uint8(wire.FeedbagPDModePermitAll)
  643. }
  644. }
  645. _, err := f.db.ExecContext(ctx,
  646. q,
  647. screenName.String(),
  648. item.GroupID,
  649. item.ItemID,
  650. item.ClassID,
  651. item.Name,
  652. buf.Bytes(),
  653. pdMode)
  654. if err != nil {
  655. return err
  656. }
  657. }
  658. return nil
  659. }
  660. func (f SQLiteUserStore) ClearBuddyListRegistry(ctx context.Context) error {
  661. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode`); err != nil {
  662. return err
  663. }
  664. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList`); err != nil {
  665. return err
  666. }
  667. return nil
  668. }
  669. func (f SQLiteUserStore) RegisterBuddyList(ctx context.Context, user IdentScreenName) error {
  670. q := `
  671. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  672. ON CONFLICT (screenName) DO NOTHING
  673. `
  674. _, err := f.db.ExecContext(ctx, q, user.String(), wire.FeedbagPDModePermitAll)
  675. return err
  676. }
  677. func (f SQLiteUserStore) UnregisterBuddyList(ctx context.Context, user IdentScreenName) error {
  678. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode WHERE screenName = ?`, user.String()); err != nil {
  679. return err
  680. }
  681. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList WHERE me = ?`, user.String()); err != nil {
  682. return err
  683. }
  684. return nil
  685. }
  686. func (f SQLiteUserStore) UseFeedbag(ctx context.Context, screenName IdentScreenName) error {
  687. q := `
  688. INSERT INTO buddyListMode (screenName, useFeedbag)
  689. VALUES (?, ?)
  690. ON CONFLICT (screenName)
  691. DO UPDATE SET clientSidePDMode = 0,
  692. useFeedbag = true
  693. `
  694. _, err := f.db.ExecContext(ctx, q, screenName.String(), true)
  695. return err
  696. }
  697. func (f SQLiteUserStore) SetPDMode(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  698. alreadySet, err := f.isPDModeEqual(ctx, me, pdMode)
  699. if err != nil {
  700. return fmt.Errorf("isPDModeEqual: %w", err)
  701. }
  702. if alreadySet {
  703. return nil
  704. }
  705. tx, err := f.db.Begin()
  706. if err != nil {
  707. return err
  708. }
  709. defer func() {
  710. _ = tx.Rollback()
  711. }()
  712. if err := setClientSidePDMode(ctx, tx, me, pdMode); err != nil {
  713. return fmt.Errorf("setClientSidePDMode: %w", err)
  714. }
  715. if err := clearClientSidePDFlags(ctx, tx, me, pdMode); err != nil {
  716. return fmt.Errorf("clearClientSidePDFlags: %w", err)
  717. }
  718. if err := clearBlankClientSideBuddies(ctx, tx, me, pdMode); err != nil {
  719. return fmt.Errorf("clearBlankClientSideBuddies: %w", err)
  720. }
  721. if err := tx.Commit(); err != nil {
  722. return fmt.Errorf("commit: %w", err)
  723. }
  724. return nil
  725. }
  726. // isPDModeEqual indicates whether the current permit/deny mode is already set
  727. // to pdMode.
  728. func (f SQLiteUserStore) isPDModeEqual(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) (bool, error) {
  729. q := `
  730. SELECT true
  731. FROM buddyListMode
  732. WHERE screenName = ? AND clientSidePDMode = ?
  733. `
  734. var isEqual bool
  735. err := f.db.QueryRowContext(ctx, q, me.String(), pdMode).Scan(&isEqual)
  736. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  737. return false, err
  738. }
  739. return isEqual, nil
  740. }
  741. // setClientSidePDMode sets the permit/deny mode for my client-side buddy list.
  742. func setClientSidePDMode(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  743. q := `
  744. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  745. ON CONFLICT (screenName)
  746. DO UPDATE SET clientSidePDMode = excluded.clientSidePDMode
  747. `
  748. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  749. if err != nil {
  750. return err
  751. }
  752. return nil
  753. }
  754. // clearBlankClientSideBuddies removes client-side buddy where all flags
  755. // (isBuddy, isPermit, isDeny) are false.
  756. func clearBlankClientSideBuddies(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  757. q := `
  758. DELETE FROM clientSideBuddyList
  759. WHERE isBuddy IS FALSE
  760. AND isPermit IS FALSE
  761. AND isDeny IS FALSE
  762. AND me = ?
  763. `
  764. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  765. return err
  766. }
  767. // clearClientSidePDFlags clears permit/deny flags.
  768. func clearClientSidePDFlags(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  769. q := `
  770. UPDATE clientSideBuddyList
  771. SET isDeny = false, isPermit = false
  772. WHERE me = ?
  773. `
  774. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  775. return err
  776. }
  777. func (f SQLiteUserStore) AddBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  778. q := `
  779. INSERT INTO clientSideBuddyList (me, them, isBuddy)
  780. VALUES (?, ?, true)
  781. ON CONFLICT (me, them) DO UPDATE SET isBuddy = true
  782. `
  783. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  784. return err
  785. }
  786. func (f SQLiteUserStore) RemoveBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  787. q := `
  788. UPDATE clientSideBuddyList
  789. SET isBuddy = false
  790. WHERE me = ?
  791. AND them = ?
  792. `
  793. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  794. return err
  795. }
  796. func (f SQLiteUserStore) DenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  797. q := `
  798. INSERT INTO clientSideBuddyList (me, them, isDeny)
  799. VALUES (?, ?, 1)
  800. ON CONFLICT (me, them) DO UPDATE SET isDeny = 1
  801. `
  802. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  803. return err
  804. }
  805. func (f SQLiteUserStore) RemoveDenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  806. q := `
  807. UPDATE clientSideBuddyList
  808. SET isDeny = false
  809. WHERE me = ?
  810. AND them = ?
  811. `
  812. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  813. return err
  814. }
  815. func (f SQLiteUserStore) PermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  816. q := `
  817. INSERT INTO clientSideBuddyList (me, them, isPermit)
  818. VALUES (?, ?, 1)
  819. ON CONFLICT (me, them) DO UPDATE SET isPermit = 1
  820. `
  821. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  822. return err
  823. }
  824. func (f SQLiteUserStore) RemovePermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  825. q := `
  826. UPDATE clientSideBuddyList
  827. SET isPermit = false
  828. WHERE me = ?
  829. AND them = ?
  830. `
  831. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  832. return err
  833. }
  834. func (f SQLiteUserStore) Profile(ctx context.Context, screenName IdentScreenName) (UserProfile, error) {
  835. q := `
  836. SELECT IFNULL(body, ''), IFNULL(mimeType, ''), IFNULL(updateTime, 0)
  837. FROM profile
  838. WHERE screenName = ?
  839. `
  840. var profile UserProfile
  841. var updateTimeUnix int64
  842. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&profile.ProfileText, &profile.MIMEType, &updateTimeUnix)
  843. if errors.Is(err, sql.ErrNoRows) {
  844. return UserProfile{}, nil
  845. }
  846. if err != nil {
  847. return UserProfile{}, err
  848. }
  849. if updateTimeUnix > 0 {
  850. profile.UpdateTime = time.Unix(updateTimeUnix, 0).UTC()
  851. }
  852. return profile, nil
  853. }
  854. func (f SQLiteUserStore) SetProfile(ctx context.Context, screenName IdentScreenName, profile UserProfile) error {
  855. var updateTimeUnix int64
  856. if !profile.UpdateTime.IsZero() {
  857. updateTimeUnix = profile.UpdateTime.Unix()
  858. }
  859. q := `
  860. INSERT INTO profile (screenName, body, mimeType, updateTime)
  861. VALUES (?, ?, ?, ?)
  862. ON CONFLICT (screenName)
  863. DO UPDATE SET body = excluded.body,
  864. mimeType = excluded.mimeType,
  865. updateTime = excluded.updateTime
  866. `
  867. _, err := f.db.ExecContext(ctx, q, screenName.String(), profile.ProfileText, profile.MIMEType, updateTimeUnix)
  868. return err
  869. }
  870. func (f SQLiteUserStore) SetDirectoryInfo(ctx context.Context, screenName IdentScreenName, info AIMNameAndAddr) error {
  871. q := `
  872. UPDATE users SET
  873. aim_firstName = ?,
  874. aim_lastName = ?,
  875. aim_middleName = ?,
  876. aim_maidenName = ?,
  877. aim_country = ?,
  878. aim_state = ?,
  879. aim_city = ?,
  880. aim_nickName = ?,
  881. aim_zipCode = ?,
  882. aim_address = ?
  883. WHERE identScreenName = ?
  884. `
  885. res, err := f.db.ExecContext(ctx,
  886. q,
  887. info.FirstName,
  888. info.LastName,
  889. info.MiddleName,
  890. info.MaidenName,
  891. info.Country,
  892. info.State,
  893. info.City,
  894. info.NickName,
  895. info.ZIPCode,
  896. info.Address,
  897. screenName.String(),
  898. )
  899. if err != nil {
  900. return fmt.Errorf("exec: %w", err)
  901. }
  902. c, err := res.RowsAffected()
  903. if err != nil {
  904. return fmt.Errorf("rows affected: %w", err)
  905. }
  906. if c == 0 {
  907. return ErrNoUser
  908. }
  909. return nil
  910. }
  911. func (f SQLiteUserStore) InsertBARTItem(ctx context.Context, hash []byte, blob []byte, itemType uint16) error {
  912. q := `
  913. INSERT INTO bartItem (hash, body, type)
  914. VALUES (?, ?, ?)
  915. `
  916. _, err := f.db.ExecContext(ctx, q, hash, blob, itemType)
  917. if err != nil {
  918. if liteErr, ok := err.(*sqlite.Error); ok {
  919. code := liteErr.Code()
  920. if code == lib.SQLITE_CONSTRAINT_PRIMARYKEY {
  921. return ErrBARTItemExists
  922. }
  923. }
  924. return err
  925. }
  926. return nil
  927. }
  928. func (f SQLiteUserStore) BARTItem(ctx context.Context, hash []byte) ([]byte, error) {
  929. q := `
  930. SELECT body
  931. FROM bartItem
  932. WHERE hash = ?
  933. `
  934. var body []byte
  935. err := f.db.QueryRowContext(ctx, q, hash).Scan(&body)
  936. if errors.Is(err, sql.ErrNoRows) {
  937. err = nil
  938. }
  939. return body, err
  940. }
  941. // BARTItem represents a BART asset with its hash and type.
  942. type BARTItem struct {
  943. Hash string
  944. Type uint16
  945. }
  946. func (f SQLiteUserStore) ListBARTItems(ctx context.Context, itemType uint16) ([]BARTItem, error) {
  947. q := `
  948. SELECT hash, type
  949. FROM bartItem
  950. WHERE type = ?
  951. `
  952. rows, err := f.db.QueryContext(ctx, q, itemType)
  953. if err != nil {
  954. return nil, err
  955. }
  956. defer rows.Close()
  957. var items []BARTItem
  958. for rows.Next() {
  959. var item BARTItem
  960. var hashBytes []byte
  961. err := rows.Scan(&hashBytes, &item.Type)
  962. if err != nil {
  963. return nil, err
  964. }
  965. item.Hash = hex.EncodeToString(hashBytes)
  966. items = append(items, item)
  967. }
  968. if err := rows.Err(); err != nil {
  969. return nil, err
  970. }
  971. return items, nil
  972. }
  973. func (f SQLiteUserStore) DeleteBARTItem(ctx context.Context, hash []byte) error {
  974. q := `
  975. DELETE FROM bartItem
  976. WHERE hash = ?
  977. `
  978. result, err := f.db.ExecContext(ctx, q, hash)
  979. if err != nil {
  980. return err
  981. }
  982. rowsAffected, err := result.RowsAffected()
  983. if err != nil {
  984. return err
  985. }
  986. if rowsAffected == 0 {
  987. return ErrBARTItemNotFound
  988. }
  989. return nil
  990. }
  991. func (f SQLiteUserStore) ChatRoomByCookie(ctx context.Context, chatCookie string) (ChatRoom, error) {
  992. chatRoom := ChatRoom{}
  993. q := `
  994. SELECT exchange, name, created, creator
  995. FROM chatRoom
  996. WHERE lower(cookie) = lower(?)
  997. `
  998. var creator string
  999. err := f.db.QueryRowContext(ctx, q, chatCookie).Scan(
  1000. &chatRoom.exchange,
  1001. &chatRoom.name,
  1002. &chatRoom.createTime,
  1003. &creator,
  1004. )
  1005. if errors.Is(err, sql.ErrNoRows) {
  1006. err = fmt.Errorf("%w: %s", ErrChatRoomNotFound, chatCookie)
  1007. }
  1008. chatRoom.creator = NewIdentScreenName(creator)
  1009. return chatRoom, err
  1010. }
  1011. func (f SQLiteUserStore) ChatRoomByName(ctx context.Context, exchange uint16, name string) (ChatRoom, error) {
  1012. chatRoom := ChatRoom{
  1013. exchange: exchange,
  1014. }
  1015. q := `
  1016. SELECT name, created, creator
  1017. FROM chatRoom
  1018. WHERE exchange = ? AND lower(name) = lower(?)
  1019. `
  1020. var creator string
  1021. err := f.db.QueryRowContext(ctx, q, exchange, name).Scan(
  1022. &chatRoom.name,
  1023. &chatRoom.createTime,
  1024. &creator,
  1025. )
  1026. if errors.Is(err, sql.ErrNoRows) {
  1027. err = ErrChatRoomNotFound
  1028. }
  1029. chatRoom.creator = NewIdentScreenName(creator)
  1030. return chatRoom, err
  1031. }
  1032. func (f SQLiteUserStore) CreateChatRoom(ctx context.Context, chatRoom *ChatRoom) error {
  1033. chatRoom.createTime = time.Now().UTC()
  1034. q := `
  1035. INSERT INTO chatRoom (cookie, exchange, name, created, creator)
  1036. VALUES (?, ?, ?, ?, ?)
  1037. `
  1038. _, err := f.db.ExecContext(ctx,
  1039. q,
  1040. chatRoom.Cookie(),
  1041. chatRoom.Exchange(),
  1042. chatRoom.Name(),
  1043. chatRoom.createTime,
  1044. chatRoom.Creator().String(),
  1045. )
  1046. if err != nil {
  1047. if strings.Contains(err.Error(), "constraint failed") {
  1048. err = ErrDupChatRoom
  1049. }
  1050. err = fmt.Errorf("CreateChatRoom: %w", err)
  1051. }
  1052. return err
  1053. }
  1054. func (f SQLiteUserStore) AllChatRooms(ctx context.Context, exchange uint16) ([]ChatRoom, error) {
  1055. q := `
  1056. SELECT created, creator, name
  1057. FROM chatRoom
  1058. WHERE exchange = ?
  1059. ORDER BY created ASC
  1060. `
  1061. rows, err := f.db.QueryContext(ctx, q, exchange)
  1062. if err != nil {
  1063. return nil, err
  1064. }
  1065. defer rows.Close()
  1066. var users []ChatRoom
  1067. for rows.Next() {
  1068. cr := ChatRoom{
  1069. exchange: exchange,
  1070. }
  1071. var creator string
  1072. if err := rows.Scan(&cr.createTime, &creator, &cr.name); err != nil {
  1073. return nil, err
  1074. }
  1075. cr.creator = NewIdentScreenName(creator)
  1076. users = append(users, cr)
  1077. }
  1078. if err := rows.Err(); err != nil {
  1079. return nil, err
  1080. }
  1081. return users, nil
  1082. }
  1083. func (f SQLiteUserStore) DeleteChatRooms(ctx context.Context, exchange uint16, names []string) error {
  1084. if len(names) == 0 {
  1085. return nil
  1086. }
  1087. // Build the query with placeholders for each name
  1088. placeholders := make([]string, len(names))
  1089. args := make([]interface{}, 0, len(names)+1)
  1090. args = append(args, exchange)
  1091. for i, name := range names {
  1092. placeholders[i] = "?"
  1093. args = append(args, name)
  1094. }
  1095. q := fmt.Sprintf(`
  1096. DELETE FROM chatRoom
  1097. WHERE exchange = ? AND name IN (%s)
  1098. `, strings.Join(placeholders, ","))
  1099. _, err := f.db.ExecContext(ctx, q, args...)
  1100. if err != nil {
  1101. return fmt.Errorf("DeleteChatRooms: %w", err)
  1102. }
  1103. return nil
  1104. }
  1105. func (f SQLiteUserStore) UpdateDisplayScreenName(ctx context.Context, displayScreenName DisplayScreenName) error {
  1106. q := `
  1107. UPDATE users
  1108. SET displayScreenName = ?
  1109. WHERE identScreenName = ?
  1110. `
  1111. _, err := f.db.ExecContext(ctx, q, displayScreenName.String(), displayScreenName.IdentScreenName().String())
  1112. return err
  1113. }
  1114. func (f SQLiteUserStore) UpdateEmailAddress(ctx context.Context, screenName IdentScreenName, emailAddress *mail.Address) error {
  1115. q := `
  1116. UPDATE users
  1117. SET emailAddress = ?
  1118. WHERE identScreenName = ?
  1119. `
  1120. _, err := f.db.ExecContext(ctx, q, emailAddress.Address, screenName.String())
  1121. return err
  1122. }
  1123. func (f SQLiteUserStore) EmailAddress(ctx context.Context, screenName IdentScreenName) (*mail.Address, error) {
  1124. q := `
  1125. SELECT emailAddress
  1126. FROM users
  1127. WHERE identScreenName = ?
  1128. `
  1129. var emailAddress string
  1130. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&emailAddress)
  1131. // username isn't found for some reason
  1132. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1133. return nil, err
  1134. }
  1135. e, err := mail.ParseAddress(emailAddress)
  1136. if err != nil {
  1137. return nil, fmt.Errorf("%w: %w", ErrNoEmailAddress, err)
  1138. }
  1139. return e, nil
  1140. }
  1141. func (f SQLiteUserStore) UpdateRegStatus(ctx context.Context, screenName IdentScreenName, regStatus uint16) error {
  1142. q := `
  1143. UPDATE users
  1144. SET regStatus = ?
  1145. WHERE identScreenName = ?
  1146. `
  1147. _, err := f.db.ExecContext(ctx, q, regStatus, screenName.String())
  1148. return err
  1149. }
  1150. func (f SQLiteUserStore) RegStatus(ctx context.Context, screenName IdentScreenName) (uint16, error) {
  1151. q := `
  1152. SELECT regStatus
  1153. FROM users
  1154. WHERE identScreenName = ?
  1155. `
  1156. var regStatus uint16
  1157. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&regStatus)
  1158. // username isn't found for some reason
  1159. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1160. return 0, err
  1161. }
  1162. return regStatus, nil
  1163. }
  1164. func (f SQLiteUserStore) UpdateConfirmStatus(ctx context.Context, screenName IdentScreenName, confirmStatus bool) error {
  1165. q := `
  1166. UPDATE users
  1167. SET confirmStatus = ?
  1168. WHERE identScreenName = ?
  1169. `
  1170. _, err := f.db.ExecContext(ctx, q, confirmStatus, screenName.String())
  1171. return err
  1172. }
  1173. func (f SQLiteUserStore) ConfirmStatus(ctx context.Context, screenName IdentScreenName) (bool, error) {
  1174. q := `
  1175. SELECT confirmStatus
  1176. FROM users
  1177. WHERE identScreenName = ?
  1178. `
  1179. var confirmStatus bool
  1180. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&confirmStatus)
  1181. // username isn't found for some reason
  1182. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1183. return false, err
  1184. }
  1185. return confirmStatus, nil
  1186. }
  1187. func (f SQLiteUserStore) UpdateSuspendedStatus(ctx context.Context, suspendedStatus uint16, screenName IdentScreenName) error {
  1188. q := `
  1189. UPDATE users
  1190. SET suspendedStatus = ?
  1191. WHERE identScreenName = ?
  1192. `
  1193. _, err := f.db.ExecContext(ctx, q, suspendedStatus, screenName.String())
  1194. return err
  1195. }
  1196. func (f SQLiteUserStore) SetBotStatus(ctx context.Context, isBot bool, screenName IdentScreenName) error {
  1197. q := `
  1198. UPDATE users
  1199. SET isBot = ?
  1200. WHERE identScreenName = ?
  1201. `
  1202. _, err := f.db.ExecContext(ctx, q, isBot, screenName.String())
  1203. return err
  1204. }
  1205. func (f SQLiteUserStore) SetWorkInfo(ctx context.Context, name IdentScreenName, data ICQWorkInfo) error {
  1206. q := `
  1207. UPDATE users SET
  1208. icq_workInfo_company = ?,
  1209. icq_workInfo_department = ?,
  1210. icq_workInfo_occupationCode = ?,
  1211. icq_workInfo_position = ?,
  1212. icq_workInfo_address = ?,
  1213. icq_workInfo_city = ?,
  1214. icq_workInfo_countryCode = ?,
  1215. icq_workInfo_fax = ?,
  1216. icq_workInfo_phone = ?,
  1217. icq_workInfo_state = ?,
  1218. icq_workInfo_webPage = ?,
  1219. icq_workInfo_zipCode = ?
  1220. WHERE identScreenName = ?
  1221. `
  1222. res, err := f.db.ExecContext(ctx,
  1223. q,
  1224. data.Company,
  1225. data.Department,
  1226. data.OccupationCode,
  1227. data.Position,
  1228. data.Address,
  1229. data.City,
  1230. data.CountryCode,
  1231. data.Fax,
  1232. data.Phone,
  1233. data.State,
  1234. data.WebPage,
  1235. data.ZIPCode,
  1236. name.String(),
  1237. )
  1238. if err != nil {
  1239. return fmt.Errorf("exec: %w", err)
  1240. }
  1241. c, err := res.RowsAffected()
  1242. if err != nil {
  1243. return fmt.Errorf("rows affected: %w", err)
  1244. }
  1245. if c == 0 {
  1246. return ErrNoUser
  1247. }
  1248. return nil
  1249. }
  1250. func (f SQLiteUserStore) SetMoreInfo(ctx context.Context, name IdentScreenName, data ICQMoreInfo) error {
  1251. q := `
  1252. UPDATE users SET
  1253. icq_moreInfo_birthDay = ?,
  1254. icq_moreInfo_birthMonth = ?,
  1255. icq_moreInfo_birthYear = ?,
  1256. icq_moreInfo_gender = ?,
  1257. icq_moreInfo_homePageAddr = ?,
  1258. icq_moreInfo_lang1 = ?,
  1259. icq_moreInfo_lang2 = ?,
  1260. icq_moreInfo_lang3 = ?
  1261. WHERE identScreenName = ?
  1262. `
  1263. res, err := f.db.ExecContext(ctx,
  1264. q,
  1265. data.BirthDay,
  1266. data.BirthMonth,
  1267. data.BirthYear,
  1268. data.Gender,
  1269. data.HomePageAddr,
  1270. data.Lang1,
  1271. data.Lang2,
  1272. data.Lang3,
  1273. name.String(),
  1274. )
  1275. if err != nil {
  1276. return fmt.Errorf("exec: %w", err)
  1277. }
  1278. c, err := res.RowsAffected()
  1279. if err != nil {
  1280. return fmt.Errorf("rows affected: %w", err)
  1281. }
  1282. if c == 0 {
  1283. return ErrNoUser
  1284. }
  1285. return nil
  1286. }
  1287. func (f SQLiteUserStore) SetUserNotes(ctx context.Context, name IdentScreenName, data ICQUserNotes) error {
  1288. q := `
  1289. UPDATE users
  1290. SET icq_notes = ?
  1291. WHERE identScreenName = ?
  1292. `
  1293. res, err := f.db.ExecContext(ctx,
  1294. q,
  1295. data.Notes,
  1296. name.String(),
  1297. )
  1298. if err != nil {
  1299. return fmt.Errorf("exec: %w", err)
  1300. }
  1301. c, err := res.RowsAffected()
  1302. if err != nil {
  1303. return fmt.Errorf("rows affected: %w", err)
  1304. }
  1305. if c == 0 {
  1306. return ErrNoUser
  1307. }
  1308. return nil
  1309. }
  1310. func (f SQLiteUserStore) SetInterests(ctx context.Context, name IdentScreenName, data ICQInterests) error {
  1311. q := `
  1312. UPDATE users SET
  1313. icq_interests_code1 = ?,
  1314. icq_interests_keyword1 = ?,
  1315. icq_interests_code2 = ?,
  1316. icq_interests_keyword2 = ?,
  1317. icq_interests_code3 = ?,
  1318. icq_interests_keyword3 = ?,
  1319. icq_interests_code4 = ?,
  1320. icq_interests_keyword4 = ?
  1321. WHERE identScreenName = ?
  1322. `
  1323. res, err := f.db.ExecContext(ctx,
  1324. q,
  1325. data.Code1,
  1326. data.Keyword1,
  1327. data.Code2,
  1328. data.Keyword2,
  1329. data.Code3,
  1330. data.Keyword3,
  1331. data.Code4,
  1332. data.Keyword4,
  1333. name.String(),
  1334. )
  1335. if err != nil {
  1336. return fmt.Errorf("exec: %w", err)
  1337. }
  1338. c, err := res.RowsAffected()
  1339. if err != nil {
  1340. return fmt.Errorf("rows affected: %w", err)
  1341. }
  1342. if c == 0 {
  1343. return ErrNoUser
  1344. }
  1345. return nil
  1346. }
  1347. func (f SQLiteUserStore) SetAffiliations(ctx context.Context, name IdentScreenName, data ICQAffiliations) error {
  1348. q := `
  1349. UPDATE users SET
  1350. icq_affiliations_currentCode1 = ?,
  1351. icq_affiliations_currentKeyword1 = ?,
  1352. icq_affiliations_currentCode2 = ?,
  1353. icq_affiliations_currentKeyword2 = ?,
  1354. icq_affiliations_currentCode3 = ?,
  1355. icq_affiliations_currentKeyword3 = ?,
  1356. icq_affiliations_pastCode1 = ?,
  1357. icq_affiliations_pastKeyword1 = ?,
  1358. icq_affiliations_pastCode2 = ?,
  1359. icq_affiliations_pastKeyword2 = ?,
  1360. icq_affiliations_pastCode3 = ?,
  1361. icq_affiliations_pastKeyword3 = ?
  1362. WHERE identScreenName = ?
  1363. `
  1364. res, err := f.db.ExecContext(ctx,
  1365. q,
  1366. data.CurrentCode1,
  1367. data.CurrentKeyword1,
  1368. data.CurrentCode2,
  1369. data.CurrentKeyword2,
  1370. data.CurrentCode3,
  1371. data.CurrentKeyword3,
  1372. data.PastCode1,
  1373. data.PastKeyword1,
  1374. data.PastCode2,
  1375. data.PastKeyword2,
  1376. data.PastCode3,
  1377. data.PastKeyword3,
  1378. name.String(),
  1379. )
  1380. if err != nil {
  1381. return fmt.Errorf("exec: %w", err)
  1382. }
  1383. c, err := res.RowsAffected()
  1384. if err != nil {
  1385. return fmt.Errorf("rows affected: %w", err)
  1386. }
  1387. if c == 0 {
  1388. return ErrNoUser
  1389. }
  1390. return nil
  1391. }
  1392. func (f SQLiteUserStore) SetBasicInfo(ctx context.Context, name IdentScreenName, data ICQBasicInfo) error {
  1393. q := `
  1394. UPDATE users SET
  1395. icq_basicInfo_cellPhone = ?,
  1396. icq_basicInfo_countryCode = ?,
  1397. icq_basicInfo_emailAddress = ?,
  1398. icq_basicInfo_firstName = ?,
  1399. icq_basicInfo_gmtOffset = ?,
  1400. icq_basicInfo_address = ?,
  1401. icq_basicInfo_city = ?,
  1402. icq_basicInfo_fax = ?,
  1403. icq_basicInfo_phone = ?,
  1404. icq_basicInfo_state = ?,
  1405. icq_basicInfo_lastName = ?,
  1406. icq_basicInfo_nickName = ?,
  1407. icq_basicInfo_publishEmail = ?,
  1408. icq_basicInfo_zipCode = ?
  1409. WHERE identScreenName = ?
  1410. `
  1411. res, err := f.db.ExecContext(ctx,
  1412. q,
  1413. data.CellPhone,
  1414. data.CountryCode,
  1415. data.EmailAddress,
  1416. data.FirstName,
  1417. data.GMTOffset,
  1418. data.Address,
  1419. data.City,
  1420. data.Fax,
  1421. data.Phone,
  1422. data.State,
  1423. data.LastName,
  1424. data.Nickname,
  1425. data.PublishEmail,
  1426. data.ZIPCode,
  1427. name.String(),
  1428. )
  1429. if err != nil {
  1430. return fmt.Errorf("exec: %w", err)
  1431. }
  1432. c, err := res.RowsAffected()
  1433. if err != nil {
  1434. return fmt.Errorf("rows affected: %w", err)
  1435. }
  1436. if c == 0 {
  1437. return ErrNoUser
  1438. }
  1439. return nil
  1440. }
  1441. func (f SQLiteUserStore) SaveMessage(ctx context.Context, offlineMessage OfflineMessage) (newCount int, err error) {
  1442. buf := &bytes.Buffer{}
  1443. if err := wire.MarshalBE(offlineMessage.Message, buf); err != nil {
  1444. return 0, fmt.Errorf("marshal: %w", err)
  1445. }
  1446. var tx *sql.Tx
  1447. tx, err = f.db.BeginTx(ctx, nil)
  1448. if err != nil {
  1449. return 0, fmt.Errorf("begin tx: %w", err)
  1450. }
  1451. defer func() {
  1452. if err != nil {
  1453. _ = tx.Rollback()
  1454. }
  1455. }()
  1456. const countQuery = `
  1457. SELECT COUNT(1)
  1458. FROM offlineMessage
  1459. WHERE sender = ? AND recipient = ?
  1460. `
  1461. var currentCount int
  1462. if err = tx.QueryRowContext(
  1463. ctx,
  1464. countQuery,
  1465. offlineMessage.Sender.String(),
  1466. offlineMessage.Recipient.String(),
  1467. ).Scan(&currentCount); err != nil {
  1468. return 0, fmt.Errorf("count: %w", err)
  1469. }
  1470. if currentCount >= offlineInboxLimit {
  1471. err = ErrOfflineInboxFull
  1472. return 0, err
  1473. }
  1474. q := `
  1475. INSERT INTO offlineMessage (sender, recipient, message, sent)
  1476. VALUES (?, ?, ?, ?)
  1477. `
  1478. if _, err = tx.ExecContext(ctx,
  1479. q,
  1480. offlineMessage.Sender.String(),
  1481. offlineMessage.Recipient.String(),
  1482. buf.Bytes(),
  1483. offlineMessage.Sent,
  1484. ); err != nil {
  1485. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1486. err = ErrNoUser
  1487. } else {
  1488. err = fmt.Errorf("insert: %w", err)
  1489. }
  1490. return 0, err
  1491. }
  1492. newCount = currentCount + 1
  1493. updateQuery := `
  1494. UPDATE users
  1495. SET offlineMsgCount = ?
  1496. WHERE identScreenName = ?
  1497. `
  1498. _, err = tx.ExecContext(ctx,
  1499. updateQuery,
  1500. newCount,
  1501. offlineMessage.Recipient.String(),
  1502. )
  1503. if err != nil {
  1504. return 0, fmt.Errorf("update offlineMsgCount: %w", err)
  1505. }
  1506. if err = tx.Commit(); err != nil {
  1507. return 0, fmt.Errorf("commit: %w", err)
  1508. }
  1509. return newCount, nil
  1510. }
  1511. func (f SQLiteUserStore) RetrieveMessages(ctx context.Context, recip IdentScreenName) ([]OfflineMessage, error) {
  1512. q := `
  1513. SELECT
  1514. sender,
  1515. message,
  1516. sent
  1517. FROM offlineMessage
  1518. WHERE recipient = ?
  1519. `
  1520. rows, err := f.db.QueryContext(ctx, q, recip.String())
  1521. if err != nil {
  1522. return nil, err
  1523. }
  1524. defer rows.Close()
  1525. var messages []OfflineMessage
  1526. for rows.Next() {
  1527. var sender string
  1528. var buf []byte
  1529. var sent time.Time
  1530. if err := rows.Scan(&sender, &buf, &sent); err != nil {
  1531. return nil, err
  1532. }
  1533. var msg wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
  1534. if err := wire.UnmarshalBE(&msg, bytes.NewBuffer(buf)); err != nil {
  1535. return nil, fmt.Errorf("unmarshal: %w", err)
  1536. }
  1537. messages = append(messages, OfflineMessage{
  1538. Sender: NewIdentScreenName(sender),
  1539. Recipient: recip,
  1540. Message: msg,
  1541. Sent: sent,
  1542. })
  1543. }
  1544. if err := rows.Err(); err != nil {
  1545. return nil, err
  1546. }
  1547. return messages, nil
  1548. }
  1549. func (f SQLiteUserStore) DeleteMessages(ctx context.Context, recip IdentScreenName) error {
  1550. q := `
  1551. DELETE FROM offlineMessage WHERE recipient = ?
  1552. `
  1553. _, err := f.db.ExecContext(ctx, q, recip.String())
  1554. return err
  1555. }
  1556. func (f SQLiteUserStore) BuddyIconMetadata(ctx context.Context, screenName IdentScreenName) (*wire.BARTID, error) {
  1557. q := `
  1558. SELECT
  1559. groupID,
  1560. itemID,
  1561. classID,
  1562. name,
  1563. attributes
  1564. FROM feedBag
  1565. WHERE screenname = ? AND name = ? AND classID = ?
  1566. `
  1567. var item wire.FeedbagItem
  1568. var attrs []byte
  1569. err := f.db.QueryRowContext(ctx, q, screenName.String(), wire.BARTTypesBuddyIcon, wire.FeedbagClassIdBart).Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs)
  1570. if errors.Is(err, sql.ErrNoRows) {
  1571. return nil, nil
  1572. }
  1573. if err != nil {
  1574. return nil, err
  1575. }
  1576. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  1577. return nil, err
  1578. }
  1579. b, hasBuf := item.Bytes(wire.FeedbagAttributesBartInfo)
  1580. if !hasBuf {
  1581. return nil, errors.New("unable to extract icon payload")
  1582. }
  1583. bartInfo := wire.BARTInfo{}
  1584. if err := wire.UnmarshalBE(&bartInfo, bytes.NewBuffer(b)); err != nil {
  1585. return nil, err
  1586. }
  1587. return &wire.BARTID{
  1588. Type: wire.BARTTypesBuddyIcon,
  1589. BARTInfo: wire.BARTInfo{
  1590. Flags: bartInfo.Flags,
  1591. Hash: bartInfo.Hash,
  1592. },
  1593. }, nil
  1594. }
  1595. func (f SQLiteUserStore) SetKeywords(ctx context.Context, screenName IdentScreenName, keywords [5]string) error {
  1596. q := `
  1597. WITH interests AS (SELECT CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword1,
  1598. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword2,
  1599. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword3,
  1600. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword4,
  1601. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword5
  1602. FROM aimKeyword
  1603. WHERE name IN (?, ?, ?, ?, ?))
  1604. UPDATE users
  1605. SET aim_keyword1 = (SELECT aim_keyword1 FROM interests WHERE aim_keyword1 IS NOT NULL),
  1606. aim_keyword2 = (SELECT aim_keyword2 FROM interests WHERE aim_keyword2 IS NOT NULL),
  1607. aim_keyword3 = (SELECT aim_keyword3 FROM interests WHERE aim_keyword3 IS NOT NULL),
  1608. aim_keyword4 = (SELECT aim_keyword4 FROM interests WHERE aim_keyword4 IS NOT NULL),
  1609. aim_keyword5 = (SELECT aim_keyword5 FROM interests WHERE aim_keyword5 IS NOT NULL)
  1610. WHERE identScreenName = ?
  1611. `
  1612. _, err := f.db.ExecContext(ctx, q,
  1613. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  1614. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  1615. screenName.String())
  1616. return err
  1617. }
  1618. func (f SQLiteUserStore) Categories(ctx context.Context) ([]Category, error) {
  1619. q := `SELECT id, name FROM aimKeywordCategory ORDER BY name`
  1620. rows, err := f.db.QueryContext(ctx, q)
  1621. if err != nil {
  1622. return nil, err
  1623. }
  1624. defer rows.Close()
  1625. var categories []Category
  1626. for rows.Next() {
  1627. category := Category{}
  1628. if err := rows.Scan(&category.ID, &category.Name); err != nil {
  1629. return nil, err
  1630. }
  1631. categories = append(categories, category)
  1632. }
  1633. if err := rows.Err(); err != nil {
  1634. return nil, err
  1635. }
  1636. return categories, nil
  1637. }
  1638. func (f SQLiteUserStore) CreateCategory(ctx context.Context, name string) (Category, error) {
  1639. tx, err := f.db.Begin()
  1640. if err != nil {
  1641. return Category{}, err
  1642. }
  1643. defer tx.Rollback()
  1644. q := `INSERT INTO aimKeywordCategory (name) VALUES (?)`
  1645. res, err := tx.ExecContext(ctx, q, name)
  1646. if err != nil {
  1647. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  1648. err = ErrKeywordCategoryExists
  1649. }
  1650. return Category{}, err
  1651. }
  1652. id, err := res.LastInsertId()
  1653. if err != nil {
  1654. return Category{}, err
  1655. }
  1656. if id > math.MaxUint8 {
  1657. return Category{}, errTooManyCategories
  1658. }
  1659. if err := tx.Commit(); err != nil {
  1660. return Category{}, err
  1661. }
  1662. return Category{
  1663. ID: uint8(id),
  1664. Name: name,
  1665. }, nil
  1666. }
  1667. func (f SQLiteUserStore) DeleteCategory(ctx context.Context, categoryID uint8) error {
  1668. q := `DELETE FROM aimKeywordCategory WHERE id = ?`
  1669. res, err := f.db.ExecContext(ctx, q, categoryID)
  1670. if err != nil {
  1671. // Check if the error is a foreign key constraint violation
  1672. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1673. return ErrKeywordInUse
  1674. }
  1675. }
  1676. c, err := res.RowsAffected()
  1677. if err != nil {
  1678. return err
  1679. }
  1680. if c == 0 {
  1681. return ErrKeywordCategoryNotFound
  1682. }
  1683. return nil
  1684. }
  1685. func (f SQLiteUserStore) KeywordsByCategory(ctx context.Context, categoryID uint8) ([]Keyword, error) {
  1686. q := `SELECT id, name FROM aimKeyword WHERE parent = ? ORDER BY name`
  1687. if categoryID == 0 {
  1688. q = `SELECT id, name FROM aimKeyword WHERE parent IS NULL ORDER BY name`
  1689. }
  1690. rows, err := f.db.QueryContext(ctx, q, categoryID)
  1691. if err != nil {
  1692. return nil, err
  1693. }
  1694. defer rows.Close()
  1695. var keywords []Keyword
  1696. for rows.Next() {
  1697. keyword := Keyword{}
  1698. if err := rows.Scan(&keyword.ID, &keyword.Name); err != nil {
  1699. return nil, err
  1700. }
  1701. keywords = append(keywords, keyword)
  1702. }
  1703. if err := rows.Err(); err != nil {
  1704. return nil, err
  1705. }
  1706. if len(keywords) == 0 {
  1707. var exists int
  1708. err = f.db.QueryRow("SELECT COUNT(*) FROM aimKeywordCategory WHERE id = ?", categoryID).Scan(&exists)
  1709. if err != nil {
  1710. return nil, err
  1711. }
  1712. if exists == 0 {
  1713. return nil, ErrKeywordCategoryNotFound
  1714. }
  1715. }
  1716. return keywords, nil
  1717. }
  1718. func (f SQLiteUserStore) CreateKeyword(ctx context.Context, name string, categoryID uint8) (Keyword, error) {
  1719. tx, err := f.db.Begin()
  1720. if err != nil {
  1721. return Keyword{}, err
  1722. }
  1723. defer tx.Rollback()
  1724. q := `INSERT INTO aimKeyword (name, parent) VALUES (?, ?)`
  1725. var parent interface{} = nil
  1726. if categoryID != 0 {
  1727. parent = categoryID
  1728. }
  1729. res, err := tx.ExecContext(ctx, q, name, parent)
  1730. if err != nil {
  1731. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  1732. err = ErrKeywordExists
  1733. } else if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1734. err = ErrKeywordCategoryNotFound
  1735. }
  1736. return Keyword{}, err
  1737. }
  1738. id, err := res.LastInsertId()
  1739. if err != nil {
  1740. return Keyword{}, err
  1741. }
  1742. if id > math.MaxUint8 {
  1743. return Keyword{}, errTooManyKeywords
  1744. }
  1745. if err := tx.Commit(); err != nil {
  1746. return Keyword{}, err
  1747. }
  1748. return Keyword{
  1749. ID: uint8(id),
  1750. Name: name,
  1751. }, nil
  1752. }
  1753. func (f SQLiteUserStore) DeleteKeyword(ctx context.Context, id uint8) error {
  1754. q := `DELETE FROM aimKeyword WHERE id = ?`
  1755. res, err := f.db.ExecContext(ctx, q, id)
  1756. if err != nil {
  1757. // Check if the error is a foreign key constraint violation
  1758. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1759. return ErrKeywordInUse
  1760. }
  1761. }
  1762. c, err := res.RowsAffected()
  1763. if err != nil {
  1764. return err
  1765. }
  1766. if c == 0 {
  1767. return ErrKeywordNotFound
  1768. }
  1769. return nil
  1770. }
  1771. // InterestList returns a list of keywords grouped by category used to render
  1772. // the AIM directory interests list. The list is made up of 3 types of elements:
  1773. //
  1774. // Categories
  1775. //
  1776. // ID: The category ID
  1777. // Cookie: The category name
  1778. // Type: [wire.ODirKeywordCategory]
  1779. //
  1780. // Keywords
  1781. //
  1782. // ID: The parent category ID
  1783. // Cookie: The keyword name
  1784. // Type: [wire.ODirKeyword]
  1785. //
  1786. // Top-level Keywords
  1787. //
  1788. // ID: 0 (does not have a parent category)
  1789. // Cookie: The keyword name
  1790. // Type: [wire.ODirKeyword]
  1791. //
  1792. // Keywords are grouped contiguously by category and preceded by the category
  1793. // name. Top-level keywords appear by themselves. Categories and top-level
  1794. // keywords are sorted alphabetically. Keyword groups are sorted alphabetically.
  1795. //
  1796. // Conceptually, the list looks like this:
  1797. //
  1798. // > Animals (top-level keyword, id=0)
  1799. // > Artificial Intelligence (keyword, id=3)
  1800. // > Cybersecurity (keyword, id=3)
  1801. // > Music (category, id=1)
  1802. // > Jazz (keyword, id=1)
  1803. // > Rock (keyword, id=1)
  1804. // > Sports (category, id=2)
  1805. // > Basketball (keyword, id=2)
  1806. // > Soccer (keyword, id=2)
  1807. // > Tennis (keyword, id=2)
  1808. // > Technology (category, id=3)
  1809. // > Zoology (top-level keyword, id=0)
  1810. func (f SQLiteUserStore) InterestList(ctx context.Context) ([]wire.ODirKeywordListItem, error) {
  1811. q := `
  1812. WITH categories AS (
  1813. SELECT
  1814. name AS grouping,
  1815. id,
  1816. 0 AS sortPrio,
  1817. name
  1818. FROM aimKeywordCategory
  1819. UNION
  1820. SELECT
  1821. IFNULL(akc.name, ak.name) AS grouping,
  1822. IFNULL(ak.parent, 0) AS id,
  1823. CASE WHEN ak.parent IS NULL THEN 1 ELSE 2 END AS sortPrio,
  1824. ak.name
  1825. FROM aimKeyword ak
  1826. LEFT JOIN aimKeywordCategory akc ON akc.id = ak.parent
  1827. ORDER BY 1, 3, 4
  1828. )
  1829. SELECT
  1830. id,
  1831. sortPrio,
  1832. name
  1833. FROM categories
  1834. `
  1835. rows, err := f.db.QueryContext(ctx, q)
  1836. if err != nil {
  1837. return nil, err
  1838. }
  1839. defer rows.Close()
  1840. var list []wire.ODirKeywordListItem
  1841. for rows.Next() {
  1842. msg := wire.ODirKeywordListItem{}
  1843. var sortPrio int
  1844. if err := rows.Scan(&msg.ID, &sortPrio, &msg.Name); err != nil {
  1845. return nil, err
  1846. }
  1847. switch sortPrio {
  1848. case 0:
  1849. msg.Type = wire.ODirKeywordCategory
  1850. case 1, 2:
  1851. msg.Type = wire.ODirKeyword
  1852. }
  1853. list = append(list, msg)
  1854. }
  1855. if err := rows.Err(); err != nil {
  1856. return nil, err
  1857. }
  1858. return list, nil
  1859. }
  1860. func (f SQLiteUserStore) SetTOCConfig(ctx context.Context, user IdentScreenName, config string) error {
  1861. q := `
  1862. UPDATE users
  1863. SET tocConfig = ?
  1864. WHERE identScreenName = ?
  1865. `
  1866. res, err := f.db.ExecContext(ctx,
  1867. q,
  1868. config,
  1869. user.String(),
  1870. )
  1871. if err != nil {
  1872. return fmt.Errorf("exec: %w", err)
  1873. }
  1874. c, err := res.RowsAffected()
  1875. if err != nil {
  1876. return fmt.Errorf("rows affected: %w", err)
  1877. }
  1878. if c == 0 {
  1879. return ErrNoUser
  1880. }
  1881. return nil
  1882. }
  1883. // SetWarnLevel updates the last warn update time and warning level for a user.
  1884. func (f SQLiteUserStore) SetWarnLevel(ctx context.Context, user IdentScreenName, lastWarnUpdate time.Time, lastWarnLevel uint16) error {
  1885. q := `
  1886. UPDATE users
  1887. SET lastWarnUpdate = ?, lastWarnLevel = ?
  1888. WHERE identScreenName = ?
  1889. `
  1890. res, err := f.db.ExecContext(ctx,
  1891. q,
  1892. lastWarnUpdate.Unix(),
  1893. lastWarnLevel,
  1894. user.String(),
  1895. )
  1896. if err != nil {
  1897. return fmt.Errorf("exec: %w", err)
  1898. }
  1899. c, err := res.RowsAffected()
  1900. if err != nil {
  1901. return fmt.Errorf("rows affected: %w", err)
  1902. }
  1903. if c == 0 {
  1904. return ErrNoUser
  1905. }
  1906. return nil
  1907. }
  1908. // SetOfflineMsgCount updates the offline message count for a user.
  1909. func (f SQLiteUserStore) SetOfflineMsgCount(ctx context.Context, screenName IdentScreenName, count int) error {
  1910. q := `
  1911. UPDATE users
  1912. SET offlineMsgCount = ?
  1913. WHERE identScreenName = ?
  1914. `
  1915. res, err := f.db.ExecContext(ctx,
  1916. q,
  1917. count,
  1918. screenName.String(),
  1919. )
  1920. if err != nil {
  1921. return fmt.Errorf("exec: %w", err)
  1922. }
  1923. c, err := res.RowsAffected()
  1924. if err != nil {
  1925. return fmt.Errorf("rows affected: %w", err)
  1926. }
  1927. if c == 0 {
  1928. return ErrNoUser
  1929. }
  1930. return nil
  1931. }