windows toast notifications with action using python winrt module - python

I've been trying to get this working for a long time now and i always get stuck at detecting button presses. I made a toast notification that looks like this:
Here's my code :
import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom
app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'
#create notifier
nManager = notifications.ToastNotificationManager
notifier = nManager.create_toast_notifier(app)
#define your notification as string
tString = """
<toast>
<visual>
<binding template='ToastGeneric'>
<text>New notifications</text>
<text>Text</text>
<text>Second text</text>
</binding>
</visual>
<actions>
<action
content="test1"
arguments="test1"
activationType="backround"/>
<action
content="test2"
arguments="test2"
activationType="backround"/>
</actions>
</toast>
"""
print(type(notifier.update))
#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)
#display notification
notifier.show(notifications.ToastNotification(xDoc))
I don't know how to detect button presses
the only thing i figured out is that if i change the argument of the buttons to a link like this:
arguments="https://google.com"
then it will open it
Is there any way i could implement this? or is there documentation for this XML format these toast notifications use. That explains how arguments work?

Alright so I know It's been a while, but I was trying to figure out the same thing and I couldn't find a good, conclusive answer anywhere. I've finally gotten something to work with WinRT in Python 3.9 so I wanted there to be an answer somewhere that people could find!
So to start, I'm not intimately familiar with how the 'arguments' attribute works, but it doesn't seem to be important for at least simple use cases. Most of what I know came from the Windows Toast docs. Here's some code that should produce a notification and open your Documents folder when you click the button. I got a headstart from an answer in this thread but it was missing some very important steps.
import os,sys,time
import subprocess
import threading
import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom
# this is not called on the main thread!
def handle_activated(sender, _):
path = os.path.expanduser("~\Documents")
subprocess.Popen('explorer "{}"'.format(path))
def test_notification():
#define your notification as
tString = """
<toast duration="short">
<visual>
<binding template='ToastGeneric'>
<text>New notifications</text>
<text>Text</text>
<text>Second text</text>
</binding>
</visual>
<actions>
<action
content="Test Button!"
arguments=""
activationType="foreground"/>
</actions>
</toast>
"""
#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)
notification = notifications.ToastNotification(xDoc)
# add the activation token.
notification.add_activated(handle_activated)
#create notifier
nManager = notifications.ToastNotificationManager
#link it to your Python executable (or whatever you want I guess?)
notifier = nManager.create_toast_notifier(sys.executable)
#display notification
notifier.show(notification)
duration = 7 # "short" duration for Toast notifications
# We have to wait for the results from the notification
# If we don't, the program will just continue and maybe even end before a button is clicked
thread = threading.Thread(target=lambda: time.sleep(duration))
thread.start()
print("We can still do things while the notification is displayed")
if __name__=="__main__":
test_notification()
The key thing to note here is that you need to find a way to wait for the response to the notification, since the notification is handled by a different thread than the program that produces it. This is why your "www.google.com" example worked while others didn't, because it didn't have anything to do with the Python program.
There's likely a more elegant solution, but a quick and easy way is to just create a Python thread and wait there for a duration. This way it doesn't interfere with the rest of your program in case you need to be doing something else. If you want your program to wait for a response, use time.sleep(duration) without all the threading code to pause the whole program.
I'm not sure how it works exactly, but it seems like the add_activated function just assigns a callback handler to the next available block in the XML. So if you wanted to add another button, my guess is that you can just do add_activated with another callback handler in the same order as you've listed your buttons.
Edit: I played around with it some and it turns out this lets you click anywhere, not just on the button. Not sure where to go from there but it's worth a heads up.

Related

Why does the TelegramBot get stuck when executing Inline commands?

