Aquí volvemos otra vez para añadir un poco de vidilla a ésto de hacer cositas con python.
En este caso vengo con una versión lite de una automatización que estoy haciendo en mi raspberry pi donde tengo muchísismos procesos ejecutándose todo el día.
Una mini aplicación que funciona con mastodon con el que se pueden hacer verdaderas virguerías.
La idea de ésta aplicación será conectarse a un servidor de mastodon (con usuario y contraseña), comprobar el número de posts que se han publicado y si superan un máximo que nosotros le hayamos indicado borre los más viejos hasta que se quede el número máximo.
También comprobará todas las notificaciones (interacciones con los posts de otros usuarios) y a cada usuario que haya interactuado con algúno de los posts comprobará si lo sigue y le seguirá automáticamente.
Lo primero será instalar la librería de mastodon.
pip install Mastodon.py
Una vez instalado tendremos que tener en cuenta que el código está adaptado para mi equipo por lo que habría que crear en %appdata% una serie de carpetas y añadir ahí un icono para que se vea en el area de notificación. Lo llamaremos mastodon.ico y así cuando se ejecute lo veríamos
De esta manera podremos parar la ejecución fácilmente 🙂
Y aquí dejo el código con algunas anotaciones que he añadido para hacerlo entendible. Espero que os sea de utilidad.
#!/usr/bin/python # -*- coding: utf-8 -*- import sys #Añadimos donde están el repositorio de código para cuando se ejecute en linux (en mi caso es necesario por la configuracion de la raspberry pi) sys.path.append("/home/pi/python/source/") import os import platform import time import re from threading import Thread from tkinter import * from pystray import MenuItem as item import pystray from PIL import Image import shutil import requests from mastodon import Mastodon from os.path import exists """--------------------------------------------------------------------------------------------- Funcion asincrona para la pantalla grafica mientras se ejecuta el bot ---------------------------------------------------------------------------------------------""" class AsyncApp(Thread): def __init__(self, pathIcon): super().__init__() self.pathIcon = pathIcon def run(self): myApp(pathIcon) """--------------------------------------------------------------------------------------------- Clase de la aplicacion grafica ---------------------------------------------------------------------------------------------""" class myApp: #Crearemos una aplicación simple que lo único que haga será mostar un icono en el area de notificaciones y si pulsamos boton derecho saldrá #un menú con una única opción que será Salir que parará toda la ejecución def __init__(self, pathIcon): self.win=Tk() self.win.title("Mastodon APP") self.win.geometry("700x350") self.win.withdraw() image=Image.open(pathIcon) menu=( item('Salir', self.quit_window), ) icon=pystray.Icon("name", image, "Mastodon APP", menu) icon.run() self.win.mainloop() def quit_window(self, icon, item): icon.stop() self.win.destroy() os._exit(0) sys.exit() #Este está puesto por si acaso no funcionase el exit """------------------------------------------------------------------------------------------------------------------- CLASE DE MASTODON PARA INTERACTUAR "-------------------------------------------------------------------------------------------------------------------""" class mastodonUtil: def __init__(self, server, account, password, mail): if platform.system() == "Windows": #Si se está ejecutando en windows obtiene la ruta en la que se guardará el fichero secret con la sesión de mastodon secretPath = f"{os.environ['APPDATA']}/automatics/mastodon/data" else: #En caso de que sea linux el entorno se pone la ruta en la que se guardará el fichero secretPath = "/home/pi/python/configs" if not os.path.exists(secretPath): os.makedirs(secretPath) secret = secretPath + "/{0}_{1}.secret".format(str(server).replace("https://","").replace("http://","").replace(".",""), account) file_exists = exists(secret) #pytooterapp if file_exists == False: Mastodon.create_app( 'SkeithAutomaticApp', #Nombre de la aplicación que se verá cuando se postee en mastodono api_base_url=server, to_file=secret ) mastodon = Mastodon( client_id=secret, api_base_url=server ) mastodon.log_in( mail, password, to_file=secret ) self.mastodon = Mastodon( access_token=secret, api_base_url=server ) #Función que obtiene las notificaciones del usuario (si han rebloqueado algo, dado a me gusta, recibido algún mensaje...etc) def getNotificaciones(self): return self.mastodon.notifications() #Función que borra un toot publicado indicándoloe el id del post (status para mastodon) def deleteStatus(self, idStatus): return self.mastodon.status_delete(id=idStatus) #Función que obtiene los datos de relación con un usuario, nos dirá si nos sigue, si le seguimos...etc def getStatusRelationship(self, id): return self.mastodon.account_relationships(id) def followAccount(self, id): return self.mastodon.account_follow(id) #Función que borra todas las notificaciones (no es marcarlas como leidas, es hacer que desaparezcan) def clearNotifications(self): return self.mastodon.notifications_clear() #Función que otbiene todos los posts publicados por el usuario con el que nos hemos logueado def getStatuses(self): cuenta = self.mastodon.me() salir = False maxId = None timeLineAll = [] cuenta = self.mastodon.me() while salir == False: if maxId != None: timeLine = self.mastodon.account_statuses(id=cuenta.id, limit=100, max_id=maxId) else: timeLine = self.mastodon.account_statuses(id=cuenta.id, limit=100) for status in timeLine: if status.account.id == cuenta.id: timeLineAll.append(status) try: maxId = timeLine._pagination_next['max_id'] except: salir = True return timeLineAll """--------------------------------------------------------------------------------------------------------------------------- FUNCIONES PARA EL TRATAMIENTO DE FICHEROS INTERNOS ---------------------------------------------------------------------------------------------------------------------------""" #Función que obtiene ficheros del almacenamiento interno def getFiles(filePath, fileName): pathNewFile = filePath + fileName basefilePath = resolver_ruta(fileName) if os.path.isfile(pathNewFile): """ Ya existe el fichero así que no se hará nada""" else: shutil.copy(basefilePath, pathNewFile) return pathNewFile #Obtiene la ruta del fichero interno def resolver_ruta(ruta_relativa): if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, ruta_relativa) return os.path.join(os.path.abspath('.'), ruta_relativa) """--------------------------------------------------------------------------------------------------------------------------- INICIO DEL PROGRAMA ---------------------------------------------------------------------------------------------------------------------------""" if platform.system() == "Windows": #Si se está ejecutando en windows obtiene el icono de la aplicacion (que se verá en el area de notificación en %appdata%), previamente la tendremos que haber colocado ahí #Se puede añadir al código para que lo haga automáticamente pero para no liarlo mucho lo he quitado y digamos que si estará el icono en dicha carpeta. dir_path_config = f"{os.environ['APPDATA']}/automatics/mastodon/data" pathIcon = f"{dir_path_config}/mastodon.ico" else: #En caso de que sea linux el entorno se pone la ruta en la que está el icono pathIcon = "/home/pi/python/imagenes/mastodon.ico" CLEANR = re.compile('<.*?>') #Esta expresión regular la usarmos para limpiar el texto de los posts/status porque vienen en html MAX_POSTS = 1500 #Número máximo de posts que vamos a dejar que haya publicados con la cuenta """--------------------------------------------------------------------------------------------------------------------------- INICIAMOS LA APLICACION EN EL ICONO DE NOTIFICACIONES ---------------------------------------------------------------------------------------------------------------------------""" #Se inicia la aplicación en el area de notificación (de esta forma sabremos que se está ejecutando y podremos pararla desde ahi) #La aplicación se lanzará de manera asíncrona (en hilo) y se quedará paralelamente ejecutándose launchApp = AsyncApp(pathIcon) launchApp.start() while True: #Un loop que nunca estará a false por lo que se ejecutará eternamente (a no ser que mandemos desde el area de notificaciones que pare) """------------------------------------------------------------------------------------------------------------------ INICIAR SESION EN EL SERVIDOR DE MASTODON ------------------------------------------------------------------------------------------------------------------""" sesion = mastodonUtil("url del servidor Mastodon", "Usuario", "Contraseña", 'Correo con el que se registró en el servidor') """------------------------------------------------------------------------------------------------------------------ TRATAMIENTO DE LOS POSTS CUANDO HAY MAS DE MAX_POSTS CON INTERACCION QUE BORRAMOS LOS MÁS VIEJOS ------------------------------------------------------------------------------------------------------------------""" timeLine = sesion.getStatuses() if len(timeLine) > MAX_POSTS: timeLineReverse = timeLine[::-1] #Invertimos el resultado para que estén los más viejos primero para poder ir borrando siempre de antiguo a nuevo postsRestantes = len(timeLineReverse) for post in timeLineReverse: if postsRestantes <= MAX_POSTS: break estadoBorrado = sesion.deleteStatus(post.id) contenidoStatus = re.sub(CLEANR, '', post.content) #Limpiamos el contendido con la expresión para mostarlo correctament print(f"Eliminado - {contenidoStatus}") time.sleep(60) #Esperamos un tiempo entre borrado y borrado para no saturar el servidor postsRestantes = postsRestantes - 1 print(f"Fin de borrar posts: Posts antiguos cuando es más de {MAX_POSTS}: | Posts actuales {postsRestantes}") """------------------------------------------------------------------------------------------------------------------ TRATAMIENTO DE LAS NOTIFICACIONES INSERTANDOLAS EN LA TABLA Y SE SIGUEL AL USUARIO QUE HA INTERACTUADO ------------------------------------------------------------------------------------------------------------------""" notificaciones = sesion.getNotificaciones() sesion.clearNotifications() for notificacion in notificaciones: #Aquí estamos sacando información de más, es un ejemplo de cosas que podemos obtener de mastodono idStatus = notificacion.status.id contentStatus = re.sub(CLEANR, '', notificacion.status.content) #Limpiamos el contendido con la expresión para mostarlo correctament usuarioNotificacion = notificacion.account.username cuentaNotificacion = notificacion.account.id estado = "" tipoNotificacion = notificacion.type idCuentaAccion = notificacion.account.id estadoRelacion = sesion.getStatusRelationship(idCuentaAccion) for estado in estadoRelacion: if estado.following == False: seguir = sesion.followAccount(idCuentaAccion) usuario = str(notificacion.account.username).replace("_"," ") tipo = str(notificacion.type) print(f"Seguimos a {usuario} porque ha ineteractuado {tipo}") print("Fin de autoseguir por interacciones con los posts") """------------------------------------------------------------------------------------------------------------------ TIEMPO DE ESPERA ENTRE INTERACCIONES ------------------------------------------------------------------------------------------------------------------""" time.sleep(3600) #Hemos puesto un tiempo de espera para que se ejecute en loop cada 1h todo el proceso