Displaying desktop notification after getting a new message python tkinter - python

I wanted to make a notification system for StackOverflow using tkinter, when an item is unread in my inbox i want it to show the notification(but only once), which it does. But its not efficient. In some ways it shows notification once, and when i click on it, it takes some time for the notification to be identified as read by the API, So new notifications keeps on popping up, causing GUI to crash(as im running it inside of a after()). So i wanted to know if the notification part could be made more efficient in some way.
Code:
import tkinter as tk
import add
from time import ctime
import datetime
from stackapi import StackAPI
from win10toast import ToastNotifier
import threading
import webbrowser
root = tk.Tk()
root.title('Stack Notifier')
def authorise():
print('auth')
global end, acc_key, start, inbox, auth_button
if required:
add.run() #selenium for automation
acc_key = add.acc_key_dum #accesskey passed from another module
start = datetime.datetime.now()
hours_added = datetime.timedelta(hours=24)
end = start + hours_added #time for reaunthentication
site.key = add.key #access key from another module
site.access_token = acc_key #access token from another module
site.max_pages = 1
inbox = site.fetch('users/13382000/inbox')
auth_button['state'] = tk.DISABLED
threading.Thread(target=first).start()
else:
inbox = site.fetch('users/13382000/inbox')
threading.Thread(target=first).start()
def first():
global current, required, inbox, auth_button
def popup():
webbrowser.open(link,new=0)
inbox = site.fetch('users/13382000/inbox')
print('chc')
current = datetime.datetime.now()
if start < current < end:
required = False
else:
required = True
auth_button['state'] = tk.NORMAL
if not required:
print('first')
is_unread = inbox['items'][0]['is_unread']
if is_unread:
title = inbox['items'][0]['title']
item_type = inbox['items'][0]['item_type']
link = inbox['items'][0]['link']
creation_date = ctime(inbox['items'][0]['creation_date'])
noti = ToastNotifier()
noti.show_toast(f'StackOveflow - {item_type}',f'{title} - {creation_date}',duration=5,callback_on_click=popup,threaded=True)
print('yes')
else:
threading.Thread(target=authorise).start()
root.after(1000,threading.Thread(target=first).start)
required = True #a reference to whether authentication is required again, after 24 hrs
label_login = tk.Label(root,text='Click To authorise with StackOverflow',font=('helvatica',16))
label_login.grid(row=0,columnspan=3)
auth_button = tk.Button(root,text='Authorise',command=authorise)
auth_button.grid(row=1,column=1)
site = StackAPI('stackoverflow')
root.mainloop()
I know this is not the best of codes, but im open to ideas and suggestions.
PS: Im using selenium for automation and ask for user credentials. and it requires giving permission every 24 hours, so i need to boot up selenium every 24 hours too, which is being finely done, i guess.
Thanks in advance :D

Related

WhatsApp texting using webbrowser module of python

