From 927c2816315c729d6d77e92c1d70916d48d30b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20S=C3=A1nchez=20Pizarro?= Date: Wed, 2 Aug 2023 10:06:48 +0200 Subject: [PATCH] =?UTF-8?q?separaci=C3=B3n=20de=20codigo=20para=20facil=20?= =?UTF-8?q?edici=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Omar Sánchez Pizarro --- app/__init__.py | 0 app/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 146 bytes app/__pycache__/arg_parser.cpython-310.pyc | Bin 0 -> 1795 bytes app/__pycache__/config_parser.cpython-310.pyc | Bin 0 -> 1130 bytes .../google_calendar.cpython-310.pyc | Bin 0 -> 4147 bytes app/__pycache__/telegram_bot.cpython-310.pyc | Bin 0 -> 835 bytes .../timenet_manager.cpython-310.pyc | Bin 0 -> 4201 bytes app/arg_parser.py | 36 +++ app/config_parser.py | 32 +++ app/google_calendar.py | 104 +++++++ app/telegram_bot.py | 15 + app/timenet_manager.py | 124 ++++++++ autoficher.py | 266 ------------------ googleoauth_grant.json | 1 + main.py | 21 ++ 15 files changed, 333 insertions(+), 266 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-310.pyc create mode 100644 app/__pycache__/arg_parser.cpython-310.pyc create mode 100644 app/__pycache__/config_parser.cpython-310.pyc create mode 100644 app/__pycache__/google_calendar.cpython-310.pyc create mode 100644 app/__pycache__/telegram_bot.cpython-310.pyc create mode 100644 app/__pycache__/timenet_manager.cpython-310.pyc create mode 100644 app/arg_parser.py create mode 100644 app/config_parser.py create mode 100644 app/google_calendar.py create mode 100644 app/telegram_bot.py create mode 100644 app/timenet_manager.py delete mode 100755 autoficher.py create mode 100644 googleoauth_grant.json create mode 100755 main.py diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-310.pyc b/app/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..726c5b2f9c8e04a26c6d25f5711e84c32dfbe773 GIT binary patch literal 146 zcmd1j<>g`k0!8(cDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6vMKO;XkRX;yB zu}D9lGC3o$C^w)eKPxr4q*y<(v?M<*GdUx*NI$WlKtDb{GcU6wK3=b&@)n0pZhlH> PPO2TqxMC(C!NLFlWrrZ5 literal 0 HcmV?d00001 diff --git a/app/__pycache__/arg_parser.cpython-310.pyc b/app/__pycache__/arg_parser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd617fc8a6343d55c05da053e17b9b9c687abba0 GIT binary patch literal 1795 zcmZ`4O>f*pbjJ4XI-5&)GoS89V zt#E3u`47plN5rky-1rH;A|drJZ~)%3yXjVfS#2Z;x`%^d`ip*9;jz=tkw zuQ_Gd0uNd**su+4=-{_gpXgT$5A|qIXe0G*%+Y+dD5ehXZ*ZGG5m#KH~tx8h^3-XuyZQ0Eosi&C9D;pJF02t4yHw(_|#9(up#q6Xs!o2 z5g_ga_mFrh(gei7(R35qLr|{G$CAg1%0wzn>4y8ZTnxi~bPqO}$Rb4TCnA#d^W~C0GC8mmtNUy54_z+<@OUX4UP&KBpD>J(9|3F;PAZ_86s9e-6NEoIFvzXgZp=} zqwj4az|McYip~eE%EOdR{Z6WdiT3k( zg6D&(H3MWC{KsGz5N|v4A=UDMPgk3XiHxGE8$}ZZMM`)tijE4AHj>(R= y?5)!pqjG4vRGK!XhrnkZ_t3Y$+H2Jk91c{Z8vifz%D>+Pu}uwP=pdi<-G2eR{K+W* literal 0 HcmV?d00001 diff --git a/app/__pycache__/config_parser.cpython-310.pyc b/app/__pycache__/config_parser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91bb3901648d151310841e3e608ed2f6ceefb062 GIT binary patch literal 1130 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/telegram_bot.cpython-310.pyc b/app/__pycache__/telegram_bot.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0339a9cdee2e465407e8aa7cc13c8ee53dcd16a GIT binary patch literal 835 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 jtSB4FjE{DD8M~=-DO1S@6e400V-By6Ih<9QO o -bt") + exit(20) + + def parse(self): + self.args = self.parser.parse_args() + self.check() + + return self.args diff --git a/app/config_parser.py b/app/config_parser.py new file mode 100644 index 0000000..68fb14b --- /dev/null +++ b/app/config_parser.py @@ -0,0 +1,32 @@ +import json, os +from app import arg_parser + +args = arg_parser.ArgParser().parse() + + +class ConfigParser: + config = None + + def __init__(self): + self.loadConfig() + + def loadConfig(self): + ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + path = os.path.join(ROOT_DIR, '../config.json') + + if args.config: + path = args.config + + with open(path, 'r') as f: + self.config = json.load(f) + + if args.geoLatitude: + self.config['geo']['latitude'] = args.geoLatitude + if args.geoLongitude: + self.config['geo']['longitude'] = args.geoLongitude + if args.user: + self.config['user'] = args.user + if args.geoAccuracy: + self.config['geo']['accuracy'] = args.geoAccuracy + + return self.config \ No newline at end of file diff --git a/app/google_calendar.py b/app/google_calendar.py new file mode 100644 index 0000000..8b0cf6a --- /dev/null +++ b/app/google_calendar.py @@ -0,0 +1,104 @@ +import google.oauth2.credentials + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError + +from datetime import datetime +from dateutil.relativedelta import relativedelta + +import os + + +class GoogleCalendar: + def __init__(self): + self.creds = None + self.service = None + self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] + self.authfile = os.path.join(os.path.dirname(__file__), '../googleoauth.json') + self.grantfile = os.path.join(os.path.dirname(__file__), '../googleoauth_grant.json') + + if os.path.exists(self.authfile): + try: + self.creds = Credentials.from_authorized_user_file(self.authfile, self.SCOPES) + except ValueError as error: + print(error) + + self.checkAuth() + self.buildService() + + def serverAuth(self): + flow = InstalledAppFlow.from_client_secrets_file(self.grantfile, self.SCOPES) + self.creds = flow.run_local_server(port=0) + + def saveAuth(self): + with open(self.authfile, 'w') as token: + token.write(self.creds.to_json()) + + def refreshAuth(self): + self.creds.refresh(Request()) + + def checkAuth(self): + if not self.creds or not self.creds.valid: + if self.creds and self.creds.expired and self.creds.refresh_token: + try: + self.refreshAuth() + except google.oauth2.credentials.exceptions.RefreshError as rerror: + self.serverAuth() + else: + self.serverAuth() + + self.saveAuth() + + def buildService(self): + self.service = build('calendar', 'v3', credentials=self.creds) + + def getNow(self): + return datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z') + + def getNowAfterOneDay(self): + return (datetime.utcnow() + relativedelta(days=1)).strftime('%Y-%m-%dT00:00:00Z') + + def getEvent(self): + try: + now = self.getNow() + now_after_one_day = self.getNowAfterOneDay() + + events_result_autoficher = self.service.events().list( + calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com', + timeMin=now, + timeMax=now_after_one_day, maxResults=10, singleEvents=True, + orderBy='startTime').execute() + events_result_holiday = self.service.events().list( + calendarId='es.spain#holiday@group.v.calendar.google.com', + timeMin=now, + timeMax=now_after_one_day, + maxResults=10, singleEvents=True, + orderBy='startTime').execute() + + events = events_result_autoficher.get('items', []) + events_result_holiday.get('items', []) + + if not events: + return False + + # Prints the start and name of the next 10 events + for event in events: + start = event['start'].get('dateTime', event['start'].get('date')) + end = event['end'].get('dateTime', event['end'].get('date')) + + if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat( + end).timestamp(): + if event['organizer']['displayName'] == 'autoficher': + return 'Forzado desde autoficher - ' + event.get("summary", "Sin Título") + + if 'description' in event and ("Cataluña" in event['description'] or 'Día festivo' in event[ + 'description'] or 'Celebración\n' in event['description']) and not 'Cambio de horario' in event[ + 'summary']: + return event['summary'] + except HttpError as error: + print(f'An error occurred: {error}') + return 'Error de llama a Google Calendar' + + return False \ No newline at end of file diff --git a/app/telegram_bot.py b/app/telegram_bot.py new file mode 100644 index 0000000..f442be6 --- /dev/null +++ b/app/telegram_bot.py @@ -0,0 +1,15 @@ +import telegram +from app import config_parser + +config = config_parser.ConfigParser().loadConfig() + + +class telegramBot: + bot = None + + def __init__(self): + self.bot = telegram.Bot(token=config['telegram']['token']) + + def sendMessage(self, message): + print(message) + self.bot.sendMessage(chat_id=config['telegram']['chat_id'], text=str(message)) diff --git a/app/timenet_manager.py b/app/timenet_manager.py new file mode 100644 index 0000000..8121cc9 --- /dev/null +++ b/app/timenet_manager.py @@ -0,0 +1,124 @@ +import requests, json, pytz +from datetime import datetime +from app import arg_parser, config_parser, telegram_bot, google_calendar + +args = arg_parser.ArgParser().parse() +config = config_parser.ConfigParser().loadConfig() + + +class timenetManager: + telegram = None + + def __init__(self): + self.telegram = telegram_bot.telegramBot() + + headers = { + "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "tn-v": "3.0.9", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36" + } + + def get(self, url, data={}, headers={}): + furl = config['api_url'] + url + if url != "version": + furl = config['api_url'] + "v1/" + url + return requests.get(furl, data=data, headers=headers) + + def post(self, url, data={}, headers={}): + furl = config['api_url'] + url + if url != "version": + furl = config['api_url'] + "v1/" + url + return requests.post(furl, data=data, headers=headers) + + def sendReport(self, message): + self.telegram.sendMessage(message) + + def updateTime(self): + self.headers["tn-d"] = "\"" + datetime.now().astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%SZ") + "\"" + + def updateVersion(self): + self.headers["tn-v"] = self.getVersion() + print(self.headers) + exit(20) + + def getVersion(self): + self.updateTime() + return self.get('version', self.headers) + + def getInfo(self): + return self.get('check/info', {"guid": args.user}) + + def sendUpdate(self): + self.updateTime() + + calendar = google_calendar.GoogleCalendar() + calendar = calendar.getEvent() + if calendar: + self.sendReport("🟥🕐 Comprobación de calendario: " + calendar) + return + + typ = 0 + if args.basedtime and not args.type: + hour = datetime.now().strftime("%H") + + hIN = config['in_hours'] + + hOUT = config['out_hours'] + + if hour in hIN: + typ = 0 + elif hour in hOUT: + typ = 1 + else: + self.sendReport("No se ha fichado ni desfichado ya que estamos en horario laboral, fuerze el estado con el parametro --type <0 = Entrada, 1 = Salida>") + else: + typ = args.type + + data = { + "cp": config['user'], + "pin": args.pin, + "typ": typ, + "date": self.headers['tn-d'], + "geoLatitude": config['geo']['latitude'], + "geoLongitude": config['geo']['longitude'], + "geoError": "", + "c": 1, + "geoAccuracy": config['geo']['accuracy'] + } + + response = None + if not args.debug: + self.sendReport("####HACIENDO CHECK####") + response = self.post("check", data, self.headers) + else: + self.sendReport('Corriendo en modo debug. No se realizará ninguna acción') + exit(20) + + if response.text == "no valid worker": + self.sendReport("El trabajador no ha podido ser identificado") + exit(20) + + try: + rj = json.loads(response.text) + except: + self.sendReport("La respuesta al hacer check no es correcta... algo ha pasado :/") + exit(20) + + if response.status_code == 200: + if rj['Repeated']: + time = datetime.strptime(rj['RepeatedTime'], "%Y-%m-%dT%H:%M:%SZ").replace( + tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") + self.sendReport("🕐 Ya se ha realizado este marcaje antes a las %s" % time) + else: + time = datetime.now().astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") + + conditional_response = [{ + "text": "fichado", + "emoji": "🕐🏢🏃‍♂️🏡" + },{ + "text": "desfichado", + "emoji": "🏡🏃‍♂️🏢🕐" + }] + self.sendReport('%s Has %s correctamente a las %s' % (conditional_response[int(typ)]['emoji'], conditional_response[int(typ)]['text'], time)) + else: + self.sendReport("🟥🕐 No se ha podido fichar, la web no ha devuelto 200 OK") diff --git a/autoficher.py b/autoficher.py deleted file mode 100755 index f80d520..0000000 --- a/autoficher.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python3 -# encoding: utf-8 -# -# Automarcaje para timenet.gpisoftware.com -# usage: autoficher.py -u USER -p PIN -t TYPE [-f] [-h] -# -# Argumentos obligatorios: -# -u USER, --user USER Usuario -# -p PIN, --pin PIN Contraseña -# -t TYPE, --type TYPE Tipo marcado. 0 = Entrada, 1 = Salida (Si no se utiliza -bt) -# -# -# Creado: Omar Sánchez 04-05-2019 - -# Importamos librerias -import json -from datetime import datetime -import os -import pytz -import requests -import argparse -import telegram - -from google.auth.transport.requests import Request -from google.oauth2.credentials import Credentials -from google_auth_oauthlib.flow import InstalledAppFlow -from googleapiclient.discovery import build -from googleapiclient.errors import HttpError - -SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] - -from dateutil.relativedelta import relativedelta - -# Introducimos los argumentos obligatorios y opcionales -parser = argparse.ArgumentParser(add_help=False) -parser._action_groups.pop() - -obligatoryArgs = parser.add_argument_group("Argumentos obligatorios") -obligatoryArgs.add_argument('-p', '--pin', help="Contraseña", type=int, required=True) -obligatoryArgs.add_argument('-t', '--type', help="Tipo marcado. 0 = Entrada, 1 = Salida", type=int) - -optionalArgs = parser.add_argument_group("Argumentos opcionales") -optionalArgs.add_argument('-bt', '--basedtime', help="Hacer check o descheck según la hora del dia", - action="store_true") -optionalArgs.add_argument('-h', '--help', action="help", help="Esta ayuda") -optionalArgs.add_argument('-d', '--debug', action='store_true') - -optionalArgs.add_argument('-c', '--config', help="Rúta al archivo de configuración") -optionalArgs.add_argument('-u', '--user', help="Usuario") -optionalArgs.add_argument('-glo', '--geoLongitude', help="GEO Longitud", type=float) -optionalArgs.add_argument('-gla', '--geoLatitude', help="GEO Latitud", type=float) -optionalArgs.add_argument('-ga', '--geoAccuracy', help="GEO Accuracy", type=float) - -args = parser.parse_args() - -if args.type is None and args.basedtime is False: - print("Hay que indicar al menos un metodo de fichaje -t <0 o 1> o -bt") - exit(20) - -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) - -class AutoFicher(): - # URL de la api - headers = { - "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", - "tn-v": "3.0.9", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36" - } - config = None - - def __init__(self): - try: - self.loadConfig() - except: - self.sendReport("No se ha podido cargar el archivo de configuración") - exit(20) - - def loadConfig(self): - ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) - path = os.path.join(ROOT_DIR, 'config.json') - - if args.config: - path = args.config - - with open(path, 'r') as f: - self.config = json.load(f) - - if args.geoLatitude: - self.config['geo']['latitude'] = args.geoLatitude - if args.geoLongitude: - self.config['geo']['longitude'] = args.geoLongitude - if args.user: - self.config['user'] = args.user - if args.geoAccuracy: - self.config['geo']['accuracy'] = args.geoAccuracy - - def sendReport(self, message): - print(message) - bot = telegram.Bot(token=self.config['telegram']['token']) - bot.sendMessage(chat_id=self.config['telegram']['chat_id'], text=str(message)) - - def get(self, url, data={}, headers={}): - furl = self.config['api_url'] + url - if url != "version": - furl = self.config['api_url'] + "v1/" + url - return requests.get(furl, data=data, headers=headers) - - def post(self, url, data={}, headers={}): - furl = self.config['api_url'] + url - if url != "version": - furl = self.config['api_url'] + "v1/" + url - return requests.post(furl, data=data, headers=headers) - - def updateTime(self): - self.headers["tn-d"] = "\"" + datetime.now().astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%SZ") + "\"" - - def updateVersion(self): - self.headers["tn-v"] = self.getVersion() - - def getVersion(self): - self.updateTime() - return self.get('version', self.headers) - - def getInfo(self): - return self.get('check/info', {"guid": args.user}) - - def sendUpdate(self): - self.updateTime() - self.updateVersion() - - calendar = self.calendarCheck() - if calendar: - self.sendReport("🟥🕐 Comprobación de calendario: " + calendar) - return - - typ = 0 - if args.basedtime and not args.type: - hour = datetime.now().strftime("%H") - - hIN = self.config['in_hours'] - - hOUT = self.config['out_hours'] - - if hour in hIN: - typ = 0 - elif hour in hOUT: - typ = 1 - else: - self.sendReport("No se ha fichado ni desfichado ya que estamos en horario laboral, fuerze el estado con el parametro --type <0 = Entrada, 1 = Salida>") - else: - typ = args.type - - data = { - "cp": self.config['user'], - "pin": args.pin, - "typ": typ, - "date": self.headers['tn-d'], - "geoLatitude": self.config['geo']['latitude'], - "geoLongitude": self.config['geo']['longitude'], - "geoError": "", - "c": 1, - "geoAccuracy": self.config['geo']['accuracy'] - } - - response = None - if not args.debug: - self.sendReport("####HACIENDO CHECK####") - response = self.post("check", data, self.headers) - else: - self.sendReport('Corriendo en modo debug. No se realizará ninguna acción') - exit(20) - - if response.text == "no valid worker": - self.sendReport("El trabajador no ha podido ser identificado") - exit(20) - - try: - rj = json.loads(response.text) - except: - self.sendReport("La respuesta al hacer check no es correcta... algo ha pasado :/") - exit(20) - - if response.status_code == 200: - if rj['Repeated']: - time = datetime.strptime(rj['RepeatedTime'], "%Y-%m-%dT%H:%M:%SZ").replace( - tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") - self.sendReport("🕐 Ya se ha realizado este marcaje antes a las %s" % time) - else: - time = datetime.now().astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") - - conditional_response = [{ - "text": "fichado", - "emoji": "🕐🏢🏃‍♂️🏡" - },{ - "text": "desfichado", - "emoji": "🏡🏃‍♂️🏢🕐" - }] - self.sendReport('%s Has %s correctamente a las %s' % (conditional_response[int(typ)]['emoji'], conditional_response[int(typ)]['text'], time)) - else: - self.sendReport("🟥🕐 No se ha podido fichar, la web no ha devuelto 200 OK") - - def calendarCheck(self): - creds = None - configpath = os.path.join(ROOT_DIR, 'googleoauth.json') - if os.path.exists(configpath): - try: - creds = Credentials.from_authorized_user_file(configpath, SCOPES) - except ValueError as error: - print(error) - else: - return False - - - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - flow = InstalledAppFlow.from_client_secrets_file(configpath, SCOPES) - creds = flow.run_local_server(port=0) - - with open(configpath, 'w') as token: - token.write(creds.to_json()) - - try: - service = build('calendar', 'v3', credentials=creds) - - # Call the Calendar API - now = datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z') # 'Z' indicates UTC time - - now_after_one_day = (datetime.utcnow() + relativedelta(days=1)).strftime('%Y-%m-%dT00:00:00Z') - events_result_autoficher = service.events().list(calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com', timeMin=now, - timeMax=now_after_one_day, maxResults=10, singleEvents=True, - orderBy='startTime').execute() - events_result_holiday = service.events().list( - calendarId='es.spain#holiday@group.v.calendar.google.com', - timeMin=now, - timeMax=now_after_one_day, - maxResults=10, singleEvents=True, - orderBy='startTime').execute() - - - events = events_result_autoficher.get('items', []) + events_result_holiday.get('items', []) - - if not events: - return False - - # Prints the start and name of the next 10 events - for event in events: - start = event['start'].get('dateTime', event['start'].get('date')) - end = event['end'].get('dateTime', event['end'].get('date')) - - if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat(end).timestamp(): - if event['organizer']['displayName'] == 'autoficher': - return 'Forzado desde autoficher - ' + event.get("summary", "Sin Título") - - if 'description' in event and ("Cataluña" in event['description'] or 'Día festivo' in event['description']or 'Celebración\n' in event['description']) and not 'Cambio de horario' in event['summary']: - return event['summary'] - except HttpError as error: - print(f'An error occurred: {error}') - return 'Error de llamada a api' - - return False - -if __name__ == '__main__': - af = AutoFicher() - af.sendUpdate() diff --git a/googleoauth_grant.json b/googleoauth_grant.json new file mode 100644 index 0000000..8df2871 --- /dev/null +++ b/googleoauth_grant.json @@ -0,0 +1 @@ +{"installed":{"client_id":"962199633636-qu0te9fbj06fe4p44gskbb1p6f2s0d5s.apps.googleusercontent.com","project_id":"autoficher","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}} diff --git a/main.py b/main.py new file mode 100755 index 0000000..8c405ec --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Automarcaje para timenet.gpisoftware.com +# usage: main.py -u USER -p PIN -t TYPE [-f] [-h] +# +# Argumentos obligatorios: +# -u USER, --user USER Usuario +# -p PIN, --pin PIN Contraseña +# -t TYPE, --type TYPE Tipo marcado. 0 = Entrada, 1 = Salida (Si no se utiliza -bt) +# +# +# Creado: Omar Sánchez 04-05-2019 + +# Importamos App Principal +from app import timenet_manager + + +if __name__ == '__main__': + af = timenet_manager.timenetManager() + af.sendUpdate()