├── .eslintrc.json ├── .jsdoc.json ├── .github └── workflows │ └── npmPublish.yml ├── LICENSE ├── exmaples └── example.js ├── package.json ├── .gitignore ├── README.md └── src └── index.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es2021": true 6 | }, 7 | "extends": [ 8 | "standard" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": "latest" 12 | }, 13 | "ignorePatterns": [ 14 | "docs" 15 | ], 16 | "plugins": [ 17 | "jsdoc" 18 | ], 19 | "rules": { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["src", "README.md", "package.json"] 4 | }, 5 | 6 | "opts": { 7 | "encoding": "utf8", 8 | "readme": "./README.md", 9 | "destination": "docs/", 10 | "recurse": true, 11 | "verbose": true, 12 | "template": "./node_modules/clean-jsdoc-theme", 13 | "theme_opts": { 14 | "theme": "dark" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/npmPublish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 12 14 | - run: npm install 15 | - uses: JS-DevTools/npm-publish@v1 16 | with: 17 | token: ${{ secrets.NPM_TOKEN }} 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Maksim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /exmaples/example.js: -------------------------------------------------------------------------------- 1 | const { GetSMS, ServiceApiError, TimeoutError, errors } = require('getsms') 2 | 3 | const sms = new GetSMS({ 4 | key: 'bc103fa02b63f986cd102a6d2f5c', 5 | url: 'https://smshub.org/stubs/handler_api.php', 6 | service: 'smshub' 7 | }); 8 | 9 | (async () => { 10 | try { 11 | // eslint-disable-next-line camelcase 12 | const { balance_number } = await sms.getBalance() 13 | // eslint-disable-next-line camelcase 14 | if (balance_number > 0) { 15 | // Service - bd, operator - mts, country - russia (0) 16 | const { id, number } = await sms.getNumber('bd', 'mts', 0) 17 | console.log('Number ID:', id) 18 | console.log('Number:', number) 19 | 20 | // Set "message has been sent" status 21 | await sms.setStatus(1, id) 22 | 23 | // Wait for code 24 | const { code } = await sms.getCode(id) 25 | console.log('Code:', code) 26 | 27 | await sms.setStatus(6, id) // Accept, end 28 | } else console.log('No money') 29 | } catch (error) { 30 | if (error instanceof TimeoutError) { 31 | console.log('Timeout reached') 32 | } 33 | 34 | if (error instanceof ServiceApiError) { 35 | if (error.code === errors.BANNED) { 36 | console.log(`Banned! Time ${error.banTime}`) 37 | } else { 38 | console.error(error.code, error.message) 39 | } 40 | } else console.error(error) 41 | } 42 | })() 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getsms", 3 | "version": "2.1.0", 4 | "description": "SMSHub API, SMS-ACTIVATE API module", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "test": "node test", 8 | "lint": "eslint .", 9 | "jsdoc": "jsdoc --configure .jsdoc.json --verbose", 10 | "remark": "npx remark README.md --use remark-preset-lint-consistent --use remark-preset-lint-recommended" 11 | }, 12 | "dependencies": { 13 | "undici": "^5.28.3" 14 | }, 15 | "devDependencies": { 16 | "eslint": "^8.56.0", 17 | "eslint-config-standard": "^17.1.0", 18 | "eslint-plugin-import": "^2.29.1", 19 | "eslint-plugin-n": "^16.6.2", 20 | "eslint-plugin-promise": "^6.1.1", 21 | "jsdoc": "^4.0.2", 22 | "eslint-plugin-jsdoc": "^48.1.0", 23 | "clean-jsdoc-theme": "^4.2.17", 24 | "remark-cli": "^12.0.0", 25 | "remark-preset-lint-consistent": "^5.1.2", 26 | "remark-preset-lint-recommended": "^6.1.3" 27 | }, 28 | "keywords": [ 29 | "smshub", 30 | "sms", 31 | "smsapi", 32 | "sms-api", 33 | "sms-hub", 34 | "smshub-api", 35 | "sms", 36 | "gate", 37 | "smsgateway", 38 | "smsactivate", 39 | "5sim", 40 | "fivesim" 41 | ], 42 | "author": { 43 | "name": "Maxim Telegin", 44 | "email": "m_telega@mail.ru" 45 | }, 46 | "bugs": { 47 | "url": "https://github.com/Viiprogrammer/getSMS/issues" 48 | }, 49 | "homepage": "https://github.com/Viiprogrammer/getSMS#readme", 50 | "repository": { 51 | "type": "git", 52 | "url": "git+https://github.com/Viiprogrammer/getSMS.git" 53 | }, 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Docs 2 | docs 3 | # IDEA 4 | .idea 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GetSMS 2 |
7 | 8 | [](https://nodei.co/npm/getsms/) 9 | 10 | getSMS - This is [Node.js](https://nodejs.org) module that allows you to interact with the SMS services api 11 | 12 | ## Features 13 | - Promises 14 | - Supports `SMSActivate`, `SMSHub` 15 | - Using [undici](https://github.com/nodejs/undici) http client 16 | 17 | ## Docs 18 | 19 | You can find documentation [here](https://viiprogrammer.github.io/getSMS/) 20 | 21 | ## Installation 22 | > **[Node.js](https://nodejs.org/) 12.0.0 or newer is required** 23 | 24 | ### Yarn 25 | ```bash 26 | yarn add getsms 27 | ``` 28 | 29 | ### NPM 30 | ```bash 31 | npm i getsms 32 | ``` 33 | ## Errors caught 34 | 35 | Errors that can be caught with `catch()`: 36 | - `MAIL_RULE` - For buying this service number you must satisfied additional site rules **(smshub, undocumented)** 37 | - `BAD_KEY` - Invalid api key 38 | - `ERROR_SQL` - Server database error 39 | - `BAD_ACTION` - Bad request data 40 | - `WRONG_SERVICE` - Wrong service identifier 41 | - `BAD_SERVICE` - Wrong service name', 42 | - `NO_ACTIVATION` - Activation not found. 43 | - `NO_BALANCE` - No balance 44 | - `NO_NUMBERS` - No numbers 45 | - `WRONG_ACTIVATION_ID` - Wrong activation id 46 | - `WRONG_EXCEPTION_PHONE` - Wrong exception phone 47 | - `NO_BALANCE_FORWARD` - No balance for forward 48 | - `NOT_AVAILABLE` - Multiservice is not available for selected country 49 | - `BAD_FORWARD` - Incorrect forward 50 | - `WRONG_ADDITIONAL_SERVICE` - Wrong additional service 51 | - `WRONG_SECURITY` - WRONG_SECURITY error 52 | - `REPEAT_ADDITIONAL_SERVICE` - Repeat additional service error 53 | - `BANNED:YYYY-m-d H-i-s` - Account banned 54 | 55 | ***if the ban code is `BANNED:YYYY-m-d H-i-s` then the error object contains the properties `banTime`, `banDate`, `banTimestamp`*** 56 | - `banTime` - `YYYY-m-d H-i-s` (for example `2020-12-31 23-59-59`) 57 | - `banTimestamp` - Unixtime 58 | - `banDate` - JavaScript `new Date()` Object 59 | ## Usage example 60 | 61 | ```javascript 62 | const { GetSMS, ServiceApiError, TimeoutError, errors } = require('getsms') 63 | 64 | const sms = new GetSMS({ 65 | key: 'bc103fa02b63f986cd102a6d2f5c', 66 | url: 'https://smshub.org/stubs/handler_api.php', 67 | service: 'smshub' 68 | }); 69 | 70 | (async () => { 71 | try { 72 | // eslint-disable-next-line camelcase 73 | const { balance_number } = await sms.getBalance() 74 | // eslint-disable-next-line camelcase 75 | if (balance_number > 0) { 76 | // Service - bd, operator - mts, country - russia (0) 77 | const { id, number } = await sms.getNumber('bd', 'mts', 0) 78 | console.log('Number ID:', id) 79 | console.log('Number:', number) 80 | 81 | // Set "message has been sent" status 82 | await sms.setStatus(1, id) 83 | 84 | // Wait for code 85 | const { code } = await sms.getCode(id) 86 | console.log('Code:', code) 87 | 88 | await sms.setStatus(6, id) // Accept, end 89 | } else console.log('No money') 90 | } catch (error) { 91 | if (error instanceof TimeoutError) { 92 | console.log('Timeout reached') 93 | } 94 | 95 | if (error instanceof ServiceApiError) { 96 | if (error.code === errors.BANNED) { 97 | console.log(`Banned! Time ${error.banTime}`) 98 | } else { 99 | console.error(error.code, error.message) 100 | } 101 | } else console.error(error) 102 | } 103 | })() 104 | ``` 105 | 106 | ## License 107 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FViiprogrammer%2FgetSMS?ref=badge_large) 108 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { request } = require('undici') 2 | 3 | const errorsList = { 4 | BANNED: 'Account banned', 5 | MAIL_RULE: 'For buying this service number you must satisfied additional site rules', 6 | NO_KEY: 'Key is empty', 7 | BAD_KEY: 'Invalid api key', 8 | ERROR_SQL: 'Server database error', 9 | BAD_ACTION: 'Bad request data', 10 | WRONG_SERVICE: 'Wrong service identifier', 11 | BAD_SERVICE: 'Wrong service name', 12 | NO_ACTIVATION: 'Activation not found.', 13 | NO_BALANCE: 'No balance', 14 | NO_NUMBERS: 'No numbers', 15 | WRONG_ACTIVATION_ID: 'Wrong activation id', 16 | WRONG_EXCEPTION_PHONE: 'Wrong exception phone', 17 | NO_BALANCE_FORWARD: 'No balance for forward', 18 | NOT_AVAILABLE: 'Multiservice is not available for selected country', 19 | BAD_FORWARD: 'Incorrect forward', 20 | WRONG_ADDITIONAL_SERVICE: 'Wrong additional service', 21 | WRONG_SECURITY: 'WRONG_SECURITY error', 22 | REPEAT_ADDITIONAL_SERVICE: 'Repeat additional service error' 23 | } 24 | 25 | const supportedMethods = { 26 | smshub: [ 27 | 'getNumbersStatus', 'getBalance', 'getNumber', 'setStatus', 'getStatus', 'getPrices', 'getCode', 28 | 'setMaxPrice', 'getCurrentActivations', 'getListOfCountriesAndOperators' 29 | ], 30 | smsactivate: [ 31 | 'getNumbersStatus', 'getBalance', 'getNumber', 'setStatus', 'getStatus', 'getPrices', 'getFullSms', 32 | 'getAdditionalService', 'getCountries', 'getQiwiRequisites', 'getCode' 33 | ] 34 | } 35 | 36 | class TimeoutError extends Error { 37 | /** 38 | * TimeoutError class constructor 39 | * @constructor 40 | * @param {string} message 41 | */ 42 | constructor (message) { 43 | super() 44 | this.message = message 45 | this.name = this.constructor.name 46 | } 47 | } 48 | 49 | class ServiceApiError extends Error { 50 | /** 51 | * ServiceApiError class constructor 52 | * @constructor 53 | * @param {string} code Sms service error string 54 | */ 55 | constructor (code) { 56 | super() 57 | this.message = errorsList[code] 58 | 59 | if (code.indexOf('BANNED:') === 0) { 60 | this.serverResponse = code 61 | this.code = 'BANNED' 62 | const datetime = code.split(':').pop() 63 | 64 | let [date, time] = datetime.split(/\s/) 65 | time = time.split('-').join(':') 66 | date = date.split('-').join('/') 67 | 68 | const obj = new Date(`${date} ${time}`) 69 | 70 | this.banTime = datetime 71 | this.banTimestamp = obj.getTime() / 1000 72 | this.banDate = obj 73 | } else { 74 | this.code = code 75 | } 76 | 77 | this.name = this.constructor.name 78 | } 79 | 80 | /** 81 | * Method for checking response includes an error from errors list 82 | * @method 83 | * @static 84 | * @private 85 | * 86 | * @param {string} response Http request response string 87 | * @return {boolean} 88 | */ 89 | static _check (response) { 90 | return Object.keys(errorsList).includes(response) && (response.indexOf('BANNED:') !== 0) 91 | } 92 | } 93 | 94 | class GetSMS { 95 | /** 96 | * @typedef {object} InitProperties 97 | * @property {string} key Service API key 98 | * @property {string} url Service endpoint url 99 | * @property {string} [secondUrl=https://smshub.org/api.php] Used for method `getListOfCountriesAndOperators` (only SMSHUB) 100 | * @property {'en'|'ru'} [lang=ru] Lang code, smshub can return results with russian or english (Only smshub) 101 | * @property {'smshub'|'smsactivate'} service Service name 102 | * @property {number} [interval=2000] Polling interval of getCode method 103 | */ 104 | 105 | /** 106 | * GetSMS class constructor 107 | * @constructor 108 | * 109 | * @param {InitProperties} options Options object 110 | * @throws {Error} 111 | * @returns {GetSMS} 112 | */ 113 | constructor ({ key, url, secondUrl = 'https://smshub.org/api.php', service, lang = 'ru', interval = 2000 }) { 114 | if (!key || !url || !service) { 115 | throw new Error('Missing argument(s)') 116 | } 117 | 118 | if (!['smshub', 'smsactivate'].includes(service)) { 119 | throw new Error('Invalid service name') 120 | } 121 | 122 | this._key = key 123 | this._url = url 124 | this._lang = lang 125 | this._secondUrl = secondUrl 126 | this._service = service 127 | this._interval = interval 128 | 129 | return new Proxy(this, { 130 | get (target, prop) { 131 | if (typeof target[prop] !== 'function') { 132 | return target[prop] 133 | } 134 | 135 | return function (...args) { 136 | if (!prop.startsWith('_') && !supportedMethods[target._service].includes(prop)) { 137 | throw new Error(`Method "${prop}" not supported by ${target._service}`) 138 | } 139 | 140 | return target[prop].apply(this, args) 141 | } 142 | } 143 | }) 144 | } 145 | 146 | /** 147 | * Method for sending API requests 148 | * @method 149 | * @private 150 | * 151 | * @param {object} qs Query string params 152 | * @param {string} method Request METHOD 153 | * @param {object} [form] Form object 154 | * @param {boolean} [second=false] Use second API endpoint 155 | * @throws {Error|ServiceApiError} 156 | * @returns {Promise