import webbrowser
import time
import datetime
name = input('Enter the contact number of a person you want to send message in WhatsApp: ')
message = input('Enter the message: ')
time1 = input('Enter time in {hh:mm:ss} format: ')
print(f'Time entered by user: {time1}')
while True:
current_time = time.ctime()
time_format = current_time[11:19]
time.sleep(1)
print(f'Current time: {time_format}')
if time1 == time_format:
webbrowser.open_new_tab(f'https://web.whatsapp.com/send?phone=+91{name}&text={message}')
break
elif time1 < time_format:
print('Enter correct time')
break
else:
print('waiting..')
I am taking message, contact number and time as a input from the user. whenever, if condition satisfies, WhatsApp is open with the contact number and message you type in before.
Only problem is, I have to manually hit the send button to send message. Everything else is working fine.
Is there any way to do that? It would be great, if you provide solution without using Selenium
Thanks in advance!
This doesn't seem really efficient and not really good to constantly check to see if the time the user gave is the same as the current one. The schedule module can be really helpful with this and it does help to make your code look cleaner. I'll provide 2 answers, one with the schedule library and one without it.
Remember that if the user inputs a time that has passed, that doesn't matter to the program and it will only send the message at the next time that the time the user provided is the current device time. For example if the user inputs 13:10:00 and the current device time is 14:00:00 then the message will be sent the next time the current time is 13:10:00 which is the next day.
import os
import time
import webbrowser
from datetime import datetime
from string import ascii_letters
name = input('Enter the contact number of a person you want to send message in WhatsApp: ')
message = input('Enter the message:\n')
time1 = input('Enter time in {hh:mm:ss} format: ')
# Check if there aren't any ':' in the input time
if ":" not in time1:
print("Please input a correct time format")
os._exit(0)
# Check if there are any letters in the input time
elif ascii_letters in time1:
print("Please input a correct time format")
os._exit(0)
print(f'Time entered by user: {time1}')
# Check every .9 seconds if the current time is the same as the user input time
while True:
current_time = datetime.now().strftime("%H:%M:%S")
print(f'Current time: {current_time}')
if time1 == current_time:
webbrowser.open_new_tab(f'https://web.whatsapp.com/send?phone=+91{name}&text={message}')
break
else:
time.sleep(0.9)
And with the schedule library this is how it would probably look like:
(this is how you would probably do it)
import os
import time
import schedule
import webbrowser
from string import ascii_letters
def send_whatsapp_msg(name, message):
webbrowser.open_new_tab(f'https://web.whatsapp.com/send?phone=+91{name}&text={message}')
return schedule.CancelJob # do this if you want to send this message only once
# or just exit the program entirely if you don't want to run any more tasks
# os._exit(0)
name = input('Enter the contact number of a person you want to send message in WhatsApp: ')
message = input('Enter the message:\n') # Message to be sent
time1 = input('Enter time in {hh:mm:ss} format: ') # Time to sent the message
# Check if there aren't any ':' in the input time
if ":" not in time1:
print("Please input a correct time format")
os._exit(0)
# Check if there are any letters in the input time
elif ascii_letters in time1:
print("Please input a correct time format")
os._exit(0)
print(f'Time entered by user: {time1}')
# Schedule the message to be sent
schedule.every().day.at(time1).do(send_whatsapp_msg, name, message)
# Wait for the tasks to run and check the time every .9 seconds
while True:
schedule.run_pending()
time.sleep(0.9)
To fix the button clicking problem I think I found a "hacky" solution, using selenium and pyautogui. On Windows, you can install the WhatsApp application and scan the QR Code only once and then this solution will probably work for you.
You will also have to install the selenium chrome webdriver and take a screenshot of the "arrow" button to send the message to on the WhatsApp app. Here is the janky solution:
import os
import time
import schedule
import pyautogui
from string import ascii_letters
from selenium import webdriver
from selenium.webdriver.commn.keys import Keys
def send_whatsapp_msg(name, message):
driver = webdriver.Chrome() # you can user different drivers like 'Firefox()' but you will have to install them first
# look at https://selenium-python.readthedocs.io/installation.html for more info
driver.get("https://api.whatsapp.com/send?phone=+91{name}&text={message}")
time.sleep(10) # Wait for everything to set up, can be assigned lower values
# Click on the 'Open WhatsApp' Prompt Button
# Much easier to do it with pyautogui since I couldn't make it work with Selenium
# Look at https://pyautogui.readthedocs.io/en/latest/quickstart.html#screenshot-functions for the '.locateCenterOnScreen' function
coords = pyautogui.locateCenterOnScreen("open_whatsapp.png")
pyautogui.click(coords[0], coords[1]) # read the docs on what '.locateCenterOnScreen' returns
time.sleep(15) # Wait for the WhatsApp dektop app to load up
coords = pyautogui.locateCenterOnScreen("click_send.png") # coordinates for the 'send' button
pyautogui.click(coords[0], coords[1])
# Your message has been sent!
return schedule.CancelJob # do this if you want to send this message only once
# or just exit the program entirely if you don't want to run any more tasks
# os._exit(0)
name = input('Enter the contact number of a person you want to send message in WhatsApp: ')
message = input('Enter the message:\n') # Message to be sent
time1 = input('Enter time in {hh:mm:ss} format: ') # Time to sent the message
# Check if there aren't any ':' in the input time
if ":" not in time1:
print("Please input a correct time format")
os._exit(0)
# Check if there are any letters in the input time
elif ascii_letters in time1:
print("Please input a correct time format")
os._exit(0)
print(f'Time entered by user: {time1}')
# Schedule the message to be sent
schedule.every().day.at(time1).do(send_whatsapp_msg, name, message)
# Wait for the tasks to run and check the time every .9 seconds
while True:
schedule.run_pending()
time.sleep(0.9)
click_send.png looks like this:
and open_whatsapp.png looks like this:
open_whatsapp.png is a screenshot of the "Open WhatsApp" button that the website prompts you, but it was different in my language so I had to edit it out.
Also I don't know how reliable pyautogui will be but it worked every time I tried to run this, so I guess it kinda works.

