ghost-driver 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. ghost_driver/__init__.py +5 -0
  2. ghost_driver/browser.py +49 -0
  3. ghost_driver/cookies.py +40 -0
  4. ghost_driver/diagnostics.py +50 -0
  5. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906284_passed_pct007p6_round2.json +542 -0
  6. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906291_passed_pct006p7_round3.json +308 -0
  7. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906299_passed_pct015p6_round4.json +416 -0
  8. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906315_passed_pct019p1_round5.json +1028 -0
  9. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906323_passed_pct019p1_round6.json +650 -0
  10. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906357_passed_pct039p6_round8.json +740 -0
  11. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906366_passed_pct047p1_round9.json +1154 -0
  12. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906382_passed_pct060p0_round10.json +1226 -0
  13. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906425_passed_pct060p0_round11.json +1226 -0
  14. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906482_passed_pct100p0_round14.json +1712 -0
  15. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906489_passed_pct100p0_round15.json +1172 -0
  16. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906501_passed_pct012p4_round16.json +578 -0
  17. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906563_passed_pct039p1_round17.json +1262 -0
  18. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906649_passed_pct067p6_round22.json +920 -0
  19. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906658_passed_pct026p2_round23.json +1118 -0
  20. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906667_passed_pct033p3_round24.json +794 -0
  21. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906674_passed_pct038p2_round25.json +704 -0
  22. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906690_passed_pct046p7_round26.json +1262 -0
  23. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906700_passed_pct048p0_round27.json +920 -0
  24. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906707_passed_pct055p6_round28.json +812 -0
  25. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906715_passed_pct053p3_round29.json +1154 -0
  26. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906722_passed_pct000p4_round30.json +686 -0
  27. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906741_passed_pct065p8_round31.json +1064 -0
  28. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906753_passed_pct056p4_round32.json +1208 -0
  29. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906763_passed_pct071p1_round33.json +884 -0
  30. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906772_passed_pct052p9_round34.json +1028 -0
  31. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906799_passed_pct043p6_round36.json +614 -0
  32. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906824_passed_pct000p0_round38.json +398 -0
  33. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906841_passed_pct052p9_round39.json +542 -0
  34. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787906849_passed_pct040p9_round40.json +956 -0
  35. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907169_passed_pctNA_round1.json +20 -0
  36. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907173_passed_pctNA_round2.json +1 -0
  37. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907198_passed_pct091p1_round3.json +884 -0
  38. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907204_passed_pct092p4_round4.json +704 -0
  39. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907226_passed_pct028p0_round5.json +758 -0
  40. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907237_passed_pct088p9_round6.json +722 -0
  41. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907245_passed_pct000p0_round7.json +596 -0
  42. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907254_passed_pct016p9_round8.json +416 -0
  43. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907262_passed_pct026p2_round9.json +524 -0
  44. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787907283_passed_pct071p1_round10.json +992 -0
  45. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912039_passed_pctNA_round1.json +1 -0
  46. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912050_passed_pct012p4_round2.json +974 -0
  47. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912064_passed_pct017p8_round3.json +470 -0
  48. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912070_passed_pct024p0_round4.json +614 -0
  49. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912078_passed_pct021p3_round5.json +524 -0
  50. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912097_passed_pct078p7_round6.json +1694 -0
  51. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912112_passed_pct073p8_round7.json +1658 -0
  52. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912193_passed_pct050p2_round8.json +866 -0
  53. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912211_passed_pct027p1_round9.json +974 -0
  54. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912223_passed_pct051p6_round10.json +1388 -0
  55. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912273_passed_pct007p1_round11.json +668 -0
  56. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912296_passed_pct064p9_round12.json +992 -0
  57. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912308_passed_pct017p3_round13.json +686 -0
  58. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912328_passed_pct042p2_round14.json +848 -0
  59. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912379_passed_pct063p6_round15.json +1136 -0
  60. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912394_passed_pct057p3_round16.json +1136 -0
  61. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912404_passed_pct010p2_round17.json +830 -0
  62. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912412_passed_pct030p7_round18.json +632 -0
  63. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912421_passed_pct026p7_round19.json +1118 -0
  64. ghost_driver/examples/e_disclosure_rotate_captcha/recorded_trajectories/1787912439_passed_pct026p2_round20.json +848 -0
  65. ghost_driver/fingerprint/__init__.py +3 -0
  66. ghost_driver/fingerprint/misc.py +100 -0
  67. ghost_driver/fingerprint/native.py +19 -0
  68. ghost_driver/fingerprint/webgl.py +33 -0
  69. ghost_driver/fingerprint/webrtc.py +42 -0
  70. ghost_driver/input/__init__.py +5 -0
  71. ghost_driver/input/cdp_replay.py +85 -0
  72. ghost_driver/input/drag.py +49 -0
  73. ghost_driver/input/trajectories.py +109 -0
  74. ghost_driver/input/x11.py +272 -0
  75. ghost_driver/logging_utils.py +31 -0
  76. ghost_driver/page_state.py +126 -0
  77. ghost_driver/patches.py +57 -0
  78. ghost_driver/recorder.py +221 -0
  79. ghost_driver/session.py +179 -0
  80. ghost_driver/solvers/__init__.py +52 -0
  81. ghost_driver/solvers/capguru.py +110 -0
  82. ghost_driver/solvers/cv_angle.py +105 -0
  83. ghost_driver/solving.py +103 -0
  84. ghost_driver-0.1.0.dist-info/METADATA +135 -0
  85. ghost_driver-0.1.0.dist-info/RECORD +87 -0
  86. ghost_driver-0.1.0.dist-info/WHEEL +4 -0
  87. ghost_driver-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,5 @@
