user_store.go 53 KB

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