Keep Python COM local server listening open in paralallel while the main code runs

I am trying to build a COM server to get real time information. The problem is that all other functionalities stop while localserver is opened. The rest of the code just runs when the localserver is closed.
I have searched for solutions and failed trying multiprocessing, not because this wouldn't work, I guess because I suck. Anyway, I am stuck in this part.
import pythoncom
import win32com
from win32com.server import localserver
from multiprocessing import Process
class PythonUtilities(object):
_reg_clsid_ = '{D9C54599-9011-4678-B1EB-A07FD272F0AF}'
_reg_desc_ = "Change information between programs"
_reg_progid_ = "Python.LetsTalk"
_public_attrs_ = ['speech', 'roger']
_readonly_attrs_ = ['roger']
_public_methods_ = ['talktome']
def __init__(self):
self.roger = 'roger'
self.speech = None
def talktome(self,speech):
self.speech = speech
print ('New speech received: ' + self.speech)
return self.roger
### ___ ###
def runserver(mess):
print(mess)
localserver.serve(['{D9C54599-9011-4678-B1EB-A07FD272F0AF}'])
if __name__=='__main__':
pu = PythonUtilities
print ("Registering COM Server ")
win32com.server.register.UseCommandLine(pu)
# Fine so far.
# The problem starts here:
localserver.serve(['{D9C54599-9011-4678-B1EB-A07FD272F0AF}'])
#... rest of the code waiting for localserver be closed
# Experiment... Doesnt work:
#proc = Process(target=runserver, args = ('starting process',))
#proc.start()
#proc.join()
It's important to say that all messages sent from the client seem to be correctly displayed BUT ONLY AFTER I close the local server manually. I want to receive it in real time like a chat app. I mean, I want to keep the localserver opened and being able to work with the information received along the rest of the code.
My problem was solved rewriting the localserver.serve() function and starting it in a new thread as the code below.
import pythoncom
from win32com.client import Dispatch # to get attributes
from win32com.server import register, factory
from threading import Thread
from queue import Queue
class PythonUtilities(object):
_reg_clsid_ = '{D9C54599-9011-4678-B1EB-A07FD272F0AF}'
_reg_desc_ = "Change information between programs"
_reg_progid_ = "Python.LetsTalk"
_public_attrs_ = ['speech']
_public_methods_ = ['talktome']
queue_speech = Queue()
def talktome(self,speech):
self.queue_speech.put(speech)
print ('New speech received: ' + speech)
return 'roger'
### ___ ###
# use instead localserver.serve()
def runserver():
# added - multithread support
pythoncom.CoInitialize()
clsids = ['{D9C54599-9011-4678-B1EB-A07FD272F0AF}']
infos = factory.RegisterClassFactories(clsids)
# commented - from original localserver.serve() method
#pythoncom.EnableQuitMessage(win32api.GetCurrentThreadId())
pythoncom.CoResumeClassObjects()
pythoncom.PumpMessages()
factory.RevokeClassFactories( infos )
pythoncom.CoUninitialize()
if __name__=='__main__':
#use this
server_thread = Thread(target=runserver) # Process works as well
server_thread.start()
#instead this
#localserver.serve(['{D9C54599-9011-4678-B1EB-A07FD272F0AF}'])
#... rest of the code now works in parallel
Also I have made some improvements like Queue to get data later. I hope it can help others.

