diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..33118cf --- /dev/null +++ b/.drone.yml @@ -0,0 +1,152 @@ +kind: pipeline +type: docker +name: arm32 + +platform: + arch: arm + +steps: +- name: Build arm/v7 + image: registry.cuzo.dev/plugins/docker + privileged: true + settings: + repo: parrazam/crypto-reporter + auto_tag: true + auto_tag_suffix: linux-arm + mirror: https://registry.cuzo.dev + username: + from_secret: DOCKER_REGISTRY_USER + password: + from_secret: DOCKER_REGISTRY_PASSWORD + +image_pull_secrets: +- PRIVATE_DOCKER_REGISTRY + +trigger: + event: + - push + - tag + +--- +kind: pipeline +type: docker +name: amd64 + +platform: + arch: amd64 + +steps: +- name: Build amd64 + image: registry.cuzo.dev/plugins/docker + privileged: true + volumes: + - name: manifest + path: docker + settings: + repo: parrazam/crypto-reporter + auto_tag: true + auto_tag_suffix: linux-amd64 + mirror: https://registry.cuzo.dev + username: + from_secret: DOCKER_REGISTRY_USER + password: + from_secret: DOCKER_REGISTRY_PASSWORD + +image_pull_secrets: +- PRIVATE_DOCKER_REGISTRY + +trigger: + event: + - push + - tag + +--- +kind: pipeline +type: docker +name: manifest + +steps: +- name: Upload manifest + image: registry.cuzo.dev/plugins/manifest + privileged: true + volumes: + - name: manifest + path: docker + settings: + target: parrazam/crypto-reporter + template: parrazam/crypto-reporter:OS-ARCH + auto_tag: true + ignore_missing: true + username: + from_secret: DOCKER_REGISTRY_USER + password: + from_secret: DOCKER_REGISTRY_PASSWORD + platforms: + - linux/amd64 + - linux/arm + +depends_on: +- amd64 +- arm32 + +image_pull_secrets: +- PRIVATE_DOCKER_REGISTRY + +trigger: + event: + - push + - tag + +--- +kind: pipeline +type: docker +name: release +steps: +- name: release + image: registry.cuzo.dev/plugins/gitea-release + settings: + api_key: + from_secret: DRONE_API_KEY + base_url: https://git.cuzo.dev + title: + from_secret: DRONE_SEMVER + when: + ref: + - refs/tags/* + +depends_on: +- manifest + +image_pull_secrets: +- PRIVATE_DOCKER_REGISTRY + +trigger: + event: + - tag +--- +kind: pipeline +type: docker +name: Notify result + +steps: +- name: send telegram notification + image: registry.cuzo.dev/appleboy/drone-telegram + when: + status: [success, failure] + settings: + to: + from_secret: TG_USER + token: + from_secret: TG_TOKEN + +depends_on: +- manifest +- release + +image_pull_secrets: +- PRIVATE_DOCKER_REGISTRY + +trigger: + event: + - push + - tag diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f295d3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# ---> Python +# 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/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..88ccfca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3-alpine + +# Install dependencies: +COPY requirements.txt . +RUN pip install -r requirements.txt + +# Run the application: +COPY main.py . +CMD ["python", "main.py"] diff --git a/README.md b/README.md index cc83cdb..c7aee98 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ # crypto-reporter + +[![Build Status](https://drone.cuzo.dev/api/badges/Parra/crypto-reporter/status.svg)](https://drone.cuzo.dev/Parra/crypto-reporter) + +Obtiene y publica precios de criptomonedas. + +## Instalación y ejecución desde CLI + +Descargar el repositorio en una nueva carpeta: + +````shell +git clone https://git.cuzo.dev/Parra/crypto-reporter.git +```` + +Copiar el fichero `env_file` a `.env` y abrirlo con un editor de textos y editarlo +con la información del servidor MQTT, así como editar los tópicos que se quieran usar +y el tiempo de refresco. + +Dar permisos de ejecución al fichero principal con `chmod +x main.py` y ejecutar con `./main.py`. + +## Instalación y ejecución desde Docker + +Crear una carpeta (por ejemplo `crypto-reporter`) y acceder a ella. + +Crear un fichero `.env` con el contenido del fichero `env_file` de este repositorio y +editarlo con la información del servidor MQTT. + +Crear un fichero `docker-compose.yml` como el siguiente: +````yml +version: '3' + +services: + crypto-reporter: + image: parrazam/crypto-reporter + container_name: crypto-reporter + restart: unless-stopped + volumes: + - ./.env:/.env:ro +```` + +Ejecutar con el comando `docker compose up -d`. + +## Menciones + +La API se extrae de Coingecko y se puede consultar aquí: https://www.coingecko.com/en/api/documentation \ No newline at end of file diff --git a/env_file b/env_file new file mode 100644 index 0000000..78583ec --- /dev/null +++ b/env_file @@ -0,0 +1,21 @@ +# Log configuration +LOG_LEVEL=INFO + +# Crypto configuration +CRYPTOS_TO_PUBLISH=bitcoin,ethereum +CURRENCIES=eur,usd + +# Broker configuration +MQTT_SERVER= +MQTT_PORT= +MQTT_USER= +MQTT_PASSWORD= +MQTT_CLIENT_ID= + +# Topic configuration +MQTT_TOPIC_PREFIX= + +MQTT_TOPIC_STATE=${MQTT_TOPIC_PREFIX}/state +MQTT_TOPIC_DATA=${MQTT_TOPIC_PREFIX}/data + +MQTT_PUBLISH_DELAY=60 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..d3a5c5e --- /dev/null +++ b/main.py @@ -0,0 +1,89 @@ +# !/usr/bin/env python3 + +"""Crypto reporter""" + +import json +import logging +import os +import requests +import time + +from dotenv import load_dotenv +import paho.mqtt.client as mqtt + + +load_dotenv() # Cargamos las variables de entorno necesarias + +LOG_LEVEL = os.getenv('LOG_LEVEL', logging.INFO) + +logging.basicConfig(level=LOG_LEVEL, format='[%(levelname)s] %(asctime)s - %(message)s') + +BASE_API = 'https://api.coingecko.com/api/v3' + +MQTT_TOPIC_DATA = os.getenv('MQTT_TOPIC_DATA', 'crypto-reporter/data') +MQTT_TOPIC_STATE = os.getenv('MQTT_TOPIC_STATE', 'crypto-reporter/state') + +MQTT_PUBLISH_DELAY = int(os.getenv('MQTT_PUBLISH_DELAY', 60)) +MQTT_CLIENT_ID = os.getenv('MQTT_CLIENT_ID', 'crypto-reporter') + +MQTT_SERVER = os.getenv('MQTT_SERVER', 'localhost') +MQTT_PORT = int(os.getenv('MQTT_PORT', '1883')) +MQTT_USER = os.getenv('MQTT_USER', '') +MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', '') + +CRYPTOS_TO_PUBLISH = os.getenv('CRYPTOS_TO_PUBLISH', 'bitcoin') +CURRENCIES = os.getenv('CURRENCIES', 'eur') + + +def on_connect(client, userdata, flags, rc): + logging.info("Connected to MQTT server.") + client.publish(MQTT_TOPIC_STATE, 'connected', 1, True) + + +def main(): + logging.info(f'Connecting to MQTT host {MQTT_SERVER}:{MQTT_PORT}...') + mqttc = mqtt.Client(MQTT_CLIENT_ID) + mqttc.username_pw_set(MQTT_USER, MQTT_PASSWORD) + mqttc.will_set(MQTT_TOPIC_STATE, 'disconnected', 1, True) + mqttc.on_connect = on_connect + + mqttc.connect(MQTT_SERVER, MQTT_PORT, 60) + mqttc.loop_start() + last_msg_time = time.time() + + while True: + try: + x = requests.get(BASE_API + '/simple/price', + params={'ids': CRYPTOS_TO_PUBLISH, + 'vs_currencies': CURRENCIES}) + logging.debug(f'HTTP response: {x.status_code}') + publish_data(mqttc, x.text) + + delay_gap = time.time() - last_msg_time + if delay_gap < MQTT_PUBLISH_DELAY: + time.sleep(MQTT_PUBLISH_DELAY - delay_gap) + last_msg_time = time.time() + + except KeyboardInterrupt: + logging.info("exiting...") + exit(0) + except Exception: + logging.exception("something went wrong.") + exit(1) + + +def publish_data(mqttc, raw): + try: + logging.debug(f'RAW message to publish: {raw}') + data = json.loads(raw) + for crypto in data: + currencies = data[crypto] + for currency in currencies: + mqttc.publish(MQTT_TOPIC_DATA + f'/{crypto}/{currency}', data[crypto][currency], 1, True) + except AttributeError: + logging.exception("Error decoding JSON") + + +if __name__ == '__main__': + logging.info('Starting crypto broker') + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6ba395e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +paho-mqtt==1.6.1 +python-dotenv==0.21.0 +requests==2.28.1 \ No newline at end of file