From b1241036b5e304b70a6a02755f1ade1da2258c54 Mon Sep 17 00:00:00 2001 From: 3mora2 <66757189+3mora2@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:59:14 +0200 Subject: [PATCH] add async --- WPP_Whatsapp/PlaywrightSafeThread/__init__.py | 79 ----------------- WPP_Whatsapp/__init__.py | 2 +- WPP_Whatsapp/api/layers/HostLayer.py | 42 ++++----- WPP_Whatsapp/controllers/browser.py | 88 ++++++++++--------- WPP_Whatsapp/controllers/initializer.py | 6 +- examples/custom_create.py | 2 +- examples/start_async.py | 10 +-- examples/wa_version.py | 2 +- requirements.txt | 2 +- setup.py | 6 +- 10 files changed, 83 insertions(+), 156 deletions(-) delete mode 100644 WPP_Whatsapp/PlaywrightSafeThread/__init__.py diff --git a/WPP_Whatsapp/PlaywrightSafeThread/__init__.py b/WPP_Whatsapp/PlaywrightSafeThread/__init__.py deleted file mode 100644 index 23591df..0000000 --- a/WPP_Whatsapp/PlaywrightSafeThread/__init__.py +++ /dev/null @@ -1,79 +0,0 @@ -import asyncio -import typing -from PlaywrightSafeThread.browser.threadsafe_browser import ThreadsafeBrowser, BrowserName, SUPPORTED_BROWSERS -from playwright.async_api import Error - - -class ThreadsafeBrowser(ThreadsafeBrowser): - def __init__( - self, - no_context=False, - browser: BrowserName = "chromium", - stealthy: bool = False, - install: bool = False, - check_open_dir=True, - close_already_profile=True, - **kwargs - ) -> None: - super().__init__(no_context=no_context, - browser=browser, - stealthy=stealthy, - install=install, - check_open_dir=check_open_dir, - close_already_profile=close_already_profile, - **kwargs) - - async def page_evaluate(self, expression: str, arg: typing.Optional[typing.Any] = None): - return await self.page.evaluate(expression, arg) - - def sync_page_evaluate(self, expression: str, arg: typing.Optional[typing.Any] = None, timeout_=60): - try: - return self.run_threadsafe(self.page.evaluate, expression, arg, timeout_=timeout_) - except Error as error: - if "Execution context was destroyed, most likely because of a navigation" in error.message: - pass - elif "ReferenceError: WPP is not defined" in error.message: - pass - else: - raise error - - async def page_wait_for_function(self, expression, arg=None, timeout: typing.Optional[float] = None, - polling: typing.Optional[typing.Union[float, typing.Literal["raf"]]] = None): - - return await self.page.wait_for_function(expression, arg=arg, timeout=timeout, polling=polling) - - def sync_page_wait_for_function(self, expression, arg=None, timeout: typing.Optional[float] = None, - polling: typing.Optional[typing.Union[float, typing.Literal["raf"]]] = None): - - return self.run_threadsafe(self.page.wait_for_function, expression, arg=arg, timeout=timeout, polling=polling) - - async def expose_function(self, *args, **kwargs): - return await self.page.expose_function(*args, **kwargs) - - def sync_expose_function(self, *args, **kwargs): - return self.run_threadsafe(self.page.expose_function, *args, **kwargs) - - async def add_script_tag(self, *args, **kwargs): - return await self.page.add_script_tag(*args, **kwargs) - - def sync_add_script_tag(self, *args, **kwargs): - return self.run_threadsafe(self.page.add_script_tag, *args, **kwargs) - - def sleep(self, val, *args, **kwargs): - try: - self.run_threadsafe(asyncio.sleep, val, *args, **kwargs, timeout_=val if val > 5 else 5) - except: - pass - - async def goto(self, *args, **kwargs): - return await self.page.goto(*args, **kwargs) - - def sync_goto(self, *args, **kwargs): - return self.run_threadsafe(self.page.goto, *args, **kwargs) - - def run_threadsafe(self, func, *args, timeout_=120, **kwargs): - future = asyncio.run_coroutine_threadsafe( - func(*args, **kwargs), self.loop - ) - result = future.result(timeout=timeout_) - return result diff --git a/WPP_Whatsapp/__init__.py b/WPP_Whatsapp/__init__.py index c3e2942..9e1e7a1 100644 --- a/WPP_Whatsapp/__init__.py +++ b/WPP_Whatsapp/__init__.py @@ -1,5 +1,5 @@ +from WPP_Whatsapp.controllers.browser import ThreadsafeBrowser from WPP_Whatsapp.api.Whatsapp import Whatsapp -from WPP_Whatsapp.controllers.browser import Browser from WPP_Whatsapp.controllers.initializer import Create diff --git a/WPP_Whatsapp/api/layers/HostLayer.py b/WPP_Whatsapp/api/layers/HostLayer.py index caae27e..aaf95a4 100644 --- a/WPP_Whatsapp/api/layers/HostLayer.py +++ b/WPP_Whatsapp/api/layers/HostLayer.py @@ -10,7 +10,7 @@ from playwright.async_api import Page from WPP_Whatsapp.api.const import whatsappUrl, Logger from WPP_Whatsapp.api.helpers.function import asciiQr -from WPP_Whatsapp.PlaywrightSafeThread import ThreadsafeBrowser +from WPP_Whatsapp.controllers.browser import ThreadsafeBrowser from WPP_Whatsapp.api.helpers.jsFunction import setInterval from WPP_Whatsapp.api.helpers.wa_version import getPageContent @@ -121,8 +121,8 @@ async def start(self): ############################### initWhatsapp #################################################### async def initWhatsapp(self): # await page.setUserAgent(useragentOverride); - # self.logger.info(f'{self.session}: unregisterServiceWorker') - # await self.unregisterServiceWorker() + self.logger.info(f'{self.session}: unregisterServiceWorker') + await self.unregisterServiceWorker() if self.version: self.logger.info(f'{self.session}: Setting WhatsApp WEB version to {self.version}') await self.setWhatsappVersion(self.version) @@ -135,6 +135,11 @@ async def initWhatsapp(self): async def unregisterServiceWorker(self): try: await self.ThreadsafeBrowser.page_evaluate("""() => { + setInterval(() => { + window.onerror = console.error; + window.onunhandledrejection = console.error; + }, 500); + // Remove existent service worker navigator.serviceWorker .getRegistrations() @@ -148,14 +153,9 @@ async def unregisterServiceWorker(self): // Disable service worker registration // @ts-ignore navigator.serviceWorker.register = new Promise(() => {}); - - setInterval(() => { - window.onerror = console.error; - window.onunhandledrejection = console.error; - }, 500); }""") except: - Logger.exception("unregisterServiceWorker") + pass async def setWhatsappVersion(self, version): body = "" @@ -364,7 +364,7 @@ def waitForPageLoad(self): # TODO:: self.ThreadsafeBrowser.sleep(.2) - self.ThreadsafeBrowser.sync_page_wait_for_function("() => WPP.isReady") + self.ThreadsafeBrowser.page_wait_for_function_sync("() => WPP.isReady") async def waitForPageLoad_(self): while not self.isInjected: @@ -507,11 +507,11 @@ def waitForLogin(self): def getHostDevice(self): """@returns Current host device details""" - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.getHost()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.getHost()") def getWid(self): """@returns Current wid connected""" - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.getWid()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.getWid()") async def getWAVersion(self): """Retrieves WA version""" @@ -523,26 +523,26 @@ async def getWAJSVersion(self): return await self.ThreadsafeBrowser.page_evaluate("() => WPP.version") def getConnectionState(self): - return self.ThreadsafeBrowser.sync_page_evaluate("() => {return WPP.whatsapp.Socket.state;}") + return self.ThreadsafeBrowser.page_evaluate_sync("() => {return WPP.whatsapp.Socket.state;}") def isConnected(self): """Retrieves if the phone is online. Please note that this may not be real time.""" - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.isConnected()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.isConnected()") def isLoggedIn(self): - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.isLoggedIn()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.isLoggedIn()") def getBatteryLevel(self): - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.getBatteryLevel()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.getBatteryLevel()") def startPhoneWatchdog(self, interval=15000): - return self.ThreadsafeBrowser.sync_page_evaluate("(interval) => WAPI.startPhoneWatchdog(interval)", interval) + return self.ThreadsafeBrowser.page_evaluate_sync("(interval) => WAPI.startPhoneWatchdog(interval)", interval) def stopPhoneWatchdog(self): - return self.ThreadsafeBrowser.sync_page_evaluate("() => WAPI.stopPhoneWatchdog()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WAPI.stopPhoneWatchdog()") def isMultiDevice(self): - return self.ThreadsafeBrowser.sync_page_evaluate("() => WPP.conn.isMultiDevice()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WPP.conn.isMultiDevice()") async def isAuthenticated(self): try: @@ -557,7 +557,7 @@ def sync_isAuthenticated(self): try: if self.page.is_closed(): return False - return self.ThreadsafeBrowser.sync_page_evaluate("() => WPP.conn.isRegistered()") + return self.ThreadsafeBrowser.page_evaluate_sync("() => WPP.conn.isRegistered()") except Exception as e: self.logger.debug(e) return False @@ -613,7 +613,7 @@ async def isInsideChat(self): return result if result else False def sync_isInsideChat(self): - result = self.ThreadsafeBrowser.sync_page_evaluate("() => WPP.conn.isMainReady()") + result = self.ThreadsafeBrowser.page_evaluate_sync("() => WPP.conn.isMainReady()") return result if result else False async def inject_api(self): diff --git a/WPP_Whatsapp/controllers/browser.py b/WPP_Whatsapp/controllers/browser.py index 1ab74d2..e03cde2 100644 --- a/WPP_Whatsapp/controllers/browser.py +++ b/WPP_Whatsapp/controllers/browser.py @@ -1,42 +1,50 @@ import asyncio -from playwright.async_api import async_playwright, Playwright, BrowserContext, Page - -from WPP_Whatsapp.api.const import useragentOverride - - -class Browser: - session: str - playwright: "Playwright" - browser: "BrowserContext" - page: "Page" - - def __init__(self, user_data_dir: str = "", headless: bool = False, *args, **kwargs): - self.user_data_dir = user_data_dir - self.loop = kwargs.get("loop") - if not self.loop: - raise Exception("Not Add Loop") - asyncio.set_event_loop(self.loop) - self.headless = headless - - async def initBrowser(self): - self.playwright = await async_playwright().start() - self.browser = await self.playwright.chromium.launch_persistent_context( - self.user_data_dir, channel="chrome", - no_viewport=True, - headless=self.headless, - # args=chromiumArgs, - bypass_csp=True, - user_agent=useragentOverride - ) - - self.page = self.browser.pages[0] if self.browser.pages else await self.browser.new_page() - - async def page_evaluate(self, expression, arg=None): - return await self.page.evaluate(expression, arg) - - async def page_wait_for_function(self, expression, arg=None, timeout=None, polling=None): - return await self.page.wait_for_function(expression, arg=arg, timeout=timeout, polling=polling) - -# if __name__ == '__main__': -# asyncio.run(Browser().initBrowser()) +from PlaywrightSafeThread.browser.threadsafe_browser import ThreadsafeBrowser as Tb, BrowserName, SUPPORTED_BROWSERS, \ + Logger +from playwright.async_api import Error + + +class ThreadsafeBrowser(Tb): + def __init__( + self, + no_context=False, + browser: BrowserName = "chromium", + stealthy: bool = False, + install: bool = False, + check_open_dir=True, + close_already_profile=True, + **kwargs + ) -> None: + super().__init__(no_context=no_context, + browser=browser, + stealthy=stealthy, + install=install, + check_open_dir=check_open_dir, + close_already_profile=close_already_profile, + **kwargs) + + def page_evaluate_sync(self, *args, timeout_=60, **kwargs, ): + try: + return super().page_evaluate_sync(*args, timeout_=60, **kwargs, ) + except Error as error: + if "Execution context was destroyed, most likely because of a navigation" in error.message: + pass + elif "ReferenceError: WPP is not defined" in error.message: + pass + else: + raise error + + async def expose_function(self, *args, **kwargs, ): + return await super().expose_function(*args, **kwargs, ) + + def sleep(self, val, timeout_=None): + try: + super().sleep(val, timeout_=timeout_) + except: + pass + + def run_threadsafe(self, func, *args, timeout_=120, **kwargs, ): + if not asyncio.iscoroutine(func): + func = func(*args, **kwargs) + return super().run_threadsafe(func, timeout_=timeout_) diff --git a/WPP_Whatsapp/controllers/initializer.py b/WPP_Whatsapp/controllers/initializer.py index 68c3059..c802526 100644 --- a/WPP_Whatsapp/controllers/initializer.py +++ b/WPP_Whatsapp/controllers/initializer.py @@ -2,8 +2,8 @@ import os import types from typing import Optional -from WPP_Whatsapp.PlaywrightSafeThread import SUPPORTED_BROWSERS -from WPP_Whatsapp.PlaywrightSafeThread import ThreadsafeBrowser +from WPP_Whatsapp.controllers.browser import SUPPORTED_BROWSERS +from WPP_Whatsapp.controllers.browser import ThreadsafeBrowser from WPP_Whatsapp.api.Whatsapp import Whatsapp from WPP_Whatsapp.api.const import Logger @@ -78,7 +78,7 @@ def sync_close(self): def _onStateChange(self, state): self.state = state if hasattr(self, "ThreadsafeBrowser") and not self.ThreadsafeBrowser.page.is_closed(): - connected = self.ThreadsafeBrowser.sync_page_evaluate("() => WPP.conn.isRegistered()") + connected = self.ThreadsafeBrowser.page_evaluate_sync("() => WPP.conn.isRegistered()") if not connected: self.ThreadsafeBrowser.sleep(2) if not self.waitLoginPromise: diff --git a/examples/custom_create.py b/examples/custom_create.py index 7f115d0..f19a5b4 100644 --- a/examples/custom_create.py +++ b/examples/custom_create.py @@ -1,5 +1,5 @@ from WPP_Whatsapp import Whatsapp -from WPP_Whatsapp.PlaywrightSafeThread import ThreadsafeBrowser +from WPP_Whatsapp import ThreadsafeBrowser session = "test" diff --git a/examples/start_async.py b/examples/start_async.py index a6f3811..5dcac3d 100644 --- a/examples/start_async.py +++ b/examples/start_async.py @@ -6,13 +6,10 @@ logger.setLevel(logging.DEBUG) -# Not Work Yet - async def main(): # start client with your session name your_session_name = "test" - creator = Create(session=your_session_name, browser="chrome", - loop=asyncio.get_event_loop()) # , version="2.2409.2") + creator = Create(session=your_session_name, browser="chrome", version="2.2409.2") client = await creator.start_() # Now scan Whatsapp Qrcode in browser @@ -21,7 +18,10 @@ async def main(): if creator.state != 'CONNECTED': raise Exception(creator.state) - print(client.getWAVersion()) + print(await client.getWAVersion()) + client.sendText("201016788", "test") + await client.sendText_("201016788", "test") + await creator.close() asyncio.run(main()) diff --git a/examples/wa_version.py b/examples/wa_version.py index b800f40..41b61da 100644 --- a/examples/wa_version.py +++ b/examples/wa_version.py @@ -7,7 +7,7 @@ # start client with your session name your_session_name = "test" -creator = Create(session=your_session_name, browser="chrome", version="2.2409.2") +creator = Create(session=your_session_name, browser="chrome", version="2.2409.2", autoClose=5) client = creator.start() # Now scan Whatsapp Qrcode in browser diff --git a/requirements.txt b/requirements.txt index cc59aea..2922293 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ typing_extensions requests node-semver aiohttp -PlaywrightSafeThread \ No newline at end of file +PlaywrightSafeThread>=0.5.2.1 \ No newline at end of file diff --git a/setup.py b/setup.py index a4df382..15b5135 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ "the creation of any interaction, such as customer service, media sending, intelligence recognition " "based on phrases artificial and many other things, use your imagination") -version = "0.1.9.6" +version = "0.2.0" setup( name="WPP_Whatsapp", @@ -30,7 +30,7 @@ "playwright-stealth", "node-semver", "aiohttp", - "PlaywrightSafeThread" + "PlaywrightSafeThread>=0.5.2.1" ], long_description=long_description, long_description_content_type="text/markdown", @@ -41,7 +41,5 @@ "Intended Audience :: Developers", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", - ], - )