Notification quit after call

I wrote a Python script to show a desktop notification if the price of bitcoin reaches $4,500, but the script will quit if the price is reached. How do I keep the script running?
Here is the code:
import time
import requests
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify
r = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
r.json()
resp = r.json()
price = resp["bpi"]["USD"]["rate_float"]
top = 4200
if price > top :
# One time initialization of libnotify
Notify.init("Crypto Notify")
# Create the notification object
summary = "Crypto Alert!"
body = "BTC : $ %s" % (price)
notification = Notify.Notification.new(
summary,
body, # Optional
)
# Actually show on screen
notification.show()
else:
while price < top :
r =requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
print price
time.sleep(10)
So from what I see it seems that you scripts is written to execute in a single pass i.e. all statements will be excuted only once. So what is happening is your script waits for the condition of price greater then to be true and once it is true it executes the rest of script of IF block.
What you need is an loop that encapsulates the script and who's end condition will take long time to achieve kind of infinite loop but safer.
Also another approach you may try is keep the script in infinite loop and simply exit the script when you want using ctrl + c
Although its not very clean way to do it.
Sample code :
import time
import requests
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify
while true :
r = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
r.json()
resp = r.json()
price = resp["bpi"]["USD"]["rate_float"]
top = 4200
if price > top :
# One time initialization of libnotify
Notify.init("Crypto Notify")
# Create the notification object
summary = "Crypto Alert!"
body = "BTC : $ %s" % (price)
notification = Notify.Notification.new(summary,body)
# Actually show on screen
notification.show()
else:
r =requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
print price
time.sleep(10)

How to apply multiprocessing in Python for event listener and function?

So basically I have set up an event listener that is constantly checking Microsoft Outlook to see if any new emails have come in to my inbox. Based on the emails coming in, I run certain processes to check whether the specific email is the one we are looking for (which does not take too much time), but if the email fits our criteria, we run a certain function on it that takes about a minute or two to complete.
My problem is, say I am getting an email at the same exact time (auto-generated), with criteria that I need and I want to run certain functions on it. When my program picks up on the first email, it goes into the process one that email and it takes about a minute or so to complete, at which point the second email had already come in, and my event listener has missed it. This is a problem because the second email is also important for me to capture and run my process on.
I assume that multiprocessing it the way to tackle this situation - how do I go about setting up a multiprocessing structure, for which my event listener does not stop running while the functions - to check if the email is valid, perform a process on the email - can proceed while the listener is still moving and capturing the next email, and then running the process on it again.
Any help or tips would be greatly appreciated. I am building a metrics/analytics database, for which many reports come in to my inbox, and I automate the reporting process.
Thank you!
import win32com.client
import pythoncom
import time
import os
class Handler_Class(object):
def OnNewMailEx(self, receivedItemsIDs):
# RecrivedItemIDs is a collection of mail IDs separated by a ",".
# You know, sometimes more than 1 mail is received at the same moment.
for ID in receivedItemsIDs.split(","):
print('')
print('Running scan...')
mail = outlook.Session.GetItemFromID(ID)
email_date = mail.SentOn.strftime("%m-%d-%Y" + " at " + "%I:%M:%S %p")
email_date_stamp = mail.SentOn.strftime('%m-%d-%Y_at_%I-%M-%S-%p')
email_message = mail.Body
email_subject = mail.Subject
email_sender = mail.SenderEmailAddress
email_attachments = mail.Attachments
print('From: ' + email_sender)
print('Subject: ' + email_subject)
print('Date: ' + email_date)
try:
if check_correct_subject(email_subject) == True:
if email_attachments.Count > 0:
print(str(email_attachments.Count) + ' attachments found.')
for i in range(email_attachments.Count):
email_attachment = email_attachments.Item(i + 1)
report_name = email_date_stamp + '_' + email_attachment.FileName
print(report_name)
print('Pushing attachment - ' + report_name + ' - to check_correct_email() function.')
if check_correct_attachment(email_attachment) == True:
save_incoming_report(email_attachment, report_name, get_report_directory(email_subject))
else:
print('Not the attachment we are looking for.')
# add error logging here
break
else: # ***********add error logging here**************
print('No attachment found.')
except: #add any error logging here#
pass
def check_correct_subject(email_subject):
def check_correct_attachment(email_attachment):
def get_report_directory(email_subject):
def save_incoming_report(email_attachment, report_name, directory):
def push_email_attachment(email_attachment, report_name, directory):
def security_risk_report(email_attachment, report_name, directory):
def broker_risk_report(email_attachment, report_name, directory):
def forex_data_report():
def create_html_security_ldw(df1, df2):
def send_outlook_email(html_text):
if __name__ == '__main__':
outlook = win32com.client.DispatchWithEvents("Outlook.Application", Handler_Class)
#and then an infinite loop that waits from events.
pythoncom.PumpMessages()
You can find great examples of using the multiprocessing module and Queue module from Python to accomplish multithreading here:
Python multiprocessing pool.map for multiple arguments
The comments in that post provide multiple examples/code structures you can adapt to your needs. Also, this post explains why you might use multiprocessing vs threads:
Multiprocessing vs Threading Python
Finally, the official library documentation for multiprocessing here gives you all the info you need:
https://docs.python.org/2/library/multiprocessing.html
how do I go about setting up a multiprocessing structure, for which my
event listener does not stop running while the functions
I think a sensible approach here would be to use BaseManager, which would act as a server to which your event listener would send Outlook messages to. Then all of your other scripts would connect to that server and retrieve any messages that need to be processed. How to do it is neatly explained here.

