Skip to content

Commit ee738cb

Browse files
committed
Start commit
0 parents  commit ee738cb

File tree

8 files changed

+914
-0
lines changed

8 files changed

+914
-0
lines changed

.gitignore

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
.hypothesis/
48+
.pytest_cache/
49+
50+
# Translations
51+
*.mo
52+
*.pot
53+
54+
# Django stuff:
55+
*.log
56+
local_settings.py
57+
db.sqlite3
58+
59+
# Flask stuff:
60+
instance/
61+
.webassets-cache
62+
63+
# Scrapy stuff:
64+
.scrapy
65+
66+
# Sphinx documentation
67+
docs/_build/
68+
69+
# PyBuilder
70+
target/
71+
72+
# Jupyter Notebook
73+
.ipynb_checkpoints
74+
75+
# pyenv
76+
.python-version
77+
78+
# celery beat schedule file
79+
celerybeat-schedule
80+
81+
# SageMath parsed files
82+
*.sage.py
83+
84+
# Environments
85+
.env
86+
.venv
87+
env/
88+
venv/
89+
ENV/
90+
env.bak/
91+
venv.bak/
92+
93+
# Spyder project settings
94+
.spyderproject
95+
.spyproject
96+
97+
# Rope project settings
98+
.ropeproject
99+
100+
# mkdocs documentation
101+
/site
102+
103+
# mypy
104+
.mypy_cache/
105+
106+
# Visual Studio Code
107+
.vscode

README.md

Whitespace-only changes.

poetry.lock

Lines changed: 641 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[tool.poetry]
2+
name = "fastapitest"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Your Name <[email protected]>"]
6+
readme = "README.md"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.11"
10+
fastapi = "^0.105.0"
11+
uvicorn = "^0.25.0"
12+
pynput = "^1.7.6"
13+
pyautogui = "^0.9.54"
14+
pyperclip = "^1.8.2"
15+
16+
17+
[build-system]
18+
requires = ["poetry-core"]
19+
build-backend = "poetry.core.masonry.api"

src/main.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Union
2+
from fastapi import FastAPI
3+
from pydantic import BaseModel
4+
5+
from services.input_service import emu
6+
7+
class ClickOptions(BaseModel):
8+
x: Union[int, float]
9+
y: Union[int, float]
10+
width: Union[int, float]
11+
height: Union[int, float]
12+
value: str
13+
14+
15+
app = FastAPI()
16+
17+
@app.post("/move-click-paste")
18+
def read_click(data: ClickOptions):
19+
emu.simulate_move_click_paste(
20+
data.x,
21+
data.y,
22+
data.width,
23+
data.height,
24+
data.value
25+
)
26+
return data

src/services/input_service.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from services.mouse_service import Mouse, MouseSimulate
2+
from services.keyboard_service import Keyboard, KeyboardSimulator
3+
4+
5+
class KeyboardMouseEmulator:
6+
def __init__(self, mouse: Mouse, keyboard: Keyboard):
7+
self.mouse = mouse
8+
self.keyboard = keyboard
9+
10+
def simulate_mouse_movement(self, end):
11+
self.mouse.move(end)
12+
13+
def simulate_mouse_move_and_click(self, x, y, width, height):
14+
self.mouse.move_and_click_inside_rectangle(x, y, width, height)
15+
16+
def simulate_move_click_paste(self, x, y, width, height, value):
17+
self.simulate_mouse_move_and_click(x, y, width, height)
18+
self.keyboard.copy_and_insert(value)
19+
20+
21+
mouse = MouseSimulate()
22+
keyboard = KeyboardSimulator()
23+
24+
25+
emu = KeyboardMouseEmulator(mouse, keyboard)
26+

src/services/keyboard_service.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
3+
from pynput.keyboard import Key, Controller as KeyboardController
4+
import pyperclip
5+
6+
class Keyboard:
7+
def copy_to_buffer(self, value: str):
8+
raise NotImplementedError
9+
10+
def insert_buffer_value(self):
11+
raise NotImplementedError
12+
13+
def copy_and_insert(self, value: str):
14+
raise NotImplementedError
15+
16+
17+
class KeyboardSimulator(Keyboard):
18+
def __init__(self):
19+
self.cmd_key = Key.cmd if os.name == 'posix' else Key.ctrl
20+
self.keyboard = KeyboardController()
21+
22+
def copy_to_buffer(self, value: str):
23+
pyperclip.copy(value)
24+
25+
def insert_buffer_value(self):
26+
with self.keyboard.pressed(self.cmd_key):
27+
self.keyboard.tap('v')
28+
29+
def copy_and_insert(self, value: str):
30+
self.copy_to_buffer(value)
31+
self.insert_buffer_value()

src/services/mouse_service.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import time
2+
import random
3+
4+
import math
5+
import pyautogui
6+
from pytweening import easeInOutQuad
7+
8+
9+
pyautogui.FAILSAFE = False
10+
11+
12+
class Mouse:
13+
def move(self, end):
14+
raise NotImplementedError
15+
16+
def move_and_click(self, end):
17+
raise NotImplementedError
18+
19+
def move_and_click_inside_rectangle(self, x, y, width, height):
20+
raise NotImplementedError
21+
22+
23+
class MouseSimulate(Mouse):
24+
"""Укажи, что благодаря target_width можно менять скорость мышки
25+
Добавь при иницилизации класса размер адресной панели браузера
26+
"""
27+
def move(self, end, target_width=300):
28+
duration = self._calculate_duration(end, target_width)
29+
pyautogui.moveTo(
30+
end,
31+
duration=duration,
32+
tween=easeInOutQuad
33+
)
34+
35+
def move_and_click(self, end):
36+
self.move(end)
37+
time.sleep(0.5)
38+
pyautogui.click()
39+
40+
def move_and_click_inside_rectangle(self, x, y, width, height):
41+
target_x, target_y = self._get_inside_point(x, y, width, height)
42+
self.move_and_click((target_x, target_y))
43+
44+
def _calculate_duration(self, end, target_width):
45+
start = pyautogui.position()
46+
distance = math.hypot(end[0] - start[0], end[1] - start[1])
47+
48+
# Параметры a и b для формулы Фиттса, установлены эмпирически
49+
a = random.uniform(0.1, 0.15)
50+
b = random.uniform(0.1, 0.15)
51+
52+
# Расчет времени перемещения по закону Фиттса
53+
return a + b * math.log2((distance / target_width) + 1)
54+
55+
def _get_inside_point(self, x, y, width, height):
56+
center_x, center_y = x + width / 2, y + height / 2
57+
58+
target_x = int(random.gauss(center_x, width / 8))
59+
target_y = int(random.gauss(center_y, height / 8))
60+
61+
target_x = max(min(target_x, x + width), x) + 1
62+
target_y = max(min(target_y, y + height), y) + 114
63+
64+
return (target_x, target_y)

0 commit comments

Comments
 (0)