Skip to content

Commit

Permalink
v1.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
DedInc committed Apr 21, 2024
1 parent bb561a2 commit 39bfa89
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 65 deletions.
179 changes: 115 additions & 64 deletions emunium/emunium.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,56 @@
import asyncio
import keyboard
import time
import math
import os
import random
import tempfile
import time
import struct

import keyboard
import pyautogui
import math
import pyclick

def get_image_size(file_path):
with open(file_path, 'rb') as file:
file.seek(16)
width_bytes = file.read(4)
height_bytes = file.read(4)
width = struct.unpack('>I', width_bytes)[0]
height = struct.unpack('>I', height_bytes)[0]
return (width, height,)

class EmuniumSelenium:
def __init__(self, driver):
self.driver = driver
self.clicker = pyclick.HumanClicker()
self.browser_offsets = ()
self.browser_inner_window = ()

def _get_browser_properties_if_not_found(self):
if not self.browser_offsets or not self.browser_inner_window:
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
temp_screen_path = temp_file.name
self.driver.save_screenshot(temp_screen_path)
location = pyautogui.locateOnScreen(temp_screen_path, confidence=0.8)
self.browser_offsets = (location.left, location.top,)
self.browser_inner_window = get_image_size(temp_screen_path)
os.remove(temp_screen_path)

def get_center(self, element):
coords = self.driver.execute_script("""
var element = arguments[0];
var rect = element.getBoundingClientRect();
var centerX = rect.left + rect.width / 2 + window.scrollX;
var centerY = rect.top + rect.height / 2 + (window.scrollY || window.pageYOffset) + (screen.height - window.innerHeight) / 1.5;
self._get_browser_properties_if_not_found()

return {x: centerX, y: centerY};
""", element)
element_location = element.location
offset_to_screen_x, offset_to_screen_y = self.browser_offsets
element_x = element_location['x'] + offset_to_screen_x
element_y = element_location['y'] + offset_to_screen_y

element_size = element.size
centered_x = element_x + (element_size['width'] // 2)
centered_y = element_y + (element_size['height'] // 2)

return {
'x': coords['x'],
'y': coords['y']
'x': centered_x,
'y': centered_y
}

def find_and_move(self, element, click=False, offset_x=random.uniform(0.0, 1.5), offset_y=random.uniform(0.0, 1.5)):
Expand Down Expand Up @@ -52,42 +79,64 @@ def silent_type(self, text, characters_per_minute=280, offset=20):
time.sleep(delay)

def scroll_smoothly_to_element(self, element):
def is_element_in_viewport(element):
return self.driver.execute_script("""
var rect = arguments[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
""", element)

def scroll_to_element(element):
self.driver.execute_script("arguments[0].scrollIntoView({ behavior: 'smooth', block: 'start' });", element)

interval = 0.1
start_time = time.time()

while not is_element_in_viewport(element):
if time.time() - start_time > 5:
break

scroll_to_element(element)
time.sleep(interval)
self._get_browser_properties_if_not_found()

element_rect = element.rect
window_width = self.browser_inner_window[0]
window_height = self.browser_inner_window[1]

scroll_amount = element_rect['y'] - window_height // 2
scroll_steps = abs(scroll_amount) // 100

if scroll_amount > 0:
scroll_direction = -1
else:
scroll_direction = 1

for _ in range(scroll_steps):
pyautogui.scroll(scroll_direction * 100)
time.sleep(random.uniform(0.05, 0.1))

remaining_scroll = scroll_amount % 100
if remaining_scroll != 0:
pyautogui.scroll(scroll_direction * remaining_scroll)
time.sleep(random.uniform(0.05, 0.1))

class EmuniumPpeteer:
def __init__(self, page):
self.page = page
self.clicker = pyclick.HumanClicker()
self.browser_offsets = ()
self.browser_inner_window = ()

async def _get_browser_properties_if_not_found(self):
if not self.browser_offsets or not self.browser_inner_window:
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
temp_screen_path = temp_file.name
await self.page.screenshot(path=temp_screen_path)
location = pyautogui.locateOnScreen(temp_screen_path, confidence=0.8)
self.browser_offsets = (location.left, location.top,)
self.browser_inner_window = get_image_size(temp_screen_path)
os.remove(temp_screen_path)

async def get_center(self, element):
return await self.page.evaluate('''(element) => {
const rect = element.getBoundingClientRect();
const centerX = rect.left + rect.width / 2 + window.scrollX;
const centerY = rect.top + rect.height / 2 + (window.scrollY || window.pageYOffset) + (screen.height - window.innerHeight) / 1.4;
return {x: centerX, y: centerY};
}''', element)
await self._get_browser_properties_if_not_found()

rect = await element.boundingBox()
if rect is None:
return None

offset_to_screen_x, offset_to_screen_y = self.browser_offsets
element_x = rect['x'] + offset_to_screen_x
element_y = rect['y'] + offset_to_screen_y
element_width = rect['width']
element_height = rect['height']
centered_x = element_x + (element_width // 2)
centered_y = element_y + (element_height // 2)
return {
'x': centered_x,
'y': centered_y
}

async def find_and_move(self, element, click=False, offset_x=random.uniform(0.0, 1.5), offset_y=random.uniform(0.0, 1.5)):
center = await self.get_center(element)
Expand Down Expand Up @@ -115,26 +164,28 @@ async def silent_type(self, text, characters_per_minute=280, offset=20):
await asyncio.sleep(delay)

async def scroll_smoothly_to_element(self, element):
is_element_in_viewport = await self.page.evaluate('''(element) => {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}''', element)

scroll_to_element = await self.page.evaluate('''(element) => {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}''', element)

interval = 0.1
start_time = time.time()

while not is_element_in_viewport:
if time.time() - start_time > 5:
break

await scroll_to_element
await asyncio.sleep(interval)
await self._get_browser_properties_if_not_found()

element_rect = await element.boundingBox()
if element_rect is None:
return None

window_width = self.browser_inner_window[0]
window_height = self.browser_inner_window[1]

scroll_amount = element_rect['y'] - window_height // 2
scroll_steps = abs(scroll_amount) // 100

if scroll_amount > 0:
scroll_direction = -1
else:
scroll_direction = 1

for _ in range(scroll_steps):
pyautogui.scroll(scroll_direction * 100)
await asyncio.sleep(random.uniform(0.05, 0.1))

remaining_scroll = scroll_amount % 100
if remaining_scroll != 0:
pyautogui.scroll(scroll_direction * remaining_scroll)
await asyncio.sleep(random.uniform(0.05, 0.1))
Binary file modified preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified preview_ppeteer.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='emunium',
version='1.0.2',
version='1.5.0',
author='Maehdakvan',
author_email='visitanimation@google.com',
description='A Python module for automating interactions to mimic human behavior in browsers when using Selenium or Pyppeteer. Provides utilities to programmatically move the mouse cursor, click on page elements, type text, and scroll as if performed by a human user.',
Expand Down

0 comments on commit 39bfa89

Please sign in to comment.