Listen onclick event in PyObjC

I try to display the current windows for each click on the system.
I do this code :
from AppKit import NSWorkspace
def getwindows():
activeAppName = NSWorkspace.sharedWorkspace().activeApplication()['NSApplicationName']
print activeAppName
return
def main():
getwindows()
main()
But only the current windows when i setup the script is displayed.
How can i bind this script in a loop with a click event ?
I already try to use Turtle but some errors appended.
Note that the activeApplication method of NSWorkSpace is deprecated. The following can be used to actively probe the running applications for their active status:
import AppKit
import time
rl = AppKit.NSRunLoop.currentRunLoop()
ws = AppKit.NSWorkspace.sharedWorkspace()
for i in xrange(10):
for app in ws.runningApplications():
if app.isActive():
print "active app:", app.localizedName()
date = AppKit.NSDate.date()
time.sleep(1)
rl.acceptInputForMode_beforeDate_( AppKit.NSDefaultRunLoopMode, date )
Active means it is the receiving keyboard input. Clicking on an application will cause it to become active. Note that the acceptInputForMode method must be called so that property changes are reflected in the current app. Run this program then click on various other applications -- you should see the active app change.
A kind of binding can be done through observers:
import AppKit
ws = AppKit.NSWorkspace.sharedWorkspace()
appL = ws.runningApplications()
class MyClass( AppKit.NSObject ):
def observeValueForKeyPath_ofObject_change_context_(self,
kpath, objid, change, context ):
print "path change", kpath, change['new'], appL[context].localizedName()
obj = MyClass.new()
for i in xrange(len(appL)):
appL[i].addObserver_forKeyPath_options_context_( obj,
"isActive", AppKit.NSKeyValueObservingOptionNew, i )
date = AppKit.NSDate.date().dateByAddingTimeInterval_( 10 )
rl = AppKit.NSRunLoop.currentRunLoop()
rl.acceptInputForMode_beforeDate_( AppKit.NSDefaultRunLoopMode, date )
for app in appL:
app.removeObserver_forKeyPath_( obj, "isActive" )
Run this program same as the last.
There are a few other properties of NSRunningApplication that you could probe/observe (such as hidden) but the list is quite short.

Categories