commit ba6c68db7f058b1df5230b5b71fc497833c8a67b Author: Alex Date: Fri Apr 5 00:38:00 2024 +0700 added main files diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2975ab4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: Upload Python Package + +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + +# - name: Publish package +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Publish package to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98bca38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,323 @@ +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### PythonVanilla template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### PyCharm+all template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8030b06 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# prosto-sms | API python wrapper + +## TODO +- tests +------ + +## Install +```shell +python -m pip install prosto_sms +``` +------ + +## Examples +### Auth by key and get profile data: +```python +import prosto_sms + +api = prosto_sms.api.API( + key="bd...5bb" +) + +print(api.methods.get_profile()) +``` + +### Auth by creds and get profile data: +```python +import prosto_sms + +api = prosto_sms.api.API( + email="me@chydo.dev", + password="password" +) + +print(api.methods.get_profile()) +``` \ No newline at end of file diff --git a/prosto_sms/__init__.py b/prosto_sms/__init__.py new file mode 100644 index 0000000..4dc7607 --- /dev/null +++ b/prosto_sms/__init__.py @@ -0,0 +1 @@ +from . import api diff --git a/prosto_sms/api.py b/prosto_sms/api.py new file mode 100644 index 0000000..943a61e --- /dev/null +++ b/prosto_sms/api.py @@ -0,0 +1,79 @@ +from typing import Optional + +import requests +from loguru import logger + +from . import exceptions +from . import methods + + +class API: + """ + Main api class + """ + + def __init__( + self, + email: str = None, + password: str = None, + key: str = None + ): + """ + Initializing the API + + :param email: account email address + :param password: account password + :param key: api key + """ + + self.email = email + self.password = password + self.key = key + self.api_url = "https://ssl.bs00.ru" + self.methods = methods.Methods(self) + + def request(self, method: str, data: dict = None) -> Optional[dict]: + """ + Method for requesting information + + :param method: required method + :param data: request parameters + :return: response data + """ + + if data is None: + data = {} + + data["format"] = "json" + if not data.get("method"): + data["method"] = method + + if self.key and not (self.email and self.password): + data["key"] = self.key + elif (self.email and self.password) and not self.key: + data["email"] = self.email + data["password"] = self.password + else: + raise exceptions.NoAuthData( + "email and password OR key is required" + ) + + try: + result = requests.get( + url=self.api_url, + params=data, + timeout=30 + ) + result.raise_for_status() + + result = result.json() + except Exception as err: + logger.exception(err) + return + + if result["response"]["msg"].get("type") == "error": + raise exceptions.ApiError( + result["response"]["msg"]["text"] + ) + + return result["data"] diff --git a/prosto_sms/exceptions.py b/prosto_sms/exceptions.py new file mode 100644 index 0000000..2385cac --- /dev/null +++ b/prosto_sms/exceptions.py @@ -0,0 +1,14 @@ +class ApiException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class NoAuthData(ApiException): + pass + + +class ApiError(ApiException): + pass \ No newline at end of file diff --git a/prosto_sms/methods.py b/prosto_sms/methods.py new file mode 100644 index 0000000..9fddb12 --- /dev/null +++ b/prosto_sms/methods.py @@ -0,0 +1,125 @@ +from . import models + + +class Methods: + def __init__(self, data): + self.request = data.request + + def push_message( + self, + *, + text: str, + phone: str, + sender_name: str, + priority: int = 2, + external_id: str = None, + route: str = None, + call_protection: int = None + ) -> models.PushMessageData: + """ + Implementation of push_msg + + :param text: message text + :param phone: receiver phone number + :param sender_name: sender name + :param priority: message priority + :param external_id: ID in your platform + :param route: message delivery route + :param call_protection: call waiting time + :return: result of calling the method + """ + + return models.PushMessageData( + **self.request( + method="push_msg", + data={ + "text": text, + "phone": phone, + "sender_name": sender_name, + "priority": priority, + "external_id": external_id, + "route": route, + "call_protection": call_protection + } + ) + ) + + + def get_message_report( + self, + *, + id: int + ) -> models.GetMessageReportData: + """ + Implementation of + + :param id: + :return: result of calling the method + """ + + return models.GetMessageReportData( + **self.request( + method="get_msg_report", + data={ + "id": id + } + ) + ) + + def get_profile(self) -> models.GetProfileData: + """ + Implementation of + + :return: result of calling the method + """ + + return models.GetProfileData( + **self.request( + method="get_profile" + ) + ) + + def get_prices( + self, + *, + group_directions: int = None + ) -> models.GetPricesData: + """ + Implementation of + + :param group_directions: + :return: result of calling the method + """ + + return models.GetPricesData( + **self.request( + method="get_prices", + data={ + "group_directions": group_directions + } + ) + ) + + def wait_call( + self, + *, + phone: str, + call_protection: int + ) -> models.WaitCallData: + """ + Implementation of + + :param phone: + :param call_protection: + :return: result of calling the method + """ + + return models.WaitCallData( + **self.request( + method="wait_call", + data={ + "phone": phone, + "call_protection": call_protection + } + ) + ) diff --git a/prosto_sms/models.py b/prosto_sms/models.py new file mode 100644 index 0000000..71ff90f --- /dev/null +++ b/prosto_sms/models.py @@ -0,0 +1,69 @@ +from typing import Optional + +from pydantic import BaseModel + + +class GetPricesData(BaseModel): + group_directions: str + + +class GetMessageReportData(BaseModel): + id: int + sender_name: str + text: str + phone: str + type: int + n_raw_sms: int + start_time: str # TODO: в будущем datetime pls + last_update: str # TODO: в будущем datetime pls + id_tarifs_group: int + state: int + state_text: str + credits: float + + +class WaitCallData(BaseModel): + call_to_number: str + id_call: str + waiting_call_from: str + + +class GetProfileData(BaseModel): + id: int + email: str + first_name: str + last_name: str + credits: float + credits_used: float + credits_name: str + currency: str + sender_name: str + referral_id: Optional[int] + + +class PushMessageData(BaseModel): + id: int + credits: float + n_raw_sms: int + sender_name: str + call_to_number: Optional[str] + + +class Message(BaseModel): + err_code: str + text: str + type: str + + +class GetProfileAnswer(BaseModel): + msg: Optional[Message] + data: Optional[GetProfileData] + + +class Answer(BaseModel): + msg: Optional[Message] + data: Optional[dict] + + +class BaseAnswer(BaseModel): + response: Answer diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eff5d97 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests +pydantic +loguru diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a30afd9 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + +with open('requirements.txt') as f: + requirements = list(map(lambda x: x.strip(), f.readlines())) + +setup( + name='prosto-sms', + version='0.0.1', + + author='Alex Dennitsev', + author_email='me@chydo.dev', + + url="", + description='ale', + + packages=['prosto_sms'], + install_requires=requirements, + + classifiers=[ + 'Environment :: Web Environment', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + ] +)