1
+ from .browser import launch
2
+ from .logging_utils import enable_logging
3
+
4
+ __all__ = ["launch", "enable_logging"]
5
+ __version__ = "0.1.0"
@@ -0,0 +1,49 @@
1
+ import asyncio
2
+ from typing import Sequence
3
+
4
+ import nodriver as uc
5
+
6
+ DEFAULT_BROWSER_ARGS = [
7
+ "--use-gl=swiftshader",
8
+ "--use-angle=swiftshader",
9
+ ]
10
+
11
+
12
+ async def launch(
13
+ *,
14
+ headless: bool = False,
15
+ sandbox: bool = False,
16
+ browser_args: Sequence[str] | None = None,
17
+ max_attempts: int = 5,
18
+ retry_delay_s: float = 2.0,
19
+ **kwargs,
20
+ ) -> uc.Browser:
21
+ """Thin wrapper around nodriver.start() with this library's defaults
22
+ applied, plus a retry loop around the browser PROCESS launch itself
23
+ (Chrome under load -- containers, cold cache -- can fail to start
24
+ transiently; this is separate from nodriver's own CDP-handshake
25
+ connection-wait retry, which ghost_driver.patches extends).
26
+
27
+ --use-gl=swiftshader --use-angle=swiftshader default ON: under Xvfb,
28
+ WebGL was found completely ABSENT without these (getContext("webgl")
29
+ returned null outright -- not just a suspicious vendor/renderer
30
+ string, something no real consumer browser ever does).
31
+
32
+ Defaults to headless=False: ghost_driver.input.x11's real-X11-input
33
+ drag mechanism (this library's whole reason to exist) requires a real
34
+ mapped window, which --headless=new never creates. Pass a real
35
+ (virtual is fine, e.g. Xvfb) DISPLAY and a window manager alongside
36
+ it if you're running this outside an existing desktop session.
37
+ """
38
+ args = list(DEFAULT_BROWSER_ARGS)
39
+ if browser_args:
40
+ args.extend(browser_args)
41
+ last_error: Exception | None = None
42
+ for attempt in range(max_attempts):
43
+ try:
44
+ return await uc.start(headless=headless, sandbox=sandbox, browser_args=args, **kwargs)
45
+ except Exception as e:
46
+ last_error = e
47
+ if attempt + 1 < max_attempts:
48
+ await asyncio.sleep(retry_delay_s)
49
+ raise RuntimeError(f"could not launch browser after {max_attempts} attempts") from last_error
@@ -0,0 +1,40 @@
1
+ import time
2
+ from datetime import datetime, timezone
3
+
4
+ from nodriver import cdp
5
+
6
+
7
+ def describe(cookies: list) -> list[str]:
8
+ """One human-readable line per cookie -- name, a truncated value,
9
+ domain, and remaining lifetime -- for a quick print() after a solve.
10
+ Takes the same list `Network.get_cookies()` (or NavigateResult.cookies)
11
+ returns."""
12
+ now = time.time()
13
+ lines = []
14
+ for c in cookies:
15
+ if c.session or c.expires is None or c.expires < 0:
16
+ lifetime = "SESSION (dies when browser closes)"
17
+ else:
18
+ remaining_h = (c.expires - now) / 3600.0
19
+ expires_iso = datetime.fromtimestamp(c.expires, tz=timezone.utc).isoformat()
20
+ lifetime = f"expires {expires_iso} (~{remaining_h:.1f}h from now)"
21
+ lines.append(f"{c.name}={c.value[:24]}... domain={c.domain} -- {lifetime}")
22
+ return lines
23
+
24
+
25
+ def to_cookie_param(cookie: "cdp.network.Cookie") -> "cdp.network.CookieParam":
26
+ """Converts a cdp.network.Cookie (from Network.get_cookies()) into a
27
+ cdp.network.CookieParam (for Network.set_cookies()) -- these are
28
+ DIFFERENT types despite the overlapping fields. In particular
29
+ Cookie.expires is a plain float; CookieParam.expires needs it wrapped
30
+ in TimeSinceEpoch, which has the to_json() the CDP serializer calls --
31
+ a bare float doesn't, and fails with an unhelpful AttributeError deep
32
+ in serialization if passed straight through.
33
+ """
34
+ expires = cdp.network.TimeSinceEpoch(cookie.expires) if cookie.expires is not None else None
35
+ return cdp.network.CookieParam(
36
+ name=cookie.name, value=cookie.value, domain=cookie.domain, path=cookie.path,
37
+ secure=cookie.secure, http_only=cookie.http_only, same_site=cookie.same_site,
38
+ expires=expires, priority=cookie.priority, source_scheme=cookie.source_scheme,
39
+ source_port=cookie.source_port,
40
+ )
@@ -0,0 +1,50 @@
1
+ import json
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
+ from urllib.parse import parse_qs, urlparse
5
+
6
+
7
+ def save_html(findings_dir: Path | str, label: str, html: str | None) -> str:
8
+ findings_dir = Path(findings_dir)
9
+ findings_dir.mkdir(parents=True, exist_ok=True)
10
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H%M%S")
11
+ path = findings_dir / f"{ts}_{label}.html"
12
+ path.write_text(html or "", encoding="utf-8")
13
+ return str(path)
14
+
15
+
16
+ def save_json(findings_dir: Path | str, label: str, payload: dict) -> str:
17
+ findings_dir = Path(findings_dir)
18
+ findings_dir.mkdir(parents=True, exist_ok=True)
19
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H%M%S")
20
+ path = findings_dir / f"{ts}_{label}.json"
21
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
22
+ return str(path)
23
+
24
+
25
+ def short_url(url: str) -> str:
26
+ """Path + query KEYS only, values dropped -- some sites' flows carry
27
+ large opaque behavioral-telemetry blobs in query values (thousands of
28
+ chars each); printing full URLs to a live console makes every run
29
+ unreadable. Keep the full URL in saved JSON diagnostics (save_json)
30
+ for later analysis; use this only for human-readable log lines."""
31
+ parsed = urlparse(url)
32
+ keys = ",".join(parse_qs(parsed.query).keys())
33
+ return f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{keys}" if keys else url
34
+
35
+
36
+ async def dump(page, findings_dir: Path | str, label: str, extra: dict | None = None) -> str:
37
+ """Saves the live page's HTML plus whatever extra diagnostic payload
38
+ you pass, both timestamped consistently under one label so they're
39
+ easy to correlate on disk. Call this at every meaningful exit point of
40
+ an attempt, not just failures -- a "passed" that later turns out wrong
41
+ has nothing to inspect after the fact otherwise."""
42
+ try:
43
+ html = await page.evaluate("document.documentElement.outerHTML")
44
+ except Exception:
45
+ html = None
46
+ saved_html = save_html(findings_dir, f"{label}_html", html)
47
+ payload = {"label": label, "saved_html": saved_html}
48
+ if extra:
49
+ payload.update(extra)
50
+ return save_json(findings_dir, label, payload)
@@ -0,0 +1,542 @@
1
+ [
2
+ {
3
+ "t": 3277.9000000059605,
4
+ "type": "pointermove",
5
+ "x": 54,
6
+ "y": 719,
7
+ "controlLeft": 52,
8
+ "controlTop": 631,
9
+ "percent": 0
10
+ },
11
+ {
12
+ "t": 3278.2000000029802,
13
+ "type": "mousemove",
14
+ "x": 54,
15
+ "y": 719,
16
+ "controlLeft": 52,
17
+ "controlTop": 631,
18
+ "percent": 0
19
+ },
20
+ {
21
+ "t": 3285.6000000089407,
22
+ "type": "pointermove",
23
+ "x": 53,
24
+ "y": 711,
25
+ "controlLeft": 52,
26
+ "controlTop": 631,
27
+ "percent": 0
28
+ },
29
+ {
30
+ "t": 3285.800000011921,
31
+ "type": "mousemove",
32
+ "x": 53,
33
+ "y": 711,
34
+ "controlLeft": 52,
35
+ "controlTop": 631,
36
+ "percent": 0
37
+ },
38
+ {
39
+ "t": 3306.2000000029802,
40
+ "type": "pointermove",
41
+ "x": 53,
42
+ "y": 705,
43
+ "controlLeft": 52,
44
+ "controlTop": 631,
45
+ "percent": 0
46
+ },
47
+ {
48
+ "t": 3306.2000000029802,
49
+ "type": "mousemove",
50
+ "x": 53,
51
+ "y": 705,
52
+ "controlLeft": 52,
53
+ "controlTop": 631,
54
+ "percent": 0
55
+ },
56
+ {
57
+ "t": 3459.5,
58
+ "type": "pointermove",
59
+ "x": 70,
60
+ "y": 662,
61
+ "controlLeft": 52,
62
+ "controlTop": 631,
63
+ "percent": 0
64
+ },
65
+ {
66
+ "t": 3459.9000000059605,
67
+ "type": "mousemove",
68
+ "x": 70,
69
+ "y": 662,
70
+ "controlLeft": 52,
71
+ "controlTop": 631,
72
+ "percent": 0
73
+ },
74
+ {
75
+ "t": 3484.6000000089407,
76
+ "type": "pointermove",
77
+ "x": 71,
78
+ "y": 660,
79
+ "controlLeft": 52,
80
+ "controlTop": 631,
81
+ "percent": 0
82
+ },
83
+ {
84
+ "t": 3485.4000000059605,
85
+ "type": "mousemove",
86
+ "x": 71,
87
+ "y": 660,
88
+ "controlLeft": 52,
89
+ "controlTop": 631,
90
+ "percent": 0
91
+ },
92
+ {
93
+ "t": 3492.800000011921,
94
+ "type": "pointermove",
95
+ "x": 71,
96
+ "y": 659,
97
+ "controlLeft": 52,
98
+ "controlTop": 631,
99
+ "percent": 0
100
+ },
101
+ {
102
+ "t": 3493,
103
+ "type": "mousemove",
104
+ "x": 71,
105
+ "y": 659,
106
+ "controlLeft": 52,
107
+ "controlTop": 631,
108
+ "percent": 0
109
+ },
110
+ {
111
+ "t": 3506.800000011921,
112
+ "type": "pointermove",
113
+ "x": 71,
114
+ "y": 657,
115
+ "controlLeft": 52,
116
+ "controlTop": 631,
117
+ "percent": 0
118
+ },
119
+ {
120
+ "t": 3506.9000000059605,
121
+ "type": "mousemove",
122
+ "x": 71,
123
+ "y": 657,
124
+ "controlLeft": 52,
125
+ "controlTop": 631,
126
+ "percent": 0
127
+ },
128
+ {
129
+ "t": 3604.4000000059605,
130
+ "type": "pointermove",
131
+ "x": 72,
132
+ "y": 657,
133
+ "controlLeft": 52,
134
+ "controlTop": 631,
135
+ "percent": 0
136
+ },
137
+ {
138
+ "t": 3604.5,
139
+ "type": "mousemove",
140
+ "x": 72,
141
+ "y": 657,
142
+ "controlLeft": 52,
143
+ "controlTop": 631,
144
+ "percent": 0
145
+ },
146
+ {
147
+ "t": 3607.2000000029802,
148
+ "type": "pointermove",
149
+ "x": 72,
150
+ "y": 656,
151
+ "controlLeft": 52,
152
+ "controlTop": 631,
153
+ "percent": 0
154
+ },
155
+ {
156
+ "t": 3607.2000000029802,
157
+ "type": "mousemove",
158
+ "x": 72,
159
+ "y": 656,
160
+ "controlLeft": 52,
161
+ "controlTop": 631,
162
+ "percent": 0
163
+ },
164
+ {
165
+ "t": 3650.300000011921,
166
+ "type": "pointermove",
167
+ "x": 73,
168
+ "y": 656,
169
+ "controlLeft": 52,
170
+ "controlTop": 631,
171
+ "percent": 0
172
+ },
173
+ {
174
+ "t": 3650.800000011921,
175
+ "type": "mousemove",
176
+ "x": 73,
177
+ "y": 656,
178
+ "controlLeft": 52,
179
+ "controlTop": 631,
180
+ "percent": 0
181
+ },
182
+ {
183
+ "t": 3659.2000000029802,
184
+ "type": "pointermove",
185
+ "x": 73,
186
+ "y": 655,
187
+ "controlLeft": 52,
188
+ "controlTop": 631,
189
+ "percent": 0
190
+ },
191
+ {
192
+ "t": 3659.300000011921,
193
+ "type": "mousemove",
194
+ "x": 73,
195
+ "y": 655,
196
+ "controlLeft": 52,
197
+ "controlTop": 631,
198
+ "percent": 0
199
+ },
200
+ {
201
+ "t": 3679.800000011921,
202
+ "type": "pointermove",
203
+ "x": 75,
204
+ "y": 654,
205
+ "controlLeft": 52,
206
+ "controlTop": 631,
207
+ "percent": 0
208
+ },
209
+ {
210
+ "t": 3679.9000000059605,
211
+ "type": "mousemove",
212
+ "x": 75,
213
+ "y": 654,
214
+ "controlLeft": 52,
215
+ "controlTop": 631,
216
+ "percent": 0
217
+ },
218
+ {
219
+ "t": 3723.7000000029802,
220
+ "type": "pointermove",
221
+ "x": 76,
222
+ "y": 653,
223
+ "controlLeft": 52,
224
+ "controlTop": 631,
225
+ "percent": 0
226
+ },
227
+ {
228
+ "t": 3724,
229
+ "type": "mousemove",
230
+ "x": 76,
231
+ "y": 653,
232
+ "controlLeft": 52,
233
+ "controlTop": 631,
234
+ "percent": 0
235
+ },
236
+ {
237
+ "t": 3740,
238
+ "type": "pointermove",
239
+ "x": 77,
240
+ "y": 652,
241
+ "controlLeft": 52,
242
+ "controlTop": 631,
243
+ "percent": 0
244
+ },
245
+ {
246
+ "t": 3740.1000000089407,
247
+ "type": "mousemove",
248
+ "x": 77,
249
+ "y": 652,
250
+ "controlLeft": 52,
251
+ "controlTop": 631,
252
+ "percent": 0
253
+ },
254
+ {
255
+ "t": 3813.4000000059605,
256
+ "type": "pointermove",
257
+ "x": 77,
258
+ "y": 651,
259
+ "controlLeft": 52,
260
+ "controlTop": 631,
261
+ "percent": 0
262
+ },
263
+ {
264
+ "t": 3813.5,
265
+ "type": "mousemove",
266
+ "x": 77,
267
+ "y": 651,
268
+ "controlLeft": 52,
269
+ "controlTop": 631,
270
+ "percent": 0
271
+ },
272
+ {
273
+ "t": 3882.4000000059605,
274
+ "type": "pointermove",
275
+ "x": 77,
276
+ "y": 651,
277
+ "controlLeft": 52,
278
+ "controlTop": 631,
279
+ "percent": 0
280
+ },
281
+ {
282
+ "t": 3882.6000000089407,
283
+ "type": "mousemove",
284
+ "x": 77,
285
+ "y": 651,
286
+ "controlLeft": 52,
287
+ "controlTop": 631,
288
+ "percent": 0
289
+ },
290
+ {
291
+ "t": 3896.4000000059605,
292
+ "type": "pointerdown",
293
+ "x": 77,
294
+ "y": 651,
295
+ "controlLeft": 52,
296
+ "controlTop": 631,
297
+ "percent": 0
298
+ },
299
+ {
300
+ "t": 3916.7000000029802,
301
+ "type": "mousedown",
302
+ "x": 77,
303
+ "y": 651,
304
+ "controlLeft": 52,
305
+ "controlTop": 631,
306
+ "percent": 0
307
+ },
308
+ {
309
+ "t": 4136.20000000298,
310
+ "type": "pointermove",
311
+ "x": 78,
312
+ "y": 651,
313
+ "controlLeft": 52,
314
+ "controlTop": 631,
315
+ "percent": 0
316
+ },
317
+ {
318
+ "t": 4146.5,
319
+ "type": "mousemove",
320
+ "x": 78,
321
+ "y": 651,
322
+ "controlLeft": 52,
323
+ "controlTop": 631,
324
+ "percent": 0.4444444444444444
325
+ },
326
+ {
327
+ "t": 4155.300000011921,
328
+ "type": "pointermove",
329
+ "x": 80,
330
+ "y": 651,
331
+ "controlLeft": 52,
332
+ "controlTop": 631,
333
+ "percent": 0.4444444444444444
334
+ },
335
+ {
336
+ "t": 4158.20000000298,
337
+ "type": "mousemove",
338
+ "x": 80,
339
+ "y": 651,
340
+ "controlLeft": 52,
341
+ "controlTop": 631,
342
+ "percent": 1.3333333333333335
343
+ },
344
+ {
345
+ "t": 4174.800000011921,
346
+ "type": "pointermove",
347
+ "x": 82,
348
+ "y": 651,
349
+ "controlLeft": 52,
350
+ "controlTop": 631,
351
+ "percent": 1.3333333333333335
352
+ },
353
+ {
354
+ "t": 4175.4000000059605,
355
+ "type": "mousemove",
356
+ "x": 82,
357
+ "y": 651,
358
+ "controlLeft": 52,
359
+ "controlTop": 631,
360
+ "percent": 2.2222222222222223
361
+ },
362
+ {
363
+ "t": 4205.5,
364
+ "type": "pointermove",
365
+ "x": 84,
366
+ "y": 651,
367
+ "controlLeft": 52,
368
+ "controlTop": 631,
369
+ "percent": 2.2222222222222223
370
+ },
371
+ {
372
+ "t": 4206.600000008941,
373
+ "type": "mousemove",
374
+ "x": 84,
375
+ "y": 651,
376
+ "controlLeft": 52,
377
+ "controlTop": 631,
378
+ "percent": 3.111111111111111
379
+ },
380
+ {
381
+ "t": 4238.300000011921,
382
+ "type": "pointermove",
383
+ "x": 86,
384
+ "y": 651,
385
+ "controlLeft": 52,
386
+ "controlTop": 631,
387
+ "percent": 3.111111111111111
388
+ },
389
+ {
390
+ "t": 4239.5,
391
+ "type": "mousemove",
392
+ "x": 86,
393
+ "y": 651,
394
+ "controlLeft": 52,
395
+ "controlTop": 631,
396
+ "percent": 4
397
+ },
398
+ {
399
+ "t": 4306.300000011921,
400
+ "type": "pointermove",
401
+ "x": 87,
402
+ "y": 651,
403
+ "controlLeft": 52,
404
+ "controlTop": 631,
405
+ "percent": 4
406
+ },
407
+ {
408
+ "t": 4307,
409
+ "type": "mousemove",
410
+ "x": 87,
411
+ "y": 651,
412
+ "controlLeft": 52,
413
+ "controlTop": 631,
414
+ "percent": 4.444444444444445
415
+ },
416
+ {
417
+ "t": 4387,
418
+ "type": "pointermove",
419
+ "x": 88,
420
+ "y": 650,
421
+ "controlLeft": 52,
422
+ "controlTop": 631,
423
+ "percent": 4.444444444444445
424
+ },
425
+ {
426
+ "t": 4387.70000000298,
427
+ "type": "mousemove",
428
+ "x": 88,
429
+ "y": 650,
430
+ "controlLeft": 52,
431
+ "controlTop": 631,
432
+ "percent": 4.888888888888889
433
+ },
434
+ {
435
+ "t": 4405.70000000298,
436
+ "type": "pointermove",
437
+ "x": 90,
438
+ "y": 650,
439
+ "controlLeft": 52,
440
+ "controlTop": 631,
441
+ "percent": 4.888888888888889
442
+ },
443
+ {
444
+ "t": 4406.300000011921,
445
+ "type": "mousemove",
446
+ "x": 90,
447
+ "y": 650,
448
+ "controlLeft": 52,
449
+ "controlTop": 631,
450
+ "percent": 5.777777777777778
451
+ },
452
+ {
453
+ "t": 4456.4000000059605,
454
+ "type": "pointermove",
455
+ "x": 91,
456
+ "y": 650,
457
+ "controlLeft": 52,
458
+ "controlTop": 631,
459
+ "percent": 5.777777777777778
460
+ },
461
+ {
462
+ "t": 4457.600000008941,
463
+ "type": "mousemove",
464
+ "x": 91,
465
+ "y": 650,
466
+ "controlLeft": 52,
467
+ "controlTop": 631,
468
+ "percent": 6.222222222222222
469
+ },
470
+ {
471
+ "t": 4491.20000000298,
472
+ "type": "pointermove",
473
+ "x": 92,
474
+ "y": 650,
475
+ "controlLeft": 52,
476
+ "controlTop": 631,
477
+ "percent": 6.222222222222222
478
+ },
479
+ {
480
+ "t": 4492.100000008941,
481
+ "type": "mousemove",
482
+ "x": 92,
483
+ "y": 650,
484
+ "controlLeft": 52,
485
+ "controlTop": 631,
486
+ "percent": 6.666666666666667
487
+ },
488
+ {
489
+ "t": 4505.600000008941,
490
+ "type": "pointermove",
491
+ "x": 93,
492
+ "y": 650,
493
+ "controlLeft": 52,
494
+ "controlTop": 631,
495
+ "percent": 6.666666666666667
496
+ },
497
+ {
498
+ "t": 4506.4000000059605,
499
+ "type": "mousemove",
500
+ "x": 93,
501
+ "y": 650,
502
+ "controlLeft": 52,
503
+ "controlTop": 631,
504
+ "percent": 7.111111111111111
505
+ },
506
+ {
507
+ "t": 4548.5,
508
+ "type": "pointermove",
509
+ "x": 94,
510
+ "y": 650,
511
+ "controlLeft": 52,
512
+ "controlTop": 631,
513
+ "percent": 7.111111111111111
514
+ },
515
+ {
516
+ "t": 4548.9000000059605,
517
+ "type": "mousemove",
518
+ "x": 94,
519
+ "y": 650,
520
+ "controlLeft": 52,
521
+ "controlTop": 631,
522
+ "percent": 7.555555555555555
523
+ },
524
+ {
525
+ "t": 4610.300000011921,
526
+ "type": "pointerup",
527
+ "x": 94,
528
+ "y": 650,
529
+ "controlLeft": 52,
530
+ "controlTop": 631,
531
+ "percent": 7.555555555555555
532
+ },
533
+ {
534
+ "t": 4645.4000000059605,
535
+ "type": "mouseup",
536
+ "x": 94,
537
+ "y": 650,
538
+ "controlLeft": 52,
539
+ "controlTop": 631,
540
+ "percent": 7.555555555555555
541
+ }
542
+ ]