#!/usr/bin/env python2 import time import random import datetime import re import ConfigParser import telepot import json import sys import os import argparse import logging import atexit import signal from telepot.loop import MessageLoop from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler version = 0.1 def writeconfig(): if not os.access(configFile, os.W_OK): print 'WARNING: Config file (%s) is not writable ...' % configFile logger.warning('Config file (%s) is not writable ...' % configFile) return False with open(configFile, 'wb') as theFile: configParser.write(theFile) logger.info('Config file (%s) updated ...' % configFile) def handle(msg): global configParser global chat_ids pair_pin = configParser.get('general', 'pair_pin') chat_id = msg['chat']['id'] username = msg['chat']['username'] firstname = msg['chat']['first_name'] lastname = msg['chat']['last_name'] command = msg['text'] param = '' paired_user = [x for x in chat_ids if x['id'] == chat_id] is_paired = len(paired_user) > 0 # if text starts with '/' then it's a command if re.match(r'^/',msg['text']): command = re.sub(r"\s.*",'',msg['text']) param = re.sub(r"^([^\s]+)\s","",msg['text']) print '\n\nUser %s sent command: %s (%s)' % (username, command, param) if command == '/pair': if is_paired: bot.sendMessage(chat_id, 'You\'re already paired.') elif param == pair_pin: chat={ 'username': username, 'firstname': firstname, 'lastname': lastname, 'id': chat_id } chat_ids.append(chat) section = 'id-' + str(chat_id) if not configParser.has_section(section): configParser.add_section(section) configParser.set(section, 'username', username) configParser.set(section, 'firstname', firstname) configParser.set(section, 'lastname', lastname) writeconfig() bot.sendMessage(chat_id, 'Pairing ok.') else: bot.sendMessage(chat_id, 'Pairing failed: wrong pin.') elif command == '/newpin': if not is_paired: bot.sendMessage(chat_id, 'You\'re not allowed to do that. You have to pair first.') elif param != '': pair_pin = param configParser.set('general', 'pair_pin', pair_pin) writeconfig() bot.sendMessage(chat_id, 'The new pin is: %s' % pair_pin) else: bot.sendMessage(chat_id, 'Please specify the new pin.') elif command == '/unpair': if not is_paired: bot.sendMessage(chat_id, 'You\'re not allowed to do that. You have to pair first.') else: chat_ids[:] = [x for x in chat_ids if x['id'] != chat_id] section = 'id-' + str(chat_id) configParser.remove_section(section) writeconfig() bot.sendMessage(chat_id, 'Bye.') elif command == '/users': if not is_paired: bot.sendMessage(chat_id, 'You\'re not allowed to do that. You have to pair first.') else: users = [x['username'] + ' (' + x['firstname'] + ' ' + x['lastname'] + ')\n' for x in chat_ids] bot.sendMessage(chat_id, 'Paired users:\n' + "".join([str(i) for i in users]) ) elif command == '/start': bot.sendMessage(chat_id, 'Started:\n' ) elif command == '/help': bot.sendMessage(chat_id, 'Help:\n' ) elif command == '/settings': bot.sendMessage(chat_id, 'Settings:\n' ) def processFile(filename): logger.info('Processing %s' % filename) if re.match(r'.*jpe?g$',filename): for c in chat_ids: try: logger.info('sending %s as picture to %s' % (filename,c['username'])) r=bot.sendPhoto(c['id'], open(filename, 'rb')) os.remove(filename) logger.debug('removed %s' % filename) except: time.sleep(10) if re.match(r'.*txt$',filename): for c in chat_ids: try: with open(filename,'r') as f: logger.info('sending %s as text to %s' % (filename,c['username'])) r=bot.sendMessage(c['id'], f.read()) os.remove(filename) logger.debug('removed %s' % filename) except: time.sleep(10) class FilesChangedHandler(PatternMatchingEventHandler): patterns = ["*.jpg", "*.txt"] def process(self, event): """ event.event_type 'modified' | 'created' | 'moved' | 'deleted' event.is_directory True | False event.src_path path/to/observed/file """ if event.event_type=='created': processFile(event.src_path) def on_modified(self, event): self.process(event) def on_created(self, event): self.process(event) def checkFiles(): spool_dir = configParser.get('general', 'spool_dir') logger.debug('Checking %s for new files ...' % spool_dir) l=os.listdir(spool_dir) for f in l: processFile("%s/%s" % (spool_dir,f) ) def initSignals(): signal.signal(signal.SIGINT, exitGracefully) signal.signal(signal.SIGTERM, exitGracefully) def exitGracefully(signum, frame): global shutdown shutdown = True def setDefaults(): global configFile global defaults configFile = '/etc/telegram-notify/telegram-notify.conf' if not os.path.isfile(configFile) or not os.access(configFile, os.R_OK): configFile = './telegram-notify.conf' if not os.path.isfile(configFile) or not os.access(configFile, os.R_OK): configFile = '' logFile = '/var/log/telegram-notify.log' if not os.path.isfile(logFile) or not os.access(logFile, os.W_OK): logFile = '' defaults = { 'log_file': logFile, 'log_level': 'info', 'daemon': False, 'token': '', 'pair_pin': '1234', 'spool_dir': '/var/spool/telegram-notify', 'pid_file': '/var/run/telegram-notify/telegram-notify.pid' } def parseCmdLine(): global configFile parser = argparse.ArgumentParser(description='Daemon for Telegram notification management') parser.add_argument('-c', '--config', nargs='?', help='start with specified config file (default: telegram-notify.conf in /etc/ or in current directory)') parser.add_argument('-V', '--version', action='store_true', help='show program version and quit') args = parser.parse_args() if args.version: print 'telegram-notify-daemon version %s - Copyright (C) 2018 by Paolo Asperti.' % version sys.exit(0) if args.config: if not os.path.isfile(args.config): print 'specified config file doesn\'t exists or is not a file' sys.exit(1) if not os.access(args.config, os.R_OK): print 'specified config file is not readable' sys.exit(1) configFile = args.config def readConfigFile(): global configParser if configFile == '': print 'no config file available' sys.exit(1) if not os.access(configFile, os.W_OK): print 'WARNING: specified config file is not writable' configParser = ConfigParser.SafeConfigParser(defaults=defaults) configParser.read(configFile) def initLogger(): global logger logFile = configParser.get('general', 'log_file') if logFile == '': print 'no log file available' sys.exit(1) logLevel = configParser.get('general', 'log_level') logging.basicConfig(filename=logFile,level=logLevel,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def checkPIDFile(): PIDFile = configParser.get('general', 'pid_file') try: pf = open(PIDFile,'r') pid = int(pf.read().strip()) pf.close() except (IOError, TypeError): pid = None if pid: logger.error('pidfile %s already exist. Daemon already running?' % PIDFile) sys.stderr.write('pidfile %s already exist. Daemon already running?' % PIDFile) sys.exit(1) atexit.register(delPIDFile) open(PIDFile,'w+').write("%s\n" % str(os.getpid())) logger.info('Created PIDfile %s' % PIDFile) def delPIDFile(): PIDFile = configParser.get('general', 'pid_file') os.remove(PIDFile) logger.info('Removed PIDfile %s' % PIDFile) def checkSpoolDir(): spool_dir = configParser.get('general', 'spool_dir') if not os.path.isdir(spool_dir): print "spool directory (%s) doesn't exists or is not a directory!" % spool_dir sys.exit(1) if not os.access(spool_dir, os.W_OK): print "spool directory (%s) is not writable!" % spool_dir sys.exit(1) def initBot(): global bot global chat_ids chat_ids=[] logger.debug('Allowed users:') for p in configParser.sections(): if re.match('^id-.*',p): chat_id={ 'username': configParser.get(p,'username'), 'firstname': configParser.get(p,'firstname'), 'lastname': configParser.get(p,'lastname'), 'id': int(re.sub(r'^id-','',p)) } chat_ids.append(chat_id) logger.debug(" - (%s) %s %s" % (chat_id['username'],chat_id['firstname'],chat_id['lastname']) ) token = configParser.get('general', 'token') bot = telepot.Bot(token) MessageLoop(bot, handle).run_as_thread() logger.info('Loop started. I am listening ...') checkFiles() spool_dir = configParser.get('general', 'spool_dir') observer = Observer() observer.schedule(FilesChangedHandler(), path=spool_dir) observer.start() initSignals() setDefaults() parseCmdLine() readConfigFile() initLogger() checkPIDFile() checkSpoolDir() initBot() daemon = configParser.get('general', 'daemon') shutdown = False while 1: checkFiles() if re.match('false', daemon, re.IGNORECASE): sys.exit(0) for x in range(0, 40): if shutdown: logger.info('Daemon shutdown requested. Exiting...') sys.exit(0) time.sleep(0.25)