mirror of
https://github.com/cheveguerra/bot-whatsapp.git
synced 2026-04-17 19:26:23 +00:00
working pkgs
This commit is contained in:
@@ -5,17 +5,17 @@
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"cli:rollup": "rollup ./packages/cli/index.js --config ./packages/cli/rollup-cli.config.js",
|
||||
"cli:rollup": "rollup ./packages/cli/index.js --config ./packages/cli/rollup-cli.config.js ",
|
||||
"io:rollup": "rollup ./packages/io/index.js --config ./packages/io/rollup-cli.config.js",
|
||||
"core:rollup": "rollup ./packages/core/index.js --config ./packages/core/rollup-cli.config.js",
|
||||
"provider:rollup": "rollup ./packages/provider/index.js --config ./packages/provider/rollup-cli.config.js",
|
||||
"database:rollup": "rollup ./packages/database/index.js --config ./packages/database/rollup-cli.config.js",
|
||||
"all:rollup": "rollup ./packages/index.js --config ./packages/rollup-cli.config.js",
|
||||
"format:check": "prettier --check ./packages",
|
||||
"format:write": "prettier --write ./packages",
|
||||
"lint:check": "eslint ./packages",
|
||||
"lint:fix": "eslint --fix ./packages",
|
||||
"build": "npm run all:rollup",
|
||||
"build": "yarn run cli:rollup && yarn run io:rollup && yarn run core:rollup && yarn run provider:rollup && yarn run database:rollup",
|
||||
"link.dist": "",
|
||||
"test.unit": "node ./node_modules/uvu/bin.js packages test",
|
||||
"test.e2e": "node ./node_modules/uvu/bin.js __tests__ test",
|
||||
"test.coverage": "node ./node_modules/c8/bin/c8.js --check-coverage --lines=90 npm run test.unit",
|
||||
@@ -48,6 +48,9 @@
|
||||
"repository": "https://github.com/leifermendez/bot-whatsapp",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-json": "^5.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-replace": "^5.0.1",
|
||||
"whatsapp-web.js": "^1.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -9,5 +9,8 @@
|
||||
"cross-spawn": "^7.0.3",
|
||||
"detect-package-manager": "^2.0.1",
|
||||
"kleur": "^4.1.5"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"./lib/cli/bundle.cli.cjs"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'cli', 'bundle.cli.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: 'index.js',
|
||||
output: {
|
||||
file: 'lib/cli/bundle.cli.cjs',
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
plugins: [commonjs(), nodeResolve()],
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"format:write": "prettier --write .",
|
||||
"lint:check": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test:core": "node ../../node_modules/uvu/bin.js tests",
|
||||
"build:core": "npm run format:write && npm run lint:fix && npm run io:rollup"
|
||||
"build:core": "yarn run format:write && yarn run lint:fix && yarn run io:rollup"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'core', 'bundle.core.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: 'index.js',
|
||||
output: {
|
||||
file: 'lib/core/bundle.core.cjs',
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
plugins: [commonjs(), nodeResolve()],
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'database', 'bundle.database.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: 'index.js',
|
||||
output: {
|
||||
file: 'lib/database/bundle.database.cjs',
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
plugins: [commonjs(), nodeResolve()],
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'io', 'bundle.io.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: 'index.js',
|
||||
output: {
|
||||
file: 'lib/io/bundle.io.cjs',
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
plugins: [commonjs(), nodeResolve()],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { Client, LocalAuth } = require('whatsapp-web.js')
|
||||
const { ProviderClass } = require('@bot-whatsapp/core')
|
||||
|
||||
const { cleanNumber, generateImage } = require('./utils')
|
||||
const { cleanNumber, generateImage, isValidNumber } = require('./utils')
|
||||
|
||||
class WebWhatsappProvider extends ProviderClass {
|
||||
vendor
|
||||
@@ -60,7 +60,17 @@ class WebWhatsappProvider extends ProviderClass {
|
||||
},
|
||||
{
|
||||
event: 'message',
|
||||
func: (payload) => this.emit('message', payload),
|
||||
func: (payload) => {
|
||||
if (payload.from === 'status@broadcast') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isValidNumber(payload.from)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.emit('message', payload)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -12,4 +12,10 @@ const generateImage = (base64) => {
|
||||
qr_svg.pipe(createWriteStream(`${process.cwd()}/qr.svg`))
|
||||
}
|
||||
|
||||
module.exports = { cleanNumber, generateImage }
|
||||
const isValidNumber = (rawNumber) => {
|
||||
const regexGroup = /\@g.us\b/gm
|
||||
const exist = rawNumber.match(regexGroup)
|
||||
return !exist
|
||||
}
|
||||
|
||||
module.exports = { cleanNumber, generateImage, isValidNumber }
|
||||
|
||||
11
packages/provider/index.js
Normal file
11
packages/provider/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const MockProvider = require('./adapters/mock')
|
||||
const WebWhatsappProvider = require('./adapters/web-whatsapp')
|
||||
const TwilioProvider = require('./adapters/twilio')
|
||||
const { ProviderClass } = require('@bot-whatsapp/core')
|
||||
|
||||
module.exports = {
|
||||
WebWhatsappProvider,
|
||||
MockProvider,
|
||||
TwilioProvider,
|
||||
ProviderClass,
|
||||
}
|
||||
@@ -7,9 +7,11 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"@bot-whatsapp/core": "*",
|
||||
"qr-image": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"./lib/provider/bundle.provider.cjs"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'provider', 'bundle.provider.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: 'index.js',
|
||||
output: {
|
||||
file: 'lib/provider/bundle.provider.cjs',
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
92
yarn.lock
92
yarn.lock
@@ -102,6 +102,33 @@
|
||||
is-reference "1.2.1"
|
||||
magic-string "^0.26.4"
|
||||
|
||||
"@rollup/plugin-json@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-5.0.1.tgz#d5cd67cc83ede42967447dfabbe1be45a091f5b7"
|
||||
integrity sha512-QCwhZZLvM8nRcTHyR1vOgyTMiAnjiNj1ebD/BMRvbO1oc/z14lZH6PfxXeegee2B6mky/u9fia4fxRM4TqrUaw==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
|
||||
"@rollup/plugin-node-resolve@^15.0.1":
|
||||
version "15.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz#72be449b8e06f6367168d5b3cd5e2802e0248971"
|
||||
integrity sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
"@types/resolve" "1.20.2"
|
||||
deepmerge "^4.2.2"
|
||||
is-builtin-module "^3.2.0"
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.22.1"
|
||||
|
||||
"@rollup/plugin-replace@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.1.tgz#49a57af3e6df111a9e75dea3f3572741f4c5c83e"
|
||||
integrity sha512-Z3MfsJ4CK17BfGrZgvrcp/l6WXoKb0kokULO+zt/7bmcyayokDaQ2K3eDJcRLCTAlp5FPI4/gz9MHAsosz4Rag==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
magic-string "^0.26.4"
|
||||
|
||||
"@rollup/pluginutils@^5.0.1":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
|
||||
@@ -126,6 +153,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
|
||||
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
|
||||
|
||||
"@types/resolve@1.20.2":
|
||||
version "1.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
||||
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||
@@ -286,6 +318,11 @@ buffers@~0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
||||
integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==
|
||||
|
||||
builtin-modules@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
|
||||
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
|
||||
|
||||
c8@^7.12.0:
|
||||
version "7.12.0"
|
||||
resolved "https://registry.yarnpkg.com/c8/-/c8-7.12.0.tgz#402db1c1af4af5249153535d1c84ad70c5c96b14"
|
||||
@@ -428,6 +465,11 @@ deep-is@^0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
dequal@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||
@@ -740,6 +782,11 @@ fstream@^1.0.12:
|
||||
mkdirp ">=0.5 0"
|
||||
rimraf "2"
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
@@ -809,6 +856,13 @@ has-flag@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
html-escaper@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
@@ -863,6 +917,20 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
is-builtin-module@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0"
|
||||
integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==
|
||||
dependencies:
|
||||
builtin-modules "^3.3.0"
|
||||
|
||||
is-core-module@^2.9.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
|
||||
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
@@ -880,6 +948,11 @@ is-glob@^4.0.0, is-glob@^4.0.3:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
|
||||
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
|
||||
|
||||
is-path-inside@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||
@@ -1219,6 +1292,11 @@ path-key@^3.0.0, path-key@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
@@ -1354,6 +1432,15 @@ resolve-from@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve@^1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
||||
dependencies:
|
||||
is-core-module "^2.9.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
@@ -1488,6 +1575,11 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
tar-fs@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
|
||||
Reference in New Issue
Block a user