diff --git a/.gitignore b/.gitignore
index 22475ae..6e69ec7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,4 @@ dist
*.zip
*.*~
*.yaml
-
+dev_utils/NOTES.txt
\ No newline at end of file
diff --git a/README.md b/README.md
index 6987c9b..f2e2874 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
This plugin integrates Telegram Messenger with Octoprint. It sends messages (with photos if available) on print start, end and failure. Also it sends messages during the print at configurable intervals. That way you don't have to remember to regularly have a look at the printing process.
Also, you can control Octoprint via messages (settings, start a print and much more). Send `/status` to get the current printer status or `/abort` to abort the current print. Send `/help` for a list of all recognized commands. You may also use this bot in groups.
-**Lates release: [1.4.0](https://github.com/fabianonline/OctoPrint-Telegram/releases)**
+**Latest release: [1.4.2](https://github.com/fabianonline/OctoPrint-Telegram/releases)**
## Contents
* [Installation](#installation)
@@ -87,13 +87,13 @@ Congratulations! Your printer is now connected to your Telegram bot.
1. Open your Octoprint settings and select *Telegram* on the left.
-2. Send a start message (any message will do) to your new bot. You should receive a message from your bot which tells you something like "Now i know you".
+2. Send a start message (any message will do) to your new bot. You should receive a message from your bot which tells you something like "Now I know you".
3. Now hit the "reload" button under the known chats list. The chat should appear in the list.
-4. Save settings to accept new user(s)/group(s) in list
+4. Save settings to accept new user(s)/group(s) in list.
5. Now reopen octoprit settings and check/set the configurations for new users. (see [below](#users))
@@ -103,6 +103,8 @@ Congratulations! Your printer is now connected to your Telegram bot.
Configuration is done via the Octoprint settings dialog. Note that only admin users will be able to change user or token settings.
### General
+**The paragraph numbers below correspond to the numbers in the picture!**
+
1. Token: Enter your bot token here. You got this from @botfather, when you [created](#create-telegram-bot) your bot there. *(admin users only)*
@@ -125,6 +127,8 @@ Configuration is done via the Octoprint settings dialog. Note that only admin us
### Users
There are users (private chat with single user) or groups you will see in the list of known chats. These settings are only accessible by admin users.
+**The paragraph numbers below correspond to the numbers in the picture!**
+
1. When clicking the *command* icon, a dialog with a list of checkboxes for every accepted command will open. Check a box to enable the user/group to use the command. When done, close the dialog. Don't forget to enable general command execution in `3.`
2. By clicking on the *notification* icon, a dialog with a list of checkboxes for every known notification event will open. Checked notifications will be send to the user/group. When done, close the dialog. Don't forget to activate general notification in `3.`
@@ -159,6 +163,8 @@ There are users (private chat with single user) or groups you will see in the li
### Notifications
In this section you can configure the content of the notification messages.
+**The paragraph numbers below correspond to the numbers in the picture!**
+
1. These three buttons will open dialogs which provide you informations about using markup, variables and emojis in the messages.
2. Enter the text you want to send when the specific event happens.
@@ -194,7 +200,7 @@ In this section you can configure the content of the notification messages.
**`/settings`** - Displays the current notification settings (time and height) and allows you to change them.
-**`/files`** - Lists all the files available for printing in upload folder. No subdirectories are not listed. and lets you download and delete them. You also can view detailed informations of the file like print history.
+**`/files`** - Lists all the files available for printing in upload folder and allows you download and delete them. You also can view detailed informations of the file like print history. If OctoPrint Version is >= 1.3.0, subdirectories are listed and you are able to move/copy files.
**`/print`** - Will open a file dialog showing the files stored in octoprint. You can select a file to print it.
diff --git a/dev_utils/NOTES.txt b/dev_utils/NOTES.txt
index 0f397b7..030ff2e 100644
--- a/dev_utils/NOTES.txt
+++ b/dev_utils/NOTES.txt
@@ -1,3 +1,7 @@
+### README
+ - /sys also system commands. Not only self defined
+ - Latest Version incement
+
### NOW
admin only settings
@@ -7,25 +11,18 @@
### CleanUp
css
- ! readme
code documentation
### Commands
- delete/download files
- File List multipage
message/icon/delay settings
[send video, makes no sense since telegram can't play .mpg]
user settings --> admin
### Features
- SD-Card in Files
@uninstall:
- backup tracking token?
- del user images
combine _send_msg and _edit_send_msg
- /files with folder representation?
- - move files
-
save user settings in files
check webcam integration to hide/prevent "with image"
tab page with individual settings per print
@@ -38,7 +35,6 @@
admin user? --> admin
on "left_chat_member" dont delete --> deactivate
option to deactivate group chats if bot leaves a group to hold the settings if reentering --> deactivate
- ! gEmo() in message texts (.format(**locals()) to make settings send_icon work?
octoprint.util.repeated timer for notification time
check octoprint.util for useful things (dict!)
@@ -49,10 +45,21 @@
! after print start wait for nozzel and bed heated befor starting print notification
times are from octoprint so there has to be own counter??? :(
- check systems.actions[].confirm when doing sysCommand
+ wrong image rotation
+
### Watchlist
TelegramAPI message Group objects (new_chat_icon, left_chat_member, etc.)
update user photos on startUp???
settings.save() on start user?
+ SD-Card in Files
+
+
+
+hash 40
+pathHash 8
+page 2
+opt 4
+/files_ 7
+
diff --git a/octoprint_telegram/__init__.py b/octoprint_telegram/__init__.py
index 756617d..265871a 100644
--- a/octoprint_telegram/__init__.py
+++ b/octoprint_telegram/__init__.py
@@ -1,6 +1,6 @@
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
+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
@@ -164,7 +164,13 @@ def handleDocumentMessage(self, message, chat_id, from_id):
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
- target_filename = "telegram_" + file_name
+ 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'})
@@ -259,7 +265,7 @@ def parseUserData(self, message):
# 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)
+ 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
@@ -347,7 +353,6 @@ class ExitThisLoopException(Exception):
############## THE PLUGIN ##############
########################################
########################################
-
class TelegramPlugin(octoprint.plugin.EventHandlerPlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.StartupPlugin,
@@ -357,7 +362,8 @@ class TelegramPlugin(octoprint.plugin.EventHandlerPlugin,
octoprint.plugin.AssetPlugin
):
- def __init__(self):
+ def __init__(self,version):
+ self.version = float(version)
# for more init stuff see on_after_startup()
self.thread = None
self.bot_url = None
@@ -402,7 +408,7 @@ def __init__(self):
'play': u'\U000025B6',
'stop': u'\U000025FC'
}
- self.emojis.update(telegramEmojiDict)
+ 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):
@@ -443,6 +449,19 @@ def get_template_configs(self):
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 settings versioning
+ # 1.4.2 : 1
+
##########
### Startup/Shutdown API
##########
@@ -506,6 +525,7 @@ def get_settings_version(self):
# 1.3.2 : 2
# 1.3.3 : 2
# 1.4.0 : 3
+ # 1.4.1 : 3
def get_settings_defaults(self):
return dict(
@@ -519,7 +539,8 @@ def get_settings_defaults(self):
chats = {'zBOTTOMOFCHATS':{'send_notifications': False,'accept_commands':False,'private':False}},
debug = False,
send_icon = True,
- image_not_connected = True
+ image_not_connected = True,
+ fileOrder = False
)
def get_settings_preprocessors(self):
@@ -1123,7 +1144,7 @@ def take_image(self):
if flipH or flipV or rotate:
image = Image.open(StringIO.StringIO(data))
if rotate:
- image = image.transpose(Image.ROTATE_90)
+ image = image.transpose(Image.ROTATE_270)
if flipH:
image = image.transpose(Image.FLIP_LEFT_RIGHT)
if flipV:
@@ -1169,8 +1190,68 @@ def route_hook(self, server_routes, *args, **kwargs):
(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__ = TelegramPlugin()
+__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
diff --git a/octoprint_telegram/static/js/telegram.js b/octoprint_telegram/static/js/telegram.js
index f7e8096..f27bac6 100644
--- a/octoprint_telegram/static/js/telegram.js
+++ b/octoprint_telegram/static/js/telegram.js
@@ -277,10 +277,14 @@ $(function() {
if(!response.ok){
$('#teleErrored').addClass("text-error");
$('#teleErrored').removeClass("text-success");
+ $('#teleErrored2').addClass("text-error");
+ $('#teleErrored2').removeClass("text-success");
}
else{
$('#teleErrored').addClass("text-success");
$('#teleErrored').removeClass("text-error");
+ $('#teleErrored2').addClass("text-success");
+ $('#teleErrored2').removeClass("text-error");
}
}
@@ -443,6 +447,6 @@ $(function() {
[ "settingsViewModel" ],
// e.g. #settings_plugin_telegram, #tab_plugin_telegram, ...
- [ '#settings_plugin_telegram' ]
+ [ '#settings_plugin_telegram','#wizard_plugin_telegram']
]);
});
diff --git a/octoprint_telegram/telegramCommands.py b/octoprint_telegram/telegramCommands.py
index b8211d8..3c31e76 100644
--- a/octoprint_telegram/telegramCommands.py
+++ b/octoprint_telegram/telegramCommands.py
@@ -1,5 +1,5 @@
from __future__ import absolute_import
-import logging, sarge, hashlib, datetime,time
+import logging, sarge, hashlib, datetime,time,operator
import octoprint.filemanager
from flask.ext.babel import gettext
from .telegramNotifications import telegramMsgDict
@@ -19,7 +19,8 @@ def __init__(self, main):
self.tuneTemp = [100,100]
self.tempTemp = []
self.conSettingsTemp = []
- #self.dirHashDict = {}
+ self.dirHashDict = {}
+ self.tmpFileHash = ""
self.commandDict = {
"Yes": {'cmd': self.cmdYes, 'bind_none': True},
"No": {'cmd': self.cmdNo, 'bind_none': True},
@@ -40,6 +41,8 @@ def __init__(self, main):
'/tune': {'cmd': self.cmdTune, 'param': True},
'/help': {'cmd': self.cmdHelp, 'bind_none': True}
}
+
+
############################################################################################
# COMMAND HANDLERS
############################################################################################
@@ -106,11 +109,11 @@ def cmdSettings(self,chat_id,from_id,cmd,parameter):
self.main.send_msg(msg,chatID=chat_id,responses=keys,msg_id = self.main.getUpdateMsgId(chat_id),markup="Markdown")
else:
self.SettingsTemp = [self.main._settings.get_float(["notification_height"]),self.main._settings.get_float(["notification_time"])]
- msg = self.gEmo('settings') + gettext(" Current notification settings are:\n\n"+self.gEmo('height')+" Height: %(height).2fmm\n\n"+self.gEmo('clock')+" Time: %(time)dmin",
+ msg = self.gEmo('settings') + gettext(" *Current notification settings are:*\n\n"+self.gEmo('height')+" Height: %(height).2fmm\n\n"+self.gEmo('clock')+" Time: %(time)dmin",
height=self.main._settings.get_float(["notification_height"]),
time=self.main._settings.get_int(["notification_time"]))
msg_id=self.main.getUpdateMsgId(chat_id) if parameter == "back" else ""
- self.main.send_msg(msg, responses=[[[self.main.emojis['height']+gettext(" Set height"),"/settings_h"], [self.main.emojis['clock']+gettext(" Set time"),"/settings_t"], [self.main.emojis['cross mark']+gettext(" Close"),"No"]]],chatID=chat_id,msg_id=msg_id)
+ self.main.send_msg(msg, responses=[[[self.main.emojis['height']+gettext(" Set height"),"/settings_h"], [self.main.emojis['clock']+gettext(" Set time"),"/settings_t"]], [[self.main.emojis['cross mark']+gettext(" Close"),"No"]]],chatID=chat_id,msg_id=msg_id,markup="Markdown")
############################################################################################
def cmdAbort(self,chat_id,from_id,cmd,parameter):
if parameter and parameter == "stop":
@@ -186,37 +189,66 @@ def cmdPrint(self,chat_id,from_id,cmd,parameter):
############################################################################################
def cmdFiles(self,chat_id,from_id,cmd,parameter):
if parameter:
- par = parameter.split('|')
- loc = par[0]
- hash = par[2] if len(par) > 2 else ""
- opt = par[3] if len(par) > 3 else ""
- page = int(par[1])
- if len(par) < 3:
- self.fileList(loc,page,cmd,chat_id)
- elif len(par) < 4:
- self.fileDetails(loc,page,cmd,hash,chat_id,from_id)
+ par = parameter.split('|')
+ pathHash = par[0]
+ page = int(par[1])
+ fileHash = par[2] if len(par) > 2 else ""
+ opt = par[3] if len(par) > 3 else ""
+ if fileHash == "" and opt =="":
+ self.fileList(pathHash,page,cmd,chat_id)
+ elif opt == "":
+ self.fileDetails(pathHash,page,cmd,fileHash,chat_id,from_id)
else:
- self.fileOption(loc,page,cmd,hash,opt,chat_id,from_id)
+ if opt.startswith("dir"):
+ self.fileList(fileHash,0,cmd,chat_id)
+ else:
+ self.fileOption(pathHash,page,cmd,fileHash,opt,chat_id,from_id)
else:
storages = self.main._file_manager.list_files(recursive=False)
- #self.generate_dir_hash_dict()
if len(storages.keys()) < 2:
self.main.send_msg("Loading files...",chatID=chat_id)
- self.cmdFiles(chat_id,from_id,cmd,str(storages.keys()[0])+"|0")
+ self.generate_dir_hash_dict()
+ self.cmdFiles(chat_id,from_id,cmd,self.hashMe(str(storages.keys()[0]+"/"),8)+"|0")
else:
+ self.generate_dir_hash_dict()
keys=[]
- keys.extend([([k,(cmd+"_"+k+"|0")] for k in storages)])
+ keys.extend([([k,(cmd+"_"+self.hashMe(k,8)+"/|0")] for k in storages)])
keys.append([[self.main.emojis['cross mark']+" Close","No"]])
msg_id=self.main.getUpdateMsgId(chat_id) if parameter == "back" else ""
self.main.send_msg(self.gEmo('save') + " *Select Storage*",chatID=chat_id,markup="Markdown",responses=keys,msg_id=msg_id)
############################################################################################
def cmdUpload(self,chat_id,from_id,cmd,parameter):
- self.main.send_msg(self.gEmo('info') + " To upload a gcode file, just send it to me.",chatID=chat_id)
+ self.main.send_msg(self.gEmo('info') + " To upload a gcode file, just send it to me.\nThe file will be stored in 'TelegramPlugin' folder.",chatID=chat_id)
############################################################################################
def cmdSys(self,chat_id,from_id,cmd,parameter):
if parameter and parameter != "back":
params = parameter.split('_')
- if params[0] == "do":
+ if params[0] == "sys":
+ if params[1] != "do":
+ self.main.send_msg(self.gEmo('question') +" *"+ params[1]+"*\nExecute system command?",responses=[[[self.main.emojis['check']+gettext(" Execute"),"/sys_sys_do_"+params[1]], [self.main.emojis['leftwards arrow with hook']+ gettext(" Back"),"/sys_back"]]],chatID=chat_id, msg_id = self.main.getUpdateMsgId(chat_id), markup="Markdown")
+ return
+ try:
+ if params[2] == "Restart OctoPrint":
+ myCmd = self.main._settings.global_get(['server','commands','serverRestartCommand'])
+ elif params[2] == "Reboot System":
+ myCmd = self.main._settings.global_get(['server','commands','systemRestartCommand'])
+ elif params[2] == "Shutdown System":
+ myCmd = self.main._settings.global_get(['server','commands','systemShutdownCommand'])
+
+ p = sarge.run(myCmd, stderr=sarge.Capture(), shell=True, async=False)
+
+ if p.returncode != 0:
+ returncode = p.returncode
+ stderr_text = p.stderr.text
+ self._logger.warn("Command failed with return code %i: %s" % (returncode, stderr_text))
+ self.main.send_msg(self.gEmo('warning') + " Command failed with return code %i: %s" % (returncode, stderr_text),chatID=chat_id, msg_id = self.main.getUpdateMsgId(chat_id))
+ return
+ self.main.send_msg(self.gEmo('check') + " System Command executed." ,chatID=chat_id, msg_id = self.main.getUpdateMsgId(chat_id))
+ except Exception, e:
+ self._logger.warn("Command failed: %s" % e)
+ self.main.send_msg(self.gEmo('warning') + " Command failed with exception: %s!" % e,chatID = chat_id, msg_id = self.main.getUpdateMsgId(chat_id))
+ return
+ elif params[0] == "do":
parameter = params[1]
else:
parameter = params[0]
@@ -250,12 +282,10 @@ def cmdSys(self,chat_id,from_id,cmd,parameter):
return
else:
message = self.gEmo('info') + " The following System Commands are known."
- empty = True
keys = []
tmpKeys = []
i = 1
for action in self.main._settings.global_get(['system','actions']):
- empty = False
if action['action'] != "divider":
tmpKeys.append([str(action['name']),"/sys_"+self.hashMe(action['action'])])
if i%2 == 0:
@@ -264,8 +294,9 @@ def cmdSys(self,chat_id,from_id,cmd,parameter):
i += 1
if len(tmpKeys) > 0:
keys.append(tmpKeys)
+ keys.append([["Restart OctoPrint","/sys_sys_Restart OctoPrint"]])
+ keys.append([["Reboot System","/sys_sys_Reboot System"],["Shutdown System","/sys_sys_Shutdown System"]])
keys.append([[self.main.emojis['cross mark']+gettext(" Close"),"No"]])
- if empty: message += "\n\n"+self.gEmo('warning')+" No System Commands found..."
msg_id=self.main.getUpdateMsgId(chat_id) if parameter == "back" else ""
self.main.send_msg(message,chatID=chat_id,responses=keys,msg_id=msg_id)
############################################################################################
@@ -517,9 +548,29 @@ def cmdHelp(self,chat_id,from_id,cmd,parameter):
############################################################################################
# FILE HELPERS
############################################################################################
- def fileList(self,loc,page,cmd,chat_id,wait = 0):
- storage = self.main._file_manager.list_files(recursive=False)
- files = self.list_files(storage[loc],loc,page,cmd)
+ def fileList(self,pathHash,page,cmd,chat_id,wait = 0):
+ fullPath = self.dirHashDict[pathHash]
+ storageKeys = self.main._file_manager.list_files(recursive=False).keys()
+ dest = fullPath.split("/")[0]
+ pathWoDest = "/".join(fullPath.split("/")[1:]) if len(fullPath.split("/")) > 1 else fullPath
+ path = "/".join(fullPath.split("/")[1:])
+ fileList = self.main._file_manager.list_files(path = path, destinations = dest, recursive=False)
+ files = fileList[dest]
+ arrayD = []
+ if self.main.version >= 1.3:
+ M = {k:v for k,v in files.iteritems() if v['type'] == "folder"}
+ for key in M:
+ arrayD.append([self.main.emojis['open file folder']+" "+key,cmd+"_"+pathHash+"|0|"+self.hashMe(fullPath+key+"/",8)+"|dir"])
+ array = []
+ L = {k:v for k,v in files.iteritems() if v['type']=="machinecode"}
+ for key,val in sorted(L.iteritems(), key=lambda x: x[1]['date'] , reverse=True):
+ array.append([self.main.emojis['page facing up']+" "+('.').join(key.split('.')[:-1]),cmd+"_" + pathHash + "|"+str(page)+"|"+ self.hashMe(pathWoDest + key + files[key]['hash'])])
+ arrayD = sorted(arrayD)
+ if not self.main._settings.get_boolean(["fileOrder"]):
+ arrayD.extend(sorted(array))
+ else:
+ arrayD.extend(array)
+ files = arrayD
pageDown = page-1 if page > 0 else 0
pageUp = page+1 if len(files)-(page+1)*10 > 0 else page
keys = []
@@ -534,41 +585,63 @@ def fileList(self,loc,page,cmd,chat_id,wait = 0):
if len(tmpKeys):
keys.append(tmpKeys)
tmpKeys = []
- backBut = [[self.main.emojis['cross mark']+" Close","No"]] if len(storage.keys()) < 2 else [[self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_back"],["Close","No"]]
+ backBut = [[self.main.emojis['settings'],cmd+"_" + pathHash + "|"+str(page)+"|0|s"],[self.main.emojis['cross mark']+" Close","No"]] if len(fullPath.split("/")) < 3 else [[self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_"+self.hashMe("/".join(fullPath.split("/")[:-2])+"/",8)+"|0"],[self.main.emojis['settings'],cmd+"_" + pathHash + "|"+str(page)+"|0|s"],[self.main.emojis['cross mark']+" Close","No"]]
if pageDown != pageUp:
if pageDown != page:
- tmpKeys.append([self.main.emojis['black left-pointing triangle'],cmd+"_"+loc+"|"+ str(pageDown)])
- tmpKeys.extend(backBut)
+ tmpKeys.append([self.main.emojis['black left-pointing triangle'],cmd+"_"+pathHash+"|"+ str(pageDown)])
if pageUp != page:
- tmpKeys.append([self.main.emojis['black right-pointing triangle'],cmd+"_"+loc+"|"+str(pageUp)])
+ tmpKeys.append([self.main.emojis['black right-pointing triangle'],cmd+"_"+pathHash+"|"+str(pageUp)])
+ tmpKeys.extend(backBut)
+
else:
tmpKeys.extend(backBut)
keys.append(tmpKeys)
pageStr = str(page+1)+"/"+str(len(files)/10 + (1 if len(files)%10 > 0 else 0))
- self.main.send_msg(self.gEmo('save') + " *Files on "+loc+" storage* \["+pageStr+"]",chatID=chat_id,markup="Markdown",responses=keys,msg_id = self.main.getUpdateMsgId(chat_id),delay=wait)
+ self.main.send_msg(self.gEmo('save') + " Files in */"+pathWoDest[:-1]+"* \["+pageStr+"]",chatID=chat_id,markup="Markdown",responses=keys,msg_id = self.main.getUpdateMsgId(chat_id),delay=wait)
############################################################################################
- def fileDetails(self,loc,page,cmd,hash,chat_id,from_id,wait=0):
- dest, path, file = self.find_file_by_hash(hash)
+ def fileDetails(self,pathHash,page,cmd,fileHash,chat_id,from_id,wait=0):
+ dest, path, file = self.find_file_by_hash(fileHash)
+ self.tmpFileHash = ""
meta = self.main._file_manager.get_metadata(dest,path)
msg = self.gEmo("info") + " File Informations\n\n"
msg += "Name: " + path
msg += "\nSize: " + self.formatSize(file['size'])
+ filaLen = 0
+ printTime = 0
if 'analysis' in meta:
if 'filament' in meta['analysis']:
msg += "\nFilament: "
filament = meta['analysis']['filament']
- if len(filament) == 1:
+ if len(filament) == 1 and 'length' in filament['tool0']:
msg += self.formatFilament(filament['tool0'])
+ filaLen += float(filament['tool0']['length'])
else:
for key in sorted(filament):
- msg += "\n "+str(key)+": "+ self.formatFilament(filament[key])
+ if 'length' in filament[key]:
+ msg += "\n "+str(key)+": "+ self.formatFilament(filament[key])
+ filaLen += float(filament[key]['length'])
if 'estimatedPrintTime' in meta['analysis']:
msg += "\nPrint Time: "+ self.formatFuzzyPrintTime(meta['analysis']['estimatedPrintTime'])
- keyPrint = [self.main.emojis['rocket']+" Print","/print_"+hash]
- keyDetails = [self.main.emojis['left-pointing magnifying glass']+" Details",cmd+"_"+loc+"|"+str(page)+"|"+hash+"|info"]
- keyDownload = [self.main.emojis['save']+" Download",cmd+"_"+loc+"|"+str(page)+"|"+hash+"|dl"]
- keyDelete = [self.main.emojis['error']+" Delete",cmd+"_"+loc+"|"+str(page)+"|"+hash+"|del"]
- keyBack = [self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_"+loc+"|"+str(page)]
+ printTime = meta['analysis']['estimatedPrintTime']
+ if self.main._plugin_manager.get_plugin("cost"):
+ if printTime != 0 and filaLen != 0:
+ cpH = self.main._settings.global_get_float(["plugins","cost","cost_per_hour"])
+ cpM = self.main._settings.global_get_float(["plugins","cost","cost_per_meter"])
+ curr = self.main._settings.global_get(["plugins","cost","currency"])
+ try:
+ curr = curr.decode("utf-8")
+ except Exception, ex:
+ pass
+ msg += "\nCost: "+curr+"%.02f " % ((filaLen/1000) * cpM + (printTime/3600) * cpH)
+ else:
+ msg += "\nCost: -"
+ keyPrint = [self.main.emojis['rocket']+" Print","/print_"+fileHash]
+ keyDetails = [self.main.emojis['left-pointing magnifying glass']+" Details",cmd+"_"+pathHash+"|"+str(page)+"|"+fileHash+"|inf"]
+ keyDownload = [self.main.emojis['save']+" Download",cmd+"_"+pathHash+"|"+str(page)+"|"+fileHash+"|dl"]
+ keyMove = [self.main.emojis['black scissors']+" Move",cmd+"_"+pathHash+"|"+str(page)+"|"+fileHash+"|m"]
+ keyCopy = [self.main.emojis['clipboard']+" Copy",cmd+"_"+pathHash+"|"+str(page)+"|"+fileHash+"|c"]
+ keyDelete = [self.main.emojis['error']+" Delete",cmd+"_"+pathHash+"|"+str(page)+"|"+fileHash+"|d"]
+ keyBack = [self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_"+pathHash+"|"+str(page)]
keysRow = []
keys = []
chkID = chat_id
@@ -578,32 +651,57 @@ def fileDetails(self,loc,page,cmd,hash,chat_id,from_id,wait=0):
keys.append(keysRow)
keysRow = []
if self.main.isCommandAllowed(chat_id, from_id, "/files"):
- if loc == octoprint.filemanager.FileDestinations.LOCAL:
- keysRow.append(keyDownload)
+ if self.main.version >= 1.3:
+ keysRow.append(keyMove)
+ keysRow.append(keyCopy)
keysRow.append(keyDelete)
+ keys.append(keysRow)
+ keysRow = []
+ if self.dirHashDict[pathHash].split("/")[0] == octoprint.filemanager.FileDestinations.LOCAL:
+ keysRow.append(keyDownload)
keysRow.append(keyBack)
keys.append(keysRow)
self.main.send_msg(msg,chatID=chat_id,markup="HTML",responses=keys,msg_id = self.main.getUpdateMsgId(chat_id),delay=wait)
############################################################################################
def fileOption(self,loc,page,cmd,hash,opt,chat_id,from_id):
- dest, path, file = self.find_file_by_hash(hash)
- meta = self.main._file_manager.get_metadata(dest,path)
- if opt.startswith("info"):
+ if opt != "m_m" and opt != "c_c":
+ dest, path, file = self.find_file_by_hash(hash)
+ meta = self.main._file_manager.get_metadata(dest,path)
+ if opt.startswith("inf"):
msg = self.gEmo("info") + " Detailed File Informations\n\n"
msg += "Name: " + path
msg += "\nSize: " + self.formatSize(file['size'])
msg += "\nUploaded: " + datetime.datetime.fromtimestamp(file['date']).strftime('%Y-%m-%d %H:%M:%S')
+ filaLen = 0
+ printTime = 0
if 'analysis' in meta:
if 'filament' in meta['analysis']:
msg += "\nFilament: "
filament = meta['analysis']['filament']
- if len(filament) == 1:
+ if len(filament) == 1 and 'length' in filament['tool0']:
msg += self.formatFilament(filament['tool0'])
+ filaLen += float(filament['tool0']['length'])
else:
for key in sorted(filament):
- msg += "\n "+str(key)+": "+ self.formatFilament(filament[key])
+ if 'length' in filament[key]:
+ msg += "\n "+str(key)+": "+ self.formatFilament(filament[key])
+ filaLen += float(filament[key]['length'])
if 'estimatedPrintTime' in meta['analysis']:
msg += "\nEstimated Print Time: "+ self.formatDuration(meta['analysis']['estimatedPrintTime'])
+ printTime = float(meta['analysis']['estimatedPrintTime'])
+ if self.main._plugin_manager.get_plugin("cost"):
+ if printTime != 0 and filaLen != 0:
+ cpH = self.main._settings.global_get_float(["plugins","cost","cost_per_hour"])
+ cpM = self.main._settings.global_get_float(["plugins","cost","cost_per_meter"])
+ curr = self.main._settings.global_get(["plugins","cost","currency"])
+ try:
+ curr = curr.decode("utf-8")
+ except Exception, ex:
+ pass
+ self._logger.debug("AF TRY")
+ msg += "\nCost: "+curr+"%.02f " % ((filaLen/1000) * cpM + (printTime/3600) * cpH)
+ else:
+ msg += "\nCost: -"
if 'statistics' in meta:
if 'averagePrintTime' in meta['statistics']:
msg += "\nAverage Print Time:"
@@ -640,49 +738,157 @@ def fileOption(self,loc,page,cmd,hash,opt,chat_id,from_id):
self.fileDetails(loc,page,cmd,hash,chat_id,from_id,wait=3)
else:
self.main.send_file(chat_id,self.main._file_manager.path_on_disk(dest,path))
- elif opt.startswith("del"):
+ elif opt.startswith("m"):
msg_id = self.main.getUpdateMsgId(chat_id)
- if opt == "del_do":
- try:
- self.main._file_manager.remove_file(dest, path)
+ if opt == "m_m":
+ destM, pathM, fileM = self.find_file_by_hash(self.tmpFileHash)
+ targetPath = self.dirHashDict[hash]
+ cpRes = self.fileCopyMove(destM,"move",pathM,"/".join(targetPath.split("/")[1:]))
+ self._logger.debug("OUT MOVE: "+cpRes)
+ if cpRes == "GOOD":
+ self.main.send_msg(self.gEmo('info')+" File "+pathM+" moved",chatID=chat_id,msg_id=msg_id)
+ self.fileList(loc,page,cmd,chat_id,wait = 3)
+ else:
+ self.main.send_msg(self.gEmo('warning')+"FAILED: Move file "+pathM+"\nReason: "+cpRes,chatID=chat_id,msg_id=msg_id)
+ self.fileDetails(loc,page,cmd,self.tmpFileHash,chat_id,from_id,wait = 3)
+ else:
+ keys = [[[self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_" + loc + "|"+str(page)+"|"+ hash]]]
+ self.tmpFileHash = hash
+ for key,val in sorted(self.dirHashDict.items(), key = operator.itemgetter(1)):
+ keys.append([[self.main.emojis['open file folder']+" "+self.dirHashDict[key],cmd+"_" + loc + "|"+str(page)+"|"+ key + "|m_m"]])
+ self.main.send_msg(self.gEmo('question') + " *Choose destination to move file*",chatID=chat_id,responses=keys,msg_id=msg_id,markup="Markdown")
+
+ elif opt.startswith("c"):
+ msg_id = self.main.getUpdateMsgId(chat_id)
+ if opt == "c_c":
+ destM, pathM, fileM = self.find_file_by_hash(self.tmpFileHash)
+ targetPath = self.dirHashDict[hash]
+ cpRes = self.fileCopyMove(destM,"copy",pathM,"/".join(targetPath.split("/")[1:]))
+ if cpRes == "GOOD":
+ self.main.send_msg(self.gEmo('info')+" File "+pathM+" copied",chatID=chat_id,msg_id=msg_id)
+ self.fileList(loc,page,cmd,chat_id,wait = 3)
+ else:
+ self.main.send_msg(self.gEmo('warning')+"FAILED: Copy file "+pathM+"\nReason: "+cpRes,chatID=chat_id,msg_id=msg_id)
+ self.fileDetails(loc,page,cmd,self.tmpFileHash,chat_id,from_id,wait = 3)
+ else:
+ keys = [[[self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_" + loc + "|"+str(page)+"|"+ hash]]]
+ self.tmpFileHash = hash
+ for key,val in sorted(self.dirHashDict.items(), key = operator.itemgetter(1)):
+ keys.append([[self.main.emojis['open file folder']+" "+self.dirHashDict[key],cmd+"_" + loc + "|"+str(page)+"|"+ key + "|c_c"]])
+ self.main.send_msg(self.gEmo('question') + " *Choose destination to copy file*",chatID=chat_id,responses=keys,msg_id=msg_id,markup="Markdown")
+
+ elif opt.startswith("d"):
+ msg_id = self.main.getUpdateMsgId(chat_id)
+ if opt == "d_d":
+ delRes = self.fileDelete(dest, path)
+ if delRes == "GOOD" :
self.main.send_msg(self.gEmo('info')+" File "+path+" deleted",chatID=chat_id,msg_id=msg_id)
self.fileList(loc,page,cmd,chat_id,wait = 3)
- except Exception as ex:
- self.main.send_msg(self.gEmo('warning')+"FAILED: Delete file "+path,chatID=chat_id,msg_id=msg_id)
+ else:
+ self.main.send_msg(self.gEmo('warning')+"FAILED: Delete file "+path+"\nReason: "+delRes,chatID=chat_id,msg_id=msg_id)
self.fileList(loc,page,cmd,chat_id,wait = 3)
else:
- keys = [[[self.main.emojis['check']+" Yes",cmd+"_" + loc + "|"+str(page)+"|"+ hash+"|del_do"],[self.main.emojis['cross mark']+" No",cmd+"_" + loc + "|"+str(page)+"|"+ hash]]]
- self.main.send_msg(self.gEmo('warning')+" Delete "+path+" ?",chatID=chat_id,responses=keys,msg_id=msg_id)
-
+ keys = [[[self.main.emojis['check']+" Yes",cmd+"_" + loc + "|"+str(page)+"|"+ hash+"|d_d"],[self.main.emojis['cross mark']+" No",cmd+"_" + loc + "|"+str(page)+"|"+ hash]]]
+ self.main.send_msg(self.gEmo('warning')+" Delete "+path+" ?",chatID=chat_id,responses=keys,msg_id=msg_id)
+ elif opt.startswith("s"):
+ if opt == "s_n":
+ self.main._settings.set_boolean(["fileOrder"],False)
+ self.fileList(loc,page,cmd,chat_id)
+ elif opt == "s_d":
+ self.main._settings.set_boolean(["fileOrder"],True)
+ self.fileList(loc,page,cmd,chat_id)
+ else:
+ keys = [[[self.main.emojis['input symbol for latin letters']+" By name",cmd+"_"+loc+"|"+str(page)+"|"+hash+"|s_n"], [self.main.emojis['tear-off calendar']+" By date",cmd+"_"+loc+"|"+str(page)+"|"+hash+"|s_d"]],[ [self.main.emojis['leftwards arrow with hook']+" Back",cmd+"_"+loc+"|"+str(page)]]]
+ self.main.send_msg(self.gEmo('question') + " *Choose sorting order of files*",chatID=chat_id,markup="Markdown",responses=keys,msg_id = self.main.getUpdateMsgId(chat_id))
+### From filemanager plugin - https://github.com/Salandora/OctoPrint-FileManager/blob/master/octoprint_filemanager/__init__.py
############################################################################################
- def list_files(self, tree, source,page,cmd,base=""):
- array = []
- #arrayD = []
+ def fileCopyMove(self, target, command, source, destination):
+ from octoprint.server.api.files import _verifyFolderExists, _verifyFileExists
+ if not _verifyFileExists(target, source) and not _verifyFolderExists(target, source):
+ return "Source does not exist"
+
+ if _verifyFolderExists(target, destination):
+ path, name = self.main._file_manager.split_path(target, source)
+ destination = self.main._file_manager.join_path(target, destination, name)
+
+ if _verifyFileExists(target, destination) or _verifyFolderExists(target, destination):
+ return "Destination does not exist"
+
+ if command == "copy":
+ if self.main._file_manager.file_exists(target, source):
+ self.main._file_manager.copy_file(target, source, destination)
+ elif self.main._file_manager.folder_exists(target, source):
+ self.main._file_manager.copy_folder(target, source, destination)
+ elif command == "move":
+ from octoprint.server.api.files import _isBusy
+ if _isBusy(target, source):
+ return "You can't move a file while it is in use"
+
+ # deselect the file if it's currently selected
+ from octoprint.server.api.files import _getCurrentFile
+ currentOrigin, currentFilename = _getCurrentFile()
+ if currentFilename is not None and source == currentFilename:
+ self.main._printer.unselect_file()
+
+ if self.main._file_manager.file_exists(target, source):
+ self.main._file_manager.move_file(target, source, destination)
+ elif self.main._file_manager.folder_exists(target, source):
+ self.main._file_manager.move_folder(target, source, destination)
+ return "GOOD"
+### From filemanager plugin - https://github.com/Salandora/OctoPrint-FileManager/blob/master/octoprint_filemanager/__init__.py
+############################################################################################
+ def fileDelete(self, target, source):
+ from octoprint.server.api.files import _verifyFolderExists, _verifyFileExists, _isBusy
+
+ # prohibit deleting or moving files that are currently in use
+ from octoprint.server.api.files import _getCurrentFile
+ currentOrigin, currentFilename = _getCurrentFile()
+
+ if _verifyFileExists(target, source):
+ from octoprint.server.api.files import _isBusy
+ if _isBusy(target, source):
+ return "Trying to delete a file that is currently in use"
+
+ # deselect the file if it's currently selected
+ if currentFilename is not None and source == currentFilename:
+ self.main._printer.unselect_file()
+
+ # delete it
+ if target == octoprint.filemanager.FileDestinations.SDCARD:
+ self.main._printer.delete_sd_file(source)
+ else:
+ self.main._file_manager.remove_file(target, source)
+ elif _verifyFolderExists(target, source):
+ if not target in [octoprint.filemanager.FileDestinations.LOCAL]:
+ return "Unknown target"
+
+ folderpath = source
+ if _isBusy(target, folderpath):
+ return "Trying to delete a folder that contains a file that is currently in use"
+
+ # deselect the file if it's currently selected
+ if currentFilename is not None and self._file_manager.file_in_path(target, folderpath, currentFilename):
+ self.main._printer.unselect_file()
+
+ # delete it
+ self.main._file_manager.remove_folder(target, folderpath)
+ return "GOOD"
+############################################################################################
+ def generate_dir_hash_dict(self):
+ self.dirHashDict = {}
+ tree = self.main._file_manager.list_files(recursive=True)
for key in tree:
- #if tree[key]['type']=="folder":
- #arrayD.append([self.main.emojis['file folder']+" "+key,"/files_"+source+"|"+str(page)+"|"+self.hashMe(base+key+"/")+"|d"])
- if tree[key]['type']=="machinecode":
- array.append([self.main.emojis['page facing up']+" "+('.').join(key.split('.')[:-1]),cmd+"_" + source + "|"+str(page)+"|"+ tree[key]['hash']])
- #arrayD.extend(array)
- return sorted(array)
-### Some preparation for Dir suppurt (comming in octoprint 1.3.0)
-############################################################################################
- #def generate_dir_hash_dict(self):
- #self.dirHashDict = {}
- #tree = self.main._file_manager.list_files(recursive=True)
- #for key in tree:
- #self.dirHashDict.update({key:self.generate_dir_hash_dict_recursively(tree[key],key)})
-############################################################################################
- #def generate_dir_hash_dict_recursively(self,tree,loc,base=""):
- #myDict = {}
- #self._logger.debug("SATRT RECUR")
- #for key in tree:
- #self._logger.debug("TEST: "+key)
- #if tree[key]['type']=="folder":
- #self._logger.debug("FOLER: "+key)
- #myDict.update({self.hashMe(loc+base+key+"/"):base+key+"/"})
- #self.generate_dir_hash_dict_recursively(tree[key]['children'],base+key+"/")
- #return myDict
+ self.dirHashDict.update({str(self.hashMe(key+"/",8)):key+"/"})
+ self.dirHashDict.update(self.generate_dir_hash_dict_recursively(tree[key],key+"/"))
+ self._logger.debug(str(self.dirHashDict))
+############################################################################################
+ def generate_dir_hash_dict_recursively(self,tree,loc):
+ myDict = {}
+ for key in tree:
+ if tree[key]['type']=="folder":
+ myDict.update({self.hashMe(loc+key+"/",8):loc+key+"/"})
+ self.dirHashDict.update(self.generate_dir_hash_dict_recursively(tree[key]['children'],loc+key+"/"))
+ return myDict
############################################################################################
def find_file_by_hash(self, hash):
tree = self.main._file_manager.list_files(recursive=True)
@@ -699,7 +905,7 @@ def find_file_by_hash_recursively(self, tree, hash, base=""):
if result is not None:
return result, file
continue
- if tree[key]['hash'].startswith(hash):
+ if self.hashMe(base+tree[key]['name']+tree[key]['hash']).startswith(hash):
return base+key, tree[key]
return None, None
############################################################################################
@@ -833,7 +1039,7 @@ def ConAuto(self,chat_id,parameter):
def ConConnect(self,chat_id,parameter):
if parameter:
if parameter[0] == "a":
- self.conSettingsTemp.extend(["Auto",0,self.main._printer_profile_manager.get_default()['id']])
+ self.conSettingsTemp.extend([None,None,None])
elif parameter[0] == "d":
self.conSettingsTemp.extend([self.main._settings.global_get(["serial","port"]),self.main._settings.global_get(["serial","baudrate"]),self.main._printer_profile_manager.get_default()])
elif parameter[0] == "p" and len(parameter) < 2:
@@ -853,7 +1059,7 @@ def ConConnect(self,chat_id,parameter):
self.main._printer.connect(port=self.conSettingsTemp[0],baudrate=self.conSettingsTemp[1],profile=self.conSettingsTemp[2])
self.conSettingsTemp = []
con = self.main._printer.get_current_connection()
- waitStates=["Offline","Detecting baudrate","Connecting"]
+ waitStates=["Offline","Detecting baudrate","Connecting","Opening serial port"]
while any(s in con[0] for s in waitStates):
con = self.main._printer.get_current_connection()
self._logger.debug("EXIT WITH: "+str(con[0]))
diff --git a/octoprint_telegram/templates/telegram_settings.jinja2 b/octoprint_telegram/templates/telegram_settings.jinja2
index d7e4fa9..c456aba 100644
--- a/octoprint_telegram/templates/telegram_settings.jinja2
+++ b/octoprint_telegram/templates/telegram_settings.jinja2
@@ -190,6 +190,16 @@