telegram-notify/telegram-notify-daemon
2018-01-15 22:34:25 +01:00

336 lines
10 KiB
Python
Executable File

#!/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)