Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from __future__ import absolute_import
- from PIL import Image
- import threading, requests, re, time, datetime, StringIO, json, random, logging, traceback, io, collections, os, flask,base64,PIL, pkg_resources
- import octoprint.plugin, octoprint.util, octoprint.filemanager
- from flask.ext.babel import gettext
- from flask.ext.login import current_user
- from .telegramCommands import TCMD # telegramCommands.
- from .telegramNotifications import TMSG # telegramNotifications
- from .telegramNotifications import telegramMsgDict # dict of known notification messages
- from .emojiDict import telegramEmojiDict # dict of known emojis
- ####################################################
- # TelegramListener Thread Class
- # Connects to Telegram and will listen for messages.
- # On incomming message the listener will process it.
- ####################################################
- class TelegramListener(threading.Thread):
- def __init__(self, main):
- threading.Thread.__init__(self)
- self.update_offset = 0
- self.first_contact = True
- self.main = main
- self.do_stop = False
- self.username = "UNKNOWN"
- self._logger = main._logger.getChild("listener")
- self.gEmo = self.main.gEmo
- def run(self):
- self._logger.debug("Try first connect.")
- self.tryFirstContact()
- # repeat fetching and processing messages unitil thread stopped
- self._logger.debug("Listener is running.")
- try:
- while not self.do_stop:
- try:
- self.loop()
- except ExitThisLoopException:
- # do nothing, just go to the next loop
- pass
- except Exception as ex:
- self._logger.error("An Exception crashed the Listener: " + str(ex) + " Traceback: " + traceback.format_exc() )
- self._logger.debug("Listener exits NOW.")
- # Try to get first contact. Repeat every 120sek if no success
- # or stop if task stopped
- def tryFirstContact(self):
- gotContact = False
- while not self.do_stop and not gotContact:
- try:
- self.username = self.main.test_token()
- gotContact = True
- self.set_status(gettext("Connected as %(username)s.", username=self.username), ok=True)
- except Exception as ex:
- self.set_status(gettext("Got an exception while initially trying to connect to telegram (Listener not running: %(ex)s. Waiting 2 minutes before trying again.)", ex=ex))
- time.sleep(120)
- def loop(self):
- chat_id = ""
- json = self.getUpdates()
- try:
- # seems like we got a message, so lets process it.
- for message in json['result']:
- self.processMessage(message)
- except ExitThisLoopException as exit:
- raise exit
- #wooooops. can't handle the message
- except Exception as ex:
- self._logger.error("Exception caught! " + str(ex))
- self.set_status(gettext("Connected as %(username)s.", username=self.username), ok=True)
- # we had first contact after octoprint startup
- # so lets send startup message
- if self.first_contact:
- self.first_contact = False
- self.main.on_event("PrinterStart",{})
- def set_update_offset(self, new_value):
- if new_value >= self.update_offset:
- self._logger.debug("Updating update_offset from {} to {}".format(self.update_offset, 1 + new_value))
- self.update_offset = 1 + new_value
- else:
- self._logger.debug("Not changing update_offset - otherwise would reduce it from {} to {}".format(self.update_offset, 1 + new_value))
- def processMessage(self, message):
- self._logger.debug("MESSAGE: " + str(message))
- # Get the update_id to only request newer Messages the next time
- self.set_update_offset(message['update_id'])
- # no message no cookies
- if 'message' in message and message['message']['chat']:
- chat_id, from_id = self.parseUserData(message)
- # if we come here without a continue (discard message)
- # we have a message from a known and not new user
- # so let's check what he send us
- # if message is a text message, we probably got a command
- # when the command is not known, the following handler will discard it
- if "text" in message['message']:
- self.handleTextMessage(message, chat_id, from_id)
- # we got no message with text (command) so lets check if we got a file
- # the following handler will check file and saves it to disk
- elif "document" in message['message']:
- self.handleDocumentMessage(message, chat_id, from_id)
- # we got message with notification for a new chat title photo
- # so lets download it
- elif "new_chat_photo" in message['message']:
- self.handleNewChatPhotoMessage(message, chat_id, from_id)
- # we got message with notification for a deleted chat title photo
- # so we do the same
- elif "delete_chat_photo" in message['message']:
- self.handleDeleteChatPhotoMessage(message, chat_id, from_id)
- # a member was removed from a group, so lets check if it's our bot and
- # delete the group from our chats if it is our bot
- elif "left_chat_member" in message['message']:
- self.handleLeftChatMemberMessage(message, chat_id, from_id)
- # we are at the end. At this point we don't know what message type it is, so we do nothing
- else:
- self._logger.warn("Got an unknown message. Doing nothing. Data: " + str(message))
- elif 'callback_query' in message:
- self.handleCallbackQuery(message)
- else:
- self._logger.warn("Response is missing .message or .message.chat or callback_query. Skipping it.")
- raise ExitThisLoopException()
- def handleCallbackQuery(self, message):
- message['callback_query']['message']['text'] = message['callback_query']['data']
- chat_id, from_id = self.parseUserData(message['callback_query'])
- self.handleTextMessage(message['callback_query'],chat_id, from_id)
- def handleLeftChatMemberMessage(self, message, chat_id, from_id):
- self._logger.debug("Message Del_Chat")
- if message['message']['left_chat_member']['username'] == self.username[1:] and str(message['message']['chat']['id']) in self.main.chats:
- del self.main.chats[str(message['message']['chat']['id'])]
- # do a self._settings.save() ???
- self._logger.debug("Chat deleted")
- def handleDeleteChatPhotoMessage(self, message, chat_id, from_id):
- self._logger.debug("Message Del_Chat_Photo")
- try:
- os.remove(self.main.get_plugin_data_folder()+"/img/user/pic" +str(message['message']['chat']['id'])+".jpg")
- self._logger.debug("File removed")
- except OSError:
- pass
- def handleNewChatPhotoMessage(self, message, chat_id, from_id):
- self._logger.debug("Message New_Chat_Photo")
- # only if we know the chat
- if str(message['message']['chat']['id']) in self.main.chats:
- self._logger.debug("New_Chat_Photo Found User")
- kwargs = {'chat_id':int(message['message']['chat']['id']), 'file_id': message['message']['new_chat_photo'][0]['file_id'] }
- t = threading.Thread(target=self.main.get_usrPic, kwargs=kwargs)
- t.daemon = True
- t.run()
- def handleDocumentMessage(self, message, chat_id, from_id):
- # first we have to check if chat or group is allowed to upload
- from_id = chat_id
- if not self.main.chats[chat_id]['private']: #is this needed? can one send files from groups to bots?
- from_id = str(message['message']['from']['id'])
- # is /upload allowed?
- if self.main.isCommandAllowed(chat_id,from_id,'/upload'):
- self.main.track_action("command/upload_exec")
- try:
- file_name = message['message']['document']['file_name']
- #if not (file_name.lower().endswith('.gcode') or file_name.lower().endswith('.gco') or file_name.lower().endswith('.g')):
- self._logger.debug(str(file_name.lower().split('.')[-1]))
- if not octoprint.filemanager.valid_file_type(file_name,"machinecode"):
- self.main.send_msg(self.gEmo('warning') + " Sorry, I only accept files with .gcode, .gco or .g extension.", chatID=chat_id)
- raise ExitThisLoopException()
- # download the file
- if self.main.version >= 1.3:
- target_filename = "TelegramPlugin/"+file_name
- from octoprint.server.api.files import _verifyFolderExists
- if not _verifyFolderExists(octoprint.filemanager.FileDestinations.LOCAL, "TelegramPlugin"):
- self.main._file_manager.add_folder(octoprint.filemanager.FileDestinations.LOCAL,"TelegramPlugin")
- else:
- target_filename = "telegram_"+file_name
- # for parameter no_markup see _send_edit_msg()
- self.main.send_msg(self.gEmo('save') + gettext(" Saving file {}...".format(target_filename)), chatID=chat_id)
- requests.get(self.main.bot_url + "/sendChatAction", params = {'chat_id': chat_id, 'action': 'upload_document'}, proxies=self.getProxies())
- data = self.main.get_file(message['message']['document']['file_id'])
- stream = octoprint.filemanager.util.StreamWrapper(file_name, io.BytesIO(data))
- self.main._file_manager.add_file(octoprint.filemanager.FileDestinations.LOCAL, target_filename, stream, allow_overwrite=True)
- # for parameter msg_id see _send_edit_msg()
- self.main.send_msg(self.gEmo('upload') + " I've successfully saved the file you sent me as {}.".format(target_filename),msg_id=self.main.getUpdateMsgId(chat_id),chatID=chat_id)
- except ExitThisLoopException:
- pass
- except Exception as ex:
- self.main.send_msg(self.gEmo('warning') + " Something went wrong during processing of your file."+self.gEmo('mistake')+" Sorry. More details are in octoprint.log.",msg_id=self.main.getUpdateMsgId(chat_id),chatID=chat_id)
- self._logger.debug("Exception occured during processing of a file: "+ traceback.format_exc() )
- else:
- self._logger.warn("Previous file was from an unauthorized user.")
- self.main.send_msg("Don't feed the octopuses! " + self.gEmo('octo'),chatID=chat_id)
- def handleTextMessage(self, message, chat_id, from_id):
- # We got a chat message.
- # handle special messages from groups (/commad@BotName)
- command = str(message['message']['text'].split('@')[0].encode('utf-8'))
- parameter = ""
- # TODO: Do we need this anymore?
- # reply_to_messages will be send on value inputs (eg notification height)
- # but also on android when pushing a button. Then we have to switch command and parameter.
- #if "reply_to_message" in message['message'] and "text" in message['message']['reply_to_message']:
- #command = message['message']['reply_to_message']['text']
- #parameter = message['message']['text']
- #if command.encode('utf-8') not in [str(k.encode('utf-8')) for k in self.main.tcmd.commandDict.keys()]:
- #command = message['message']['text']
- #parameter = message['message']['reply_to_message']['text']
- # if command is with parameter, get the parameter
- if any((k+"_") in command for k,v in self.main.tcmd.commandDict.iteritems() if 'param' in v):
- parameter = '_'.join(command.split('_')[1:])
- command = command.split('_')[0]
- self._logger.info("Got a command: '" + str(command.encode('utf-8')) + "' with parameter: '" + str(parameter.encode('utf-8')) + "' in chat " + str(message['message']['chat']['id']))
- # is command known?
- if command not in self.main.tcmd.commandDict:
- # we dont know the command so skip the message
- self._logger.warn("Previous command was an unknown command.")
- self.main.send_msg("I do not understand you! " + self.gEmo('mistake'),chatID=chat_id)
- raise ExitThisLoopException()
- # check if user is allowed to execute the command
- if self.main.isCommandAllowed(chat_id,from_id,command):
- # Track command
- if command.startswith("/"):
- self.main.track_action("command/" + command[1:])
- # execute command
- self.main.tcmd.commandDict[command]['cmd'](chat_id,from_id,command,parameter)
- else:
- # user was not alloed to execute this command
- self._logger.warn("Previous command was from an unauthorized user.")
- self.main.send_msg("You are not allowed to do this! " + self.gEmo('notallowed'),chatID=chat_id)
- def parseUserData(self, message):
- self.main.chats = self.main._settings.get(["chats"])
- chat = message['message']['chat']
- chat_id = str(chat['id'])
- data = self.main.newChat # data for new user
- # if we know the user or chat, overwrite data with user data
- if chat_id in self.main.chats:
- data = self.main.chats[chat_id]
- # update data or get data for new user
- data['type'] = chat['type'].upper()
- if chat['type']=='group' or chat['type'] == 'supergroup':
- data['private'] = False
- data['title'] = chat['title']
- elif chat['type']=='private':
- data['private'] = True
- data['title'] = ""
- if "first_name" in chat:
- data['title'] += chat['first_name'] + " - "
- if "last_name" in chat:
- data['title'] += chat['last_name'] + " - "
- if "username" in chat:
- data['title'] += "@" + chat['username']
- from_id = chat_id
- # if message is from a group, chat_id will be left as id of group
- # and from_id is set to id of user who send the message
- if not data['private']:
- if 'from' in message:
- from_id = str(message['from']['id'])
- else:
- from_id = str(message['message']['from']['id'])
- # if group accepts only commands from known users (allow_users = true, accept_commands=false)
- # and user is not in known chats, then he is unknown and we dont wnat to listen to him.
- if chat_id in self.main.chats:
- if self.main.chats[chat_id]['allow_users'] and from_id not in self.main.chats and not self.main.chats[chat_id]['accept_commands']:
- self._logger.warn("Previous command was from an unknown user.")
- self.main.send_msg("I don't know you! Certainly you are a nice Person " + self.gEmo('heart'),chatID=chat_id)
- raise ExitThisLoopException()
- # if we dont know the user or group, create new user
- # send welcome message and skip message
- if chat_id not in self.main.chats:
- self.main.chats[chat_id] = data
- self.main.send_msg(self.gEmo('info') + "Now I know you. Before you can do anything, go to OctoPrint Settings and edit some rights.",chatID=chat_id)
- kwargs = {'chat_id':int(chat_id)}
- t = threading.Thread(target=self.main.get_usrPic, kwargs=kwargs)
- t.daemon = True
- t.run()
- self._logger.debug("Got new User")
- raise ExitThisLoopException()
- return (chat_id, from_id)
- def getUpdates(self):
- self._logger.debug("listener: sending request with offset " + str(self.update_offset) + "...")
- req = None
- # try to check for incoming messages. wait 120sek and repeat on failure
- try:
- if self.update_offset == 0 and self.first_contact:
- res = ["0","0"]
- while len(res) > 0:
- req = requests.get(self.main.bot_url + "/getUpdates", params={'offset':self.update_offset, 'timeout':0}, allow_redirects=False, timeout=10, proxies=self.getProxies())
- json = req.json()
- if not json['ok']:
- self.set_status(gettext("Response didn't include 'ok:true'. Waiting 2 minutes before trying again. Response was: %(response)s", json))
- time.sleep(120)
- raise ExitThisLoopException()
- if len(json['result']) > 0 and 'update_id' in json['result'][0]:
- self.set_update_offset(json['result'][0]['update_id'])
- res = json['result']
- if len(res) < 1:
- self._logger.debug("Ignoring message because first_contact is True.")
- if self.update_offset == 0:
- self.set_update_offset(0)
- else:
- req = requests.get(self.main.bot_url + "/getUpdates", params={'offset':self.update_offset, 'timeout':30}, allow_redirects=False, timeout=40, proxies=self.getProxies())
- except requests.exceptions.Timeout:
- # Just start the next loop.
- raise ExitThisLoopException()
- except Exception as ex:
- self.set_status(gettext("Got an exception while trying to connect to telegram API: %(exception)s. Waiting 2 minutes before trying again.", exception=ex))
- time.sleep(120)
- raise ExitThisLoopException()
- if req.status_code != 200:
- self.set_status(gettext("Telegram API responded with code %(status_code)s. Waiting 2 minutes before trying again.", status_code=req.status_code))
- time.sleep(120)
- raise ExitThisLoopException()
- if req.headers['content-type'] != 'application/json':
- self.set_status(gettext("Unexpected Content-Type. Expected: application/json. Was: %(type)s. Waiting 2 minutes before trying again.", type=req.headers['content-type']))
- time.sleep(120)
- raise ExitThisLoopException()
- json = req.json()
- if not json['ok']:
- self.set_status(gettext("Response didn't include 'ok:true'. Waiting 2 minutes before trying again. Response was: %(response)s", json))
- time.sleep(120)
- raise ExitThisLoopException()
- if "result" in json and len(json['result']) > 0:
- for entry in json['result']:
- self.set_update_offset(entry['update_id'])
- return json
- # stop the listener
- def stop(self):
- self.do_stop = True
- def set_status(self, status, ok=False):
- if status != self.main.connection_state_str:
- if self.do_stop:
- self._logger.debug("Would set status but do_stop is True: %s", status)
- return
- if ok:
- self._logger.debug("Setting status: %s", status)
- else:
- self._logger.error("Setting status: %s", status)
- self.connection_ok = ok
- self.main.connection_state_str = status
- def getProxies(self):
- http_proxy = self.main._settings.get(["http_proxy"])
- https_proxy = self.main._settings.get(["https_proxy"])
- return {
- 'http': http_proxy,
- 'https': https_proxy
- }
- class TelegramPluginLoggingFilter(logging.Filter):
- def filter(self, record):
- for match in re.findall("[0-9]+:[a-zA-Z0-9_\-]+", record.msg):
- new = re.sub("[0-9]", "1", re.sub("[a-z]", "a", re.sub("[A-Z]", "A", match)))
- record.msg = record.msg.replace(match, new)
- return True
- class ExitThisLoopException(Exception):
- pass
- ########################################
- ########################################
- ############## THE PLUGIN ##############
- ########################################
- ########################################
- class TelegramPlugin(octoprint.plugin.EventHandlerPlugin,
- octoprint.plugin.SettingsPlugin,
- octoprint.plugin.StartupPlugin,
- octoprint.plugin.ShutdownPlugin,
- octoprint.plugin.TemplatePlugin,
- octoprint.plugin.SimpleApiPlugin,
- octoprint.plugin.AssetPlugin
- ):
- def __init__(self,version):
- self.version = float(version)
- # for more init stuff see on_after_startup()
- self.thread = None
- self.bot_url = None
- self.chats = {}
- self.connection_state_str = gettext("Disconnected.")
- self.connection_ok = False
- requests.packages.urllib3.disable_warnings()
- self.updateMessageID = {}
- self.shut_up = {}
- self.send_messages = True
- self.tcmd = None
- self.tmsg = None
- self.sending_okay_minute = None
- self.sending_okay_count = 0
- # initial settings for new chat. See on_after_startup()
- # !!! sync with newUsrDict in on_settings_migrate() !!!
- self.newChat = {}
- # use of emojis see below at method gEmo()
- self.emojis = {
- 'octo': u'\U0001F419', #octopus
- 'mistake': u'\U0001F616',
- 'notify': u'\U0001F514',
- 'shutdown' : u'\U0001F4A4',
- 'shutup': u'\U0001F64A',
- 'noNotify': u'\U0001F515',
- 'notallowed': u'\U0001F62C',
- 'rocket': u'\U0001F680',
- 'save': u'\U0001F4BE',
- 'heart': u'\U00002764',
- 'info': u'\U00002139',
- 'settings': u'\U0001F4DD',
- 'clock': u'\U000023F0',
- 'height': u'\U00002B06',
- 'question': u'\U00002753',
- 'warning': u'\U000026A0',
- 'enter': u'\U0000270F',
- 'upload': u'\U0001F4E5',
- 'check': u'\U00002705',
- 'lamp': u'\U0001F4A1',
- 'movie': u'\U0001F3AC',
- 'finish': u'\U0001F3C1',
- 'cam': u'\U0001F3A6',
- 'hooray': u'\U0001F389',
- 'error': u'\U000026D4',
- 'play': u'\U000025B6',
- 'stop': u'\U000025FC'
- }
- self.emojis.update(telegramEmojiDict)
- # all emojis will be get via this method to disable them globaly by the corrosponding setting
- # so if you want to use emojis anywhere use gEmo("...") istead of emojis["..."]
- def gEmo(self,key):
- if self._settings.get(["send_icon"]) and key in self.emojis:
- return self.emojis[key]
- return ""
- # starts the telegram listener thread
- def start_listening(self):
- if self._settings.get(['token']) != "" and self.thread is None:
- self._logger.debug("Starting listener.")
- self.bot_url = "https://api.telegram.org/bot" + self._settings.get(['token'])
- self.bot_file_url = "https://api.telegram.org/file/bot" + self._settings.get(['token'])
- self.thread = TelegramListener(self)
- self.thread.daemon = True
- self.thread.start()
- # stops the telegram listener thread
- def stop_listening(self):
- if self.thread is not None:
- self._logger.debug("Stopping listener.")
- self.thread.stop()
- self.thread = None
- def shutdown(self):
- self._logger.warn("shutdown() running!")
- self.stop_listening()
- self.send_messages = False
- def sending_okay(self):
- # If the count ever goeas above 10, we stop doing everything else and just return False
- # so if this is ever reached, it will stay this way.
- if self.sending_okay_count > 10:
- self._logger.warn("Sent more than 10 messages in the last minute. Shutting down...")
- self.shutdown()
- return False
- if self.sending_okay_minute != datetime.datetime.now().minute:
- self.sending_okay_minute = datetime.datetime.now().minute
- self.sending_okay_count = 1
- else:
- self.sending_okay_count += 1
- return True
- ##########
- ### Asset API
- ##########
- def get_assets(self):
- return dict(js=["js/telegram.js"])
- ##########
- ### Template API
- ##########
- def get_template_configs(self):
- return [
- dict(type="settings", name="Telegram", custom_bindings=True)
- ]
- ##########
- ### Wizard API
- ##########
- def is_wizard_required(self):
- return self._settings.get(["token"]) is ""
- def get_wizard_version(self):
- return 1
- # Wizard version numbers used in releases
- # < 1.4.2 : no wizard
- # 1.4.2 : 1
- # 1.4.3 : 1
- ##########
- ### Startup/Shutdown API
- ##########
- def on_after_startup(self):
- self.set_log_level()
- self._logger.addFilter(TelegramPluginLoggingFilter())
- self.tcmd = TCMD(self)
- self.tmsg = TMSG(self) # Notification Message Handler class. called only by on_event()
- # initial settings for new chat.
- # !!! sync this dict with newUsrDict in on_settings_migrate() !!!
- self.newChat = {
- 'private': True,
- 'title': "[UNKNOWN]",
- 'accept_commands' : False,
- 'send_notifications' : False,
- 'new': True,
- 'type': '',
- 'allow_users': False,
- 'commands': {k: False for k,v in self.tcmd.commandDict.iteritems()},
- 'notifications': {k: False for k,v in telegramMsgDict.iteritems()}
- }
- self.chats = self._settings.get(["chats"])
- self.start_listening()
- self.track_action("started")
- # Delete user profile photos if user doesn't exist anymore
- for f in os.listdir(self.get_plugin_data_folder()+"/img/user"):
- fcut = f.split('.')[0][3:]
- self._logger.debug("Testing Pic ID " + str(fcut))
- if fcut not in self.chats:
- self._logger.debug("Removing pic" +fcut+".jpg")
- try:
- os.remove(self.get_plugin_data_folder()+"/img/user/"+f)
- except OSError:
- pass
- #Update user profile photos
- for key in self.chats:
- try:
- if key is not 'zBOTTOMOFCHATS':
- kwargs = {}
- kwargs['chat_id'] = int(key)
- t = threading.Thread(target=self.get_usrPic, kwargs=kwargs)
- t.daemon = True
- t.run()
- except Exception:
- pass
- def on_shutdown(self):
- self.on_event("PrinterShutdown",{})
- self.stop_listening()
- ##########
- ### Settings API
- ##########
- def get_settings_version(self):
- return 4
- # Settings version numbers used in releases
- # < 1.3.0: no settings versioning
- # 1.3.0 : 1
- # 1.3.1 : 2
- # 1.3.2 : 2
- # 1.3.3 : 2
- # 1.4.0 : 3
- # 1.4.1 : 3
- # 1.4.2 : 3
- # 1.4.3 : 4
- def get_settings_defaults(self):
- return dict(
- token = "",
- notification_height = 5.0,
- notification_time = 15,
- message_at_print_done_delay = 0,
- messages = telegramMsgDict,
- tracking_activated = False,
- tracking_token = None,
- chats = {'zBOTTOMOFCHATS':{'send_notifications': False,'accept_commands':False,'private':False}},
- debug = False,
- send_icon = True,
- image_not_connected = True,
- fileOrder = False
- )
- def get_settings_preprocessors(self):
- return dict(), dict(
- notification_height=lambda x: float(x),
- notification_time=lambda x: int(x)
- )
- def on_settings_migrate(self, target, current=None):
- self._logger.setLevel(logging.DEBUG)
- self._logger.debug("MIGRATE DO")
- tcmd = TCMD(self)
- # initial settings for new chat.
- # !!! sync this dict with newChat in on_after_startup() !!!
- newUsrDict = {
- 'private': True,
- 'title': "[UNKNOWN]",
- 'accept_commands' : False,
- 'send_notifications' : False,
- 'new': False,
- 'type': '',
- 'allow_users': False,
- 'commands': {k: False for k,v in tcmd.commandDict.iteritems()},
- 'notifications': {k: False for k,v in telegramMsgDict.iteritems()}
- }
- ##########
- ### migrate from old plugin Versions < 1.3 (old versions had no settings version check)
- ##########
- chats = {k: v for k, v in self._settings.get(['chats']).iteritems() if k != 'zBOTTOMOFCHATS'}
- self._logger.debug("LOADED CHATS: " + str(chats))
- self._settings.set(['chats'], {})
- if current is None or current < 1:
- ########## Update Chats
- # there shouldn't be any chats, but maybe somone had installed any test branch.
- # Then we have to check if all needed settings are populated
- for chat in chats:
- for setting in newUsrDict:
- if setting not in chats[chat]:
- if setting == "commands":
- chats[chat]['commands'] = {k: False for k,v in tcmd.commandDict.iteritems() if 'bind_none' not in v}
- elif setting == "notifications":
- chats[chat]['notifications'] = {k: False for k,v in telegramMsgDict.iteritems()}
- else:
- chats[chat][setting] = False
- ########## Is there a chat from old single user plugin version?
- # then migrate it into chats
- chat = self._settings.get(["chat"])
- if chat is not None:
- self._settings.set(["chat"], None)
- data = {}
- data.update(newUsrDict)
- data['private'] = True
- data['title'] = "[UNKNOWN]"
- #try to get infos from telegram by sending a "you are migrated" message
- try:
- message = {}
- message['text'] = "The OctoPrint Plugin " + self._plugin_name + " has been updated to new Version "+self._plugin_version+ ".\n\nPlease open your " + self._plugin_name + " settings in OctoPrint and set configurations for this chat. Until then you are not able to send or receive anything useful with this Bot.\n\nMore informations on: https://github.com/fabianonline/OctoPrint-Telegram"
- message['chat_id'] = chat
- message['disable_web_page_preview'] = True
- r = requests.post("https://api.telegram.org/bot" + self._settings.get(['token']) + "/sendMessage", data = message, proxies=self.getProxies())
- r.raise_for_status()
- if r.headers['content-type'] != 'application/json':
- raise Exception("invalid content-type")
- json = r.json()
- if not json['ok']:
- raise Exception("invalid request")
- chat = json['result']['chat']
- if chat['type']=='group':
- data['private'] = False
- data['title'] = chat['title']
- elif chat['type']=='private':
- data['private'] = True
- data['title'] = ""
- if "first_name" in chat:
- data['title'] += chat['first_name'] + " - "
- if "last_name" in chat:
- data['title'] += chat['last_name'] + " - "
- if "username" in chat:
- data['title'] += "@" + chat['username']
- except Exception as ex:
- self._logger.debug("ERROR migrating chat. Done with defaults private=true,title=[UNKNOWN] : " + str(ex))
- # place the migrated chat in chats
- chats.update({str(chat['id']): data})
- self._logger.debug("MIGRATED Chats: " + str(chats))
- ########## Update messages. Old text will be taken to new structure
- messages = self._settings.get(['messages'])
- msgOut = {}
- for msg in messages:
- if msg == 'TelegramSendNotPrintingStatus':
- msg2 = 'StatusNotPrinting'
- elif msg == 'TelegramSendPrintingStatus':
- msg2 = 'StatusPrinting'
- else:
- msg2 = msg
- if type(messages[msg]) is not type({}):
- newMsg = telegramMsgDict[msg2].copy()
- newMsg['text'] = str(messages[msg])
- msgOut.update({msg2: newMsg})
- else:
- msgOut.update({msg2: messages[msg]})
- self._settings.set(['messages'], msgOut)
- ########## Delete old settings
- self._settings.set(["message_at_startup"], None)
- self._settings.set(["message_at_shutdown"], None)
- self._settings.set(["message_at_print_started"], None)
- self._settings.set(["message_at_print_done"], None)
- self._settings.set(["message_at_print_failed"], None)
- ##########
- ### Migrate to new command/notification settings version.
- ### This should work on all future versions. So if you add/del
- ### some commands/notifications, then increment settings version counter
- ### in get_settings_version(). This will trigger octoprint to update settings
- ##########
- if current is None or current < target:
- # first we have to check if anything has changed in commandDict or telegramMsgDict
- # then we have to update user comamnd or notification settings
- if chats is not None and chats is not {}:
- # this for loop updates commands and notifications settings items of chats
- # if there are changes in commandDict or telegramMsgDict
- for chat in chats:
- # handle renamed commands
- if '/list' in chats[chat]['commands']:
- chats[chat]['commands'].update({'/files':chats[chat]['commands']['/list']})
- if '/imsorrydontshutup' in chats[chat]['commands']:
- chats[chat]['commands'].update({'/dontshutup':chats[chat]['commands']['/imsorrydontshutup']})
- if 'type' not in chats[chat]:
- chats[chat].update({'type': 'PRIVATE' if chats[chat]['private'] else 'GROUP'})
- delCmd = []
- # collect remove 'bind_none' commands
- for cmd in tcmd.commandDict:
- if cmd in chats[chat]['commands'] and 'bind_none' in tcmd.commandDict[cmd]:
- delCmd.append(cmd)
- # collect Delete commands from settings if they don't belong to commandDict anymore
- for cmd in chats[chat]['commands']:
- if cmd not in tcmd.commandDict:
- delCmd.append(cmd)
- # finally delete commands
- for cmd in delCmd:
- del chats[chat]['commands'][cmd]
- # If there are new commands in comamndDict, add them to settings
- for cmd in tcmd.commandDict:
- if cmd not in chats[chat]['commands']:
- if 'bind_none' not in tcmd.commandDict[cmd]:
- chats[chat]['commands'].update({cmd: False})
- # Delete notifications from settings if they don't belong to msgDict anymore
- delMsg = []
- for msg in chats[chat]['notifications']:
- if msg not in telegramMsgDict:
- delMsg.append(msg)
- for msg in delMsg:
- del chats[chat]['notifications'][msg]
- # If there are new notifications in msgDict, add them to settings
- for msg in telegramMsgDict:
- if msg not in chats[chat]['notifications']:
- chats[chat]['notifications'].update({msg: False})
- self._settings.set(['chats'],chats)
- ########## if anything changed in telegramMsgDict, we also have to update settings for messages
- messages = self._settings.get(['messages'])
- if messages is not None and messages is not {}:
- # this for loop deletes items from messages settings
- # if they dont't belong to telegramMsgDict anymore
- delMsg = []
- for msg in messages:
- if msg not in telegramMsgDict:
- delMsg.append(msg)
- for msg in delMsg:
- del messages[msg]
- # this for loop adds new message settings from telegramMsgDict to settings
- for msg in telegramMsgDict:
- if msg not in messages:
- messages.update({msg: telegramMsgDict[msg]})
- elif 'combined' not in messages[msg]:
- messages[msg].update({'combined' : True})
- self._settings.set(['messages'],messages)
- self._logger.debug("MESSAGES: " + str(self._settings.get(['messages'])))
- if current is not None:
- if current < 2:
- if chats is not None and chats is not {}:
- for chat in chats:
- if os.path.isfile(self.get_plugin_data_folder()+"/pic"+chat+".jpg"):
- os.remove(self.get_plugin_data_folder()+"/pic"+chat+".jpg")
- ##########
- ### save the settings after Migration is done
- ##########
- self._logger.debug("SAVED Chats: " + str(self._settings.get(['chats'])))
- try:
- self._settings.save()
- except Exception as ex:
- self._logger.error("MIGRATED Save failed - " + str(ex))
- self._logger.debug("MIGRATED Saved")
- def on_settings_save(self, data):
- # Remove 'new'-flag and apply bindings for all chats
- if 'chats' in data and data['chats']:
- delList = []
- for key in data['chats']:
- if 'new' in data['chats'][key] or 'new' in data['chats'][key]:
- data['chats'][key]['new'] = False
- # Look for deleted chats
- if not key in self.chats and not key == "zBOTTOMOFCHATS":
- delList.append(key)
- # Delete chats finally
- for key in delList:
- del data['chats'][key]
- # Also remove 'new'-flag from self.chats so settingsUI is consistent
- # self.chats will only update to settings data on first received message after saving done
- for key in self.chats:
- if 'new' in self.chats[key]:
- self.chats[key]['new'] = False
- self._logger.debug("Saving data: " + str(data))
- # Check token for right format
- if 'token' in data:
- data['token'] = data['token'].strip()
- if not re.match("^[0-9]+:[a-zA-Z0-9_\-]+$", data['token']):
- self._logger.error("Not saving token because it doesn't seem to have the right format.")
- self.connection_state_str = gettext("The previously entered token doesn't seem to have the correct format. It should look like this: 12345678:AbCdEfGhIjKlMnOpZhGtDsrgkjkZTCHJKkzvjhb")
- data['token'] = ""
- old_token = self._settings.get(["token"])
- # Update Tracking
- if 'tracking_activated' in data and not data['tracking_activated']:
- data['tracking_token'] = None
- # Now save settings
- octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
- self.set_log_level()
- # Reconnect on new token
- # Will stop listener on invalid token
- if 'token' in data:
- if data['token']!=old_token:
- self.stop_listening()
- if data['token']!="":
- self.start_listening()
- else:
- self.connection_state_str = gettext("No token given.")
- def on_settings_load(self):
- data = octoprint.plugin.SettingsPlugin.on_settings_load(self)
- # only return our restricted settings to admin users - this is only needed for OctoPrint <= 1.2.16
- restricted = (("token", None), ("tracking_token", None), ("chats", dict()))
- for r, v in restricted:
- if r in data and (current_user is None or current_user.is_anonymous() or not current_user.is_admin()):
- data[r] = v
- return data
- def get_settings_restricted_paths(self):
- # only used in OctoPrint versions > 1.2.16
- return dict(admin=[["token"], ["tracking_token"], ["chats"]])
- ##########
- ### Softwareupdate API
- ##########
- def get_update_information(self, *args, **kwargs):
- return dict(
- telegram=dict(
- displayName=self._plugin_name,
- displayVersion=self._plugin_version,
- type="github_release",
- current=self._plugin_version,
- user="fabianonline",
- repo="OctoPrint-Telegram",
- pip="https://github.com/fabianonline/OctoPrint-Telegram/releases/{target_version}/download/release.zip"
- )
- )
- ##########
- ### EventHandler API
- ##########
- def on_event(self, event, payload, **kwargs):
- try:
- # if we know the event, start handler
- if event in self.tmsg.msgCmdDict:
- self._logger.debug("Got an event: " + event + " Payload: " + str(payload))
- # Start event handler
- self.tmsg.startEvent(event, payload, **kwargs)
- else:
- # return as fast as possible
- return
- except Exception as e:
- self._logger.debug("Exception: " + str(e))
- ##########
- ### SimpleApi API
- ##########
- def get_api_commands(self):
- return dict(
- testToken=["token"],
- delChat=["ID"]
- )
- def on_api_get(self, request):
- # got an user-update with this command. so lets do that
- if 'id' in request.args and 'cmd' in request.args and 'note' in request.args and 'allow' in request.args:
- self.chats[request.args['id']]['accept_commands'] = self.str2bool(str(request.args['cmd']))
- self.chats[request.args['id']]['send_notifications'] = self.str2bool(str(request.args['note']))
- self.chats[request.args['id']]['allow_users'] = self.str2bool(str(request.args['allow']))
- self._logger.debug("Updated chat - " + str(request.args['id']))
- elif 'bindings' in request.args:
- bind_text = {}
- for key in {k: v for k, v in telegramMsgDict.iteritems() if 'bind_msg' in v }:
- if telegramMsgDict[key]['bind_msg'] in bind_text:
- bind_text[telegramMsgDict[key]['bind_msg']].append(key)
- else:
- bind_text[telegramMsgDict[key]['bind_msg']] = [key]
- return json.dumps({
- 'bind_cmd':[k for k, v in self.tcmd.commandDict.iteritems() if 'bind_none' not in v ],
- 'bind_msg':[k for k, v in telegramMsgDict.iteritems() if 'bind_msg' not in v ],
- 'bind_text':bind_text,
- 'no_setting':[k for k, v in telegramMsgDict.iteritems() if 'no_setting' in v ]})
- retChats = {k: v for k, v in self.chats.iteritems() if 'delMe' not in v and k != 'zBOTTOMOFCHATS'}
- for chat in retChats:
- if os.path.isfile(self.get_plugin_data_folder()+"/img/user/pic" +chat+".jpg"):
- retChats[chat]['image'] = "/plugin/telegram/img/user/pic" +chat+".jpg"
- elif int(chat) < 0:
- retChats[chat]['image'] = "/plugin/telegram/img/static/group.jpg"
- else:
- retChats[chat]['image'] = "/plugin/telegram/img/static/default.jpg"
- return json.dumps({'chats':retChats, 'connection_state_str':self.connection_state_str, 'connection_ok':self.connection_ok})
- def on_api_command(self, command, data):
- if command=="testToken":
- self._logger.debug("Testing token {}".format(data['token']))
- try:
- if self._settings.get(["token"]) != data["token"]:
- username = self.test_token(data['token'])
- self._settings.set(['token'], data['token'])
- self.stop_listening() #to start with new token if already running
- self.start_listening()
- return json.dumps({'ok': True, 'connection_state_str': gettext("Token valid for %(username)s.", username=username), 'error_msg': None, 'username': username})
- return json.dumps({'ok': True, 'connection_state_str': gettext("Token valid for %(username)s.", username=self.thread.username), 'error_msg': None, 'username': self.thread.username})
- except Exception as ex:
- return json.dumps({'ok': False, 'connection_state_str': gettext("Error: %(error)s", error=ex), 'username': None, 'error_msg': str(ex)})
- # delete a chat (will not be removed and show up again on octorint restart
- # if save button is not pressed on settings dialog)
- elif command=="delChat":
- strId = str(data['ID'])
- if strId in self.chats:
- del self.chats[strId]
- # do self._settings.save() here???????
- return json.dumps({'chats':{k: v for k, v in self.chats.iteritems() if 'delMe' not in v and k != 'zBOTTOMOFCHATS'}, 'connection_state_str':self.connection_state_str, 'connection_ok':self.connection_ok})
- ##########
- ### Telegram API-Functions
- ##########
- def send_msg(self, message, **kwargs):
- if not self.send_messages:
- return
- kwargs['message'] = message
- try:
- # If it's a regular event notification
- if 'chatID' not in kwargs and 'event' in kwargs:
- self._logger.debug("Send_msg() found event: " + str(kwargs['event']))
- for key in self.chats:
- if key != 'zBOTTOMOFCHATS':
- if self.chats[key]['notifications'][kwargs['event']] and (key not in self.shut_up or self.shut_up[key]==0) and self.chats[key]['send_notifications']:
- kwargs['chatID'] = key
- t = threading.Thread(target=self._send_msg, kwargs = kwargs).run()
- # Seems to be a broadcast
- elif 'chatID' not in kwargs:
- for key in self.chats:
- kwargs['chatID'] = key
- t = threading.Thread(target=self._send_msg, kwargs = kwargs).run()
- # This is a 'editMessageText' message
- elif 'msg_id' in kwargs and kwargs['msg_id'] is not "" and kwargs['msg_id'] is not None:
- t = threading.Thread(target=self._send_edit_msg, kwargs = kwargs).run()
- # direct message or event notification to a chat_id
- else:
- t = threading.Thread(target=self._send_msg, kwargs = kwargs).run()
- except Exception as ex:
- self._logger.debug("Caught an exception in send_msg(): " + str(ex))
- # this method is used to update a message text of a sent message
- # the sent message had to have no_markup = true when calling send_msg() (otherwise it would not work)
- # by setting no_markup = true we got a messageg_id on sending the message which is saved in selfupdateMessageID
- # if this message_id is passed in msg_id to send_msg() then this method will be called
- def _send_edit_msg(self,message="",msg_id="",chatID="", responses= None, inline=True, markup=None,delay=0, **kwargs):
- if not self.send_messages:
- return
- if delay > 0:
- time.sleep(delay)
- try:
- self._logger.debug("Sending a message UPDATE: " + message.replace("\n", "\\n") + " chatID= " + str(chatID))
- data = {}
- data['text'] = message
- data['message_id'] = msg_id
- data['chat_id'] = int(chatID)
- if markup is not None:
- if "HTML" in markup or "Markdown" in markup:
- data["parse_mode"] = markup
- if responses and inline:
- myArr = []
- for k in responses:
- myArr.append(map(lambda x: {"text":x[0],"callback_data":x[1]}, k))
- keyboard = {'inline_keyboard':myArr}
- data['reply_markup'] = json.dumps(keyboard)
- self._logger.debug("SENDING UPDATE: " + str(data))
- req = requests.post(self.bot_url + "/editMessageText", data=data, proxies=self.getProxies())
- if req.headers['content-type'] != 'application/json':
- self._logger.debug(gettext("Unexpected Content-Type. Expected: application/json. Was: %(type)s. Waiting 2 minutes before trying again.", type=req.headers['content-type']))
- return
- myJson = req.json()
- self._logger.debug("REQUEST RES: "+str(myJson))
- if inline:
- self.updateMessageID[chatID] = msg_id
- except Exception as ex:
- self._logger.debug("Caught an exception in _send_edit_msg(): " + str(ex))
- def _send_msg(self, message="", with_image=False, responses=None, delay=0, inline = True, chatID = "", markup=None, showWeb=False, **kwargs):
- if not self.send_messages:
- return
- if delay > 0:
- time.sleep(delay)
- try:
- if with_image:
- if 'event' in kwargs and not self._settings.get(["messages",kwargs['event'],"combined"]):
- args = locals()
- del args['kwargs']['event']
- del args['self']
- args['message'] = ""
- self._logger.debug("Sending image...")
- t = threading.Thread(target=self._send_msg, kwargs = args).run()
- args['message'] = message
- args['with_image'] = False
- self._logger.debug("Sending text...")
- t = threading.Thread(target=self._send_msg, kwargs = args).run()
- return
- self._logger.debug("Sending a message: " + message.replace("\n", "\\n") + " with_image=" + str(with_image) + " chatID= " + str(chatID))
- data = {}
- # Do we want to show web link previews?
- data['disable_web_page_preview'] = not showWeb
- # Do we want the message to be parsed in any markup?
- if markup is not None:
- if "HTML" in markup or "Markdown" in markup:
- data["parse_mode"] = markup
- if responses:
- myArr = []
- for k in responses:
- myArr.append(map(lambda x: {"text":x[0],"callback_data":x[1]}, k))
- keyboard = {'inline_keyboard':myArr}
- data['reply_markup'] = json.dumps(keyboard)
- image_data = None
- if with_image:
- image_data = self.take_image()
- self._logger.debug("data so far: " + str(data))
- if not image_data and with_image:
- message = "[ERR GET IMAGE]\n\n" + message
- r = None
- data['chat_id'] = chatID
- if image_data:
- self._logger.debug("Sending with image.. " + str(chatID))
- files = {'photo':("image.jpg", image_data)}
- if message is not "":
- data['caption'] = message
- r = requests.post(self.bot_url + "/sendPhoto", files=files, data=data, proxies=self.getProxies())
- self._logger.debug("Sending finished. " + str(r.status_code))
- else:
- self._logger.debug("Sending without image.. " + str(chatID))
- data['text'] = message
- r =requests.post(self.bot_url + "/sendMessage", data=data, proxies=self.getProxies())
- self._logger.debug("Sending finished. " + str(r.status_code))
- if r is not None and inline:
- r.raise_for_status()
- myJson = r.json()
- if not myJson['ok']:
- raise NameError("ReqErr")
- if 'message_id' in myJson['result']:
- self.updateMessageID[chatID] = myJson['result']['message_id']
- except Exception as ex:
- self._logger.debug("Caught an exception in _send_msg(): " + str(ex))
- def send_file(self,chat_id,path):
- if not self.send_messages:
- return
- try:
- requests.get(self.bot_url + "/sendChatAction", params = {'chat_id': chat_id, 'action': 'upload_document'}, proxies=self.getProxies())
- files = {'document': open(path, 'rb')}
- r = requests.post(self.bot_url + "/sendDocument", files=files, data={'chat_id':chat_id}, proxies=self.getProxies())
- except Exception as ex:
- pass
- def send_video(self, message, video_file):
- if not self.send_messages:
- return
- files = {'video': open(video_file, 'rb')}
- #r = requests.post(self.bot_url + "/sendVideo", files=files, data={'chat_id':self._settings.get(["chat"]), 'caption':message})
- self._logger.debug("Sending finished. " + str(r.status_code) + " " + str(r.content))
- def get_file(self, file_id):
- if not self.send_messages:
- return
- self._logger.debug("Requesting file with id %s.", file_id)
- r = requests.get(self.bot_url + "/getFile", data={'file_id': file_id}, proxies=self.getProxies())
- # {"ok":true,"result":{"file_id":"BQADAgADCgADrWJxCW_eFdzxDPpQAg","file_size":26,"file_path":"document\/file_3.gcode"}}
- r.raise_for_status()
- data = r.json()
- if not "ok" in data:
- raise Exception(_("Telegram didn't respond well to getFile. The response was: %(response)s", response=r.text))
- url = self.bot_file_url + "/" + data['result']['file_path']
- self._logger.debug("Downloading file: %s", url)
- r = requests.get(url, proxies=self.getProxies())
- r.raise_for_status()
- return r.content
- def get_usrPic(self,chat_id, file_id=""):
- if not self.send_messages:
- return
- self._logger.debug("Requesting Profile Photo for chat_id: " + str(chat_id))
- try:
- if file_id == "":
- if int(chat_id) < 0:
- self._logger.debug("Not able to load group photos. "+ str(chat_id)+" EXIT")
- return
- r = requests.get(self.bot_url + "/getUserProfilePhotos", params = {'limit': 1, "user_id": chat_id}, proxies=self.getProxies())
- r.raise_for_status()
- data = r.json()
- if not "ok" in data:
- raise Exception(_("Telegram didn't respond well to getUserProfilePhoto "+ str(chat_id)+". The response was: %(response)s", response=r.text))
- if data['result']['total_count'] < 1:
- self._logger.debug("NO PHOTOS "+ str(chat_id)+". EXIT")
- return
- r = self.get_file(data['result']['photos'][0][0]['file_id'])
- else:
- r = self.get_file(file_id)
- file_name = self.get_plugin_data_folder() + "/img/user/pic" + str(chat_id) + ".jpg"
- img = Image.open(StringIO.StringIO(r))
- img = img.resize((40, 40), PIL.Image.ANTIALIAS)
- img.save(file_name, format="JPEG")
- self._logger.debug("Saved Photo "+ str(chat_id))
- except Exception as ex:
- self._logger.error("Can't load UserImage: " + str(ex))
- def test_token(self, token=None):
- if not self.send_messages:
- return
- if token is None:
- token = self._settings.get(["token"])
- response = requests.get("https://api.telegram.org/bot" + token + "/getMe", proxies=self.getProxies())
- self._logger.debug("getMe returned: " + str(response.json()))
- self._logger.debug("getMe status code: " + str(response.status_code))
- json = response.json()
- if not 'ok' in json or not json['ok']:
- if json['description']:
- raise(Exception(gettext("Telegram returned error code %(error)s: %(message)s", error=json['error_code'], message=json['description'])))
- else:
- raise(Exception(gettext("Telegram returned an unspecified error.")))
- else:
- return "@" + json['result']['username']
- ##########
- ### Helper methods
- ##########
- def str2bool(self,v):
- return v.lower() in ("yes", "true", "t", "1")
- def set_log_level(self):
- self._logger.setLevel(logging.DEBUG if self._settings.get_boolean(["debug"]) else logging.NOTSET)
- def getProxies(self):
- http_proxy = self._settings.get(["http_proxy"])
- https_proxy = self._settings.get(["https_proxy"])
- return {
- 'http': http_proxy,
- 'https': https_proxy
- }
- # checks if the received command is allowed to execute by the user
- def isCommandAllowed(self, chat_id, from_id, command):
- if 'bind_none' in self.tcmd.commandDict[command]:
- return True
- if command is not None or command is not "":
- if self.chats[chat_id]['accept_commands']:
- if self.chats[chat_id]['commands'][command]:
- return True
- elif int(chat_id) < 0 and self.chats[chat_id]['allow_users']:
- if self.chats[from_id]['commands'][command] and self.chats[from_id]['accept_commands']:
- return True
- elif int(chat_id) < 0 and self.chats[chat_id]['allow_users']:
- if self.chats[from_id]['commands'][command] and self.chats[from_id]['accept_commands']:
- return True
- return False
- # Helper function to handle /editMessageText Telegram API commands
- # see main._send_edit_msg()
- def getUpdateMsgId(self,id):
- uMsgID = ""
- if id in self.updateMessageID:
- uMsgID = self.updateMessageID[id]
- del self.updateMessageID[id]
- return uMsgID
- def take_image(self):
- snapshot_url = self._settings.global_get(["webcam", "snapshot"])
- self._logger.debug("Snapshot URL: " + str(snapshot_url))
- data = None
- if snapshot_url:
- try:
- r = requests.get(snapshot_url)
- data = r.content
- except Exception as e:
- return None
- flipH = self._settings.global_get(["webcam", "flipH"])
- flipV = self._settings.global_get(["webcam", "flipV"])
- rotate= self._settings.global_get(["webcam", "rotate90"])
- if flipH or flipV or rotate:
- image = Image.open(StringIO.StringIO(data))
- if flipH:
- image = image.transpose(Image.FLIP_LEFT_RIGHT)
- if flipV:
- image = image.transpose(Image.FLIP_TOP_BOTTOM)
- if rotate:
- image = image.transpose(Image.ROTATE_90)
- output = StringIO.StringIO()
- image.save(output, format="JPEG")
- data = output.getvalue()
- output.close()
- return data
- def track_action(self, action):
- if not self._settings.get_boolean(["tracking_activated"]):
- return
- if self._settings.get(["tracking_token"]) is None:
- token = "".join(random.choice("abcdef0123456789") for i in xrange(16))
- self._settings.set(["tracking_token"], token)
- params = {
- 'idsite': '3',
- 'rec': '1',
- 'url': 'http://octoprint-telegram/'+action,
- 'action_name': ("%20/%20".join(action.split("/"))),
- '_id': self._settings.get(["tracking_token"]),
- 'uid': self._settings.get(["tracking_token"]),
- 'cid': self._settings.get(["tracking_token"]),
- 'send_image': '0',
- '_idvc': '1',
- 'dimension1': str(self._plugin_version)
- }
- t = threading.Thread(target=requests.get, args=("http://piwik.schlenz.ruhr/piwik.php",), kwargs={'params': params})
- t.daemon = True
- t.run()
- def route_hook(self, server_routes, *args, **kwargs):
- from octoprint.server.util.tornado import LargeResponseHandler, UrlProxyHandler, path_validation_factory
- from octoprint.util import is_hidden_path
- if not os.path.exists(self.get_plugin_data_folder()+"/img"):
- os.mkdir(self.get_plugin_data_folder()+"/img")
- if not os.path.exists(self.get_plugin_data_folder()+"/img/user"):
- os.mkdir(self.get_plugin_data_folder()+"/img/user")
- return [
- (r"/img/user/(.*)", LargeResponseHandler, dict(path=self.get_plugin_data_folder() + r"/img/user/", as_attachment=True,allow_client_caching =False)),
- (r"/img/static/(.*)", LargeResponseHandler, dict(path=self._basefolder + "/static/img/", as_attachment=True,allow_client_caching =True))
- ]
- ########################################
- ########################################
- ### Some methods to check version and
- ### get the right implementation
- ########################################
- ########################################
- # copied from pluginmanager plugin
- def _is_octoprint_compatible(compatibility_entries):
- """
- Tests if the current octoprint_version is compatible to any of the provided ``compatibility_entries``.
- """
- octoprint_version = _get_octoprint_version()
- for octo_compat in compatibility_entries:
- if not any(octo_compat.startswith(c) for c in ("<", "<=", "!=", "==", ">=", ">", "~=", "===")):
- octo_compat = ">={}".format(octo_compat)
- s = next(pkg_resources.parse_requirements("OctoPrint" + octo_compat))
- if octoprint_version in s:
- break
- else:
- return False
- return True
- # copied from pluginmanager plugin
- def _get_octoprint_version():
- from octoprint.server import VERSION
- octoprint_version_string = VERSION
- if "-" in octoprint_version_string:
- octoprint_version_string = octoprint_version_string[:octoprint_version_string.find("-")]
- octoprint_version = pkg_resources.parse_version(octoprint_version_string)
- if isinstance(octoprint_version, tuple):
- # old setuptools
- base_version = []
- for part in octoprint_version:
- if part.startswith("*"):
- break
- base_version.append(part)
- octoprint_version = ".".join(base_version)
- else:
- # new setuptools
- octoprint_version = pkg_resources.parse_version(octoprint_version.base_version)
- return octoprint_version
- # check if we have min version 1.3.0
- # this is important because of WizardPlugin mixin and folders in filebrowser
- def get_implementation_class():
- if not _is_octoprint_compatible(["1.3.0"]):
- return TelegramPlugin(1.2)
- else:
- class NewTelegramPlugin(TelegramPlugin,octoprint.plugin.WizardPlugin):
- def __init__(self,version):
- super(self.__class__, self).__init__(version)
- return NewTelegramPlugin(1.3)
- __plugin_name__ = "Telegram Notifications"
- __plugin_implementation__ = get_implementation_class()
- __plugin_hooks__ = {
- "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information,
- "octoprint.server.http.routes": __plugin_implementation__.route_hook
- }
Add Comment
Please, Sign In to add comment