When writing a bot, I used Inline - keyboard. I found that it was when EXIT (just in the background from the application) and LOGIN (into the application) in Telegram that the response to the button works again, as if it loops. Why is this happening? It seems like it didn't happen before.Is it possible to count on the incorrectness of the Internet or something like that?
It is interesting that anything can be in the body of the function, the code loops in any case...
import telebot
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
bot = telebot.TeleBot(config.TOKEN)
markup = InlineKeyboardMarkup()
markup.row_width = 1
markup.add(InlineKeyboardButton("My Links", callback_data="my_links"))
#bot.callback_query_handler(func=lambda call: call.data == 'my_links')
def myLinks(call):
# ...
# looping code
# ...
bot.polling()
It might be worth trying to replace the lambda function. To be honest, there are no thoughts

Python Outlook win32 event trigger when email is opened

my goal is to use a python script to trigger an event handler whenever I open up an email on Outlook, from there I should be able to get the data of the email that was opened, then do something with the data. There is a similar thread on how to do it via VBA (here), but I can't figure out how to translate that to python using win32com.
I have looked through the microsoft docs but can't figure out how to trigger events on MailItem objects.
https://learn.microsoft.com/en-us/office/vba/api/outlook.mailitem
The closest I had come to doing something close was by doing something like the below, which is probably not the solution as item in this case(as the doc states) does not contain the data.
import win32com.client
import pythoncom
import re
class Handler_Class(object):
def OnItemLoad(self, item):
print(item.Class)
outlook = win32com.client.DispatchWithEvents("Outlook.Application",Handler_Class)
Any ideas/suggestions appreciated! Thanks in advance!
Here's something that worked for me as proof-of-concept, from an amalgam of SO posts including this one:
How to pass arguments to win32com event handler. It prints out the Subject line and Body of a MailItem when Read.
The extra step from the OP's code is to handle the Application.ItemLoad event, and with the information passed go on to separately set a handler for the Item. Also, since your MailItem handler doesn't receive the this or self pointer in the event handler call (ie the IDispatch interface to the MailItem) you have to save it away yourself for later.
import win32com.client
import pythoncom
#Handler for Application Object
class Application_Handler(object):
def OnItemLoad(self, item):
print('Application::OnItemLoad')
#Only want to work with MailItems
if( item.Class == win32com.client.constants.olMail ):
#Get a Dispatch interface to the item
cli = win32com.client.Dispatch(item)
#Set up a handler
handler = win32com.client.WithEvents(cli,MailItem_Handler)
#Store the MailItem's Dispatch interface for use later
handler.setDisp(cli)
#Handler for MailItem object
class MailItem_Handler(object):
def setDisp(self,disp):
self._disp = disp
def OnOpen(self,bCancel):
print('MailItem::OnOpen')
def OnRead(self):
print('MailItem::OnRead')
subj = self._disp.Subject
print('Subject:',subj)
body = self._disp.Body
print('Body:',body)
outlook = win32com.client.DispatchWithEvents("Outlook.Application", Application_Handler)
#Message loop
pythoncom.PumpMessages()

Downloading PDF's and tracking downloads with Python

I'm creating an application that downloads PDF's from a website and saves them to disk. I understand the Requests module is capable of this but is not capable of handling the logic behind the download (File size, progress, time remaining etc.).
I've created the program using selenium thus far and would like to eventually incorporate this into a GUI Tkinter app eventually.
What would be the best way to handle the downloading, tracking and eventually creating a progress bar?
This is my code so far:
from selenium import webdriver
from time import sleep
import requests
import secrets
class manual_grabber():
""" A class creating a manual downloader for the Roger Technology website """
def __init__(self):
""" Initialize attributes of manual grabber """
self.driver = webdriver.Chrome('\\Users\\Joel\\Desktop\\Python\\manual_grabber\\chromedriver.exe')
def login(self):
""" Function controlling the login logic """
self.driver.get('https://rogertechnology.it/en/b2b')
sleep(1)
# Locate elements and enter login details
user_in = self.driver.find_element_by_xpath('/html/body/div[2]/form/input[6]')
user_in.send_keys(secrets.username)
pass_in = self.driver.find_element_by_xpath('/html/body/div[2]/form/input[7]')
pass_in.send_keys(secrets.password)
enter_button = self.driver.find_element_by_xpath('/html/body/div[2]/form/div/input')
enter_button.click()
# Click Self Service Area button
self_service_button = self.driver.find_element_by_xpath('//*[#id="bs-example-navbar-collapse-1"]/ul/li[1]/a')
self_service_button.click()
def download_file(self):
"""Access file tree and navigate to PDF's and download"""
# Wait for all elements to load
sleep(3)
# Find and switch to iFrame
frame = self.driver.find_element_by_xpath('//*[#id="siteOutFrame"]/iframe')
self.driver.switch_to.frame(frame)
# Find and click tech manuals button
tech_manuals_button = self.driver.find_element_by_xpath('//*[#id="fileTree_1"]/ul/li/ul/li[6]/a')
tech_manuals_button.click()
bot = manual_grabber()
bot.login()
bot.download_file()
So in summary, I'd like to make this code download PDF's on a website, store them in a specific directory (named after it's parent folder in the JQuery File Tree) and keep tracking of the progress (file size, time remaining etc.)
Here is the DOM:
I hope this is enough information. Any more required please let me know.

How create local notification on MacOS Catalina pyobjc?

I am having some difficulty finding out how to send local notifications on Catalina using pyobjc.
The closes example I have seen is this:
PyObjC "Notifications are not allowed for this application"
Edit (June 27, 2020): I've created a package which has functionality to display notifications on Mac OS here. It will use PyObjC to create and display notifications. If It does not work for whatever reason, it will fallback to AppleScript notifications with osascript. I did some testing and found that the PyObjC notifications work on some devices but don't on some.
Answer:
I have also been searching for this answer, so I'd like to share what I've found:
The first thing you'll notice is that the function notify() defines a class, then returns an instance of it. You might be wondering why you can't directly call Notification.send(params). I tried it, but I was getting an error with the PyObjC, which I am unfortunately unable to fix:
# Error
class Notification(NSObject):
objc.BadPrototypeError: Objective-C expects 1 arguments, Python argument has 2 arguments for <unbound selector send of Notification at 0x10e410180>
Now onto the code:
# vscode may show the error: "No name '...' in module 'Foundation'; you can ignore it"
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject, NSDate
from PyObjCTools import AppHelper
def notify(
title='Notification',
subtitle=None, text=None,
delay=0,
action_button_title=None,
action_button_callback=None,
other_button_title=None,
other_button_callback=None,
reply_placeholder=None,
reply_callback=None
):
class Notification(NSObject):
def send(self):
notif = NSUserNotification.alloc().init()
if title is not None:
notif.setTitle_(title)
if subtitle is not None:
notif.setSubtitle_(subtitle)
if text is not None:
notif.setInformativeText_(text)
# notification buttons (main action button and other button)
if action_button_title:
notif.setActionButtonTitle_(action_button_title)
notif.set_showsButtons_(True)
if other_button_title:
notif.setOtherButtonTitle_(other_button_title)
notif.set_showsButtons_(True)
# reply button
if reply_callback:
notif.setHasReplyButton_(True)
if reply_placeholder:
notif.setResponsePlaceholder_(reply_placeholder)
NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
# setting delivery date as current date + delay (in seconds)
notif.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(delay, NSDate.date()))
# schedule the notification send
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notif)
# on if any of the callbacks are provided, start the event loop (this will keep the program from stopping)
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
def userNotificationCenter_didDeliverNotification_(self, center, notif):
print('delivered notification')
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
reply_callback(reply_text)
AppHelper.stopEventLoop()
# create the new notification
new_notif = Notification.alloc().init()
# return notification
return new_notif
def main():
n = notify(
title='Notification',
delay=0,
action_button_title='Action',
action_button_callback=lambda: print('Action'),
# other_button_title='Other',
# other_button_callback=lambda: print('Other'),
reply_placeholder='Enter your reply please',
reply_callback=lambda reply: print('Replied: ', reply),
)
n.send()
if __name__ == '__main__':
main()
Explanation
The notify() function takes in quite a few parameters (they are self-explanatory). The delay is how many seconds later the notification will appear. Note that if you set a delay that's longer than the execution of the program, the notification will be sent ever after the program is being executed.
You'll see the button parameters. There are three types of buttons:
Action button: the dominant action
Other button: the secondary action
Reply button: the button that opens a text field and takes a user input. This is commonly seen in messaging apps like iMessage.
All those if statements are setting the buttons appropriately and self explanatory. For instance, if the parameters for the other button are not provided, a Other button will not be shown.
One thing you'll notice is that if there are buttons, we are starting the console event loop:
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
This is a part of Python Objective-C. This is not a good explanation, but it basically keeps program "on" (I hope someone cane give a better explanation).
Basically, if you specify that you want a button, the program will continue to be "on" until AppHelper.stopEventLoop() (more about this later).
Now there are some "hook" functions:
userNotificationCenter_didDeliverNotification_(self, notification_center, notification): called when the notification is delivered
userNotificationCenter_didActivateNotification_(self, notification_center, notification): called when the user interacts with the notification (clicks, clicks action button, or reply) (documentation)
There surely are more, but I do not think there is a hook for the notification being dismissed or ignored, unfortunately.
With userNotificationCenter_didActivateNotification_, we can define some callbacks:
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
# action button callback
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
# reply button callback
reply_callback(reply_text)
AppHelper.stopEventLoop()
There are different activation types for the types of actions. The text from the reply action can also be retrieved as shown.
You'll also notice the AppHelper.stopEventLoop() at the end. This means to "end" the program from executing, since the notification has been dealt with by the user.
Now let's address all the problems with this solution.
Problems
The program will never stop if the user does not interact with the notification. The notification will slide away into the notification center and may or may never be interacted with. As I stated before, there's no hook for notification ignored or notification dismissed, so we cannot call AppHelper.stopEventLoop() at times like this.
Because AppHelper.stopEventLoop() is being run after interaction, it is not possible to send multiple notifications with callbacks, as the program will stop executing after the first notification is interacted with.
Although I can show the Other button (and give it text), I couldn't find a way to give it a callback. This is why I haven't addressed it in the above code block. I can give it text, but it's essentially a dummy button as it cannot do anything.
Should I still use this solution?
If you want notifications with callbacks, you probably should not, because of the problems I addressed.
If you only want to show notifications to alert the user on something, yes.
Other solutions
PYNC is a wrapper around terminal-notifier. However, both received their last commit in 2018. Alerter seems to be a successor to terminal-notifier, but there is not Python wrapper.
You can also try running applescript to send notifications, but you cannot set callbacks, nor can you change the icon.
I hope this answer has helped you. I am also trying to find out how to reliably send notifications with callbacks on Mac OS. I've figured out how to send notifications, but callbacks is the issue.

Notification in python without stopping the script

I was writing code using python and opencv. If no face is detected it should notify the windows10 user. I used win10toast
import time
from win10toast import ToastNotifier
notif = ToastNotifier()
notif.show_toast(title= "Nusrat", msg= "One baby is missing")
time.sleep(10)
but it stops the code when notification is being shown. Is there any way I can show notification using gui or anything but that will not stop the code?
The library's Github repo shows how to avoid blocking in the landing page example :
from win10toast import ToastNotifier
import time
toaster = ToastNotifier()
toaster.show_toast("Example two",
"This notification is in it's own thread!",
icon_path=None,
duration=5,
threaded=True)
# Wait for threaded notification to finish
while toaster.notification_active(): time.sleep(0.1)
You need to add the threaded=True parameter. Sleeping is only needed if you want to check whether the notification is still active.
Displaying notification is not a blocking operation. The library's code forces a sleep equal to duration before closing the window :
# take a rest then destroy
sleep(duration)
DestroyWindow(self.hwnd)
UnregisterClass(self.wc.lpszClassName, None)
threaded=True will execute the show/sleep/destroy flow in a separate thread. Frankly, there are cleaner ways to do that, eg using a one-off timer.

Categories

Resources