624 lines
16 KiB
Python
624 lines
16 KiB
Python
import math
|
||
import random
|
||
from typing import Any
|
||
|
||
|
||
# 生成类人的鼠标轨迹
|
||
def generate_realistic_trajectory(
|
||
start_x: int, start_y: int, end_x: int, end_y: int, start_time: int
|
||
) -> list[Any]:
|
||
"""生成类人的鼠标轨迹"""
|
||
trajectory = []
|
||
|
||
# 计算距离和步数
|
||
distance = ((end_x - start_x) ** 2 + (end_y - start_y) ** 2) ** 0.5
|
||
steps = int(distance / 2) + random.randint(5, 15) # 根据距离动态调整步数
|
||
|
||
current_time = start_time
|
||
current_x, current_y = start_x, start_y
|
||
|
||
for i in range(steps):
|
||
# 使用贝塞尔曲线模拟自然移动
|
||
progress = i / steps
|
||
|
||
# 添加缓动函数(开始快,中间慢,结束快)
|
||
if progress < 0.3:
|
||
eased = progress / 0.3 * 0.2
|
||
elif progress < 0.7:
|
||
eased = 0.2 + (progress - 0.3) / 0.4 * 0.5
|
||
else:
|
||
eased = 0.7 + (progress - 0.7) / 0.3 * 0.3
|
||
|
||
# 计算目标位置(添加随机抖动)
|
||
target_x = start_x + (end_x - start_x) * eased
|
||
target_y = start_y + (end_y - start_y) * eased
|
||
|
||
# 添加微小的随机偏移(模拟手抖)
|
||
jitter_x = random.uniform(-0.5, 0.5)
|
||
jitter_y = random.uniform(-0.5, 0.5)
|
||
|
||
current_x = int(target_x + jitter_x)
|
||
current_y = int(target_y + jitter_y)
|
||
|
||
# 随机时间间隔(3-25ms,符合人类反应)
|
||
time_delta = random.choices(
|
||
[3, 4, 5, 6, 7, 8, 10, 12, 15, 20, 24],
|
||
weights=[5, 8, 10, 12, 10, 8, 5, 3, 2, 1, 1],
|
||
)[0]
|
||
current_time += time_delta
|
||
|
||
trajectory.append(["move", current_x, current_y, current_time, "pointermove"])
|
||
|
||
# 偶尔在同一位置停留(模拟视觉确认)
|
||
if random.random() < 0.15:
|
||
trajectory.append(
|
||
[
|
||
"move",
|
||
current_x,
|
||
current_y,
|
||
current_time + random.randint(5, 15),
|
||
"pointermove",
|
||
]
|
||
)
|
||
current_time += random.randint(5, 15)
|
||
|
||
# 到达目标后的悬停
|
||
hover_time = random.randint(50, 150)
|
||
for _ in range(random.randint(2, 5)):
|
||
current_time += random.randint(8, 25)
|
||
trajectory.append(
|
||
[
|
||
"move",
|
||
end_x + random.randint(-1, 1),
|
||
end_y + random.randint(-1, 1),
|
||
current_time,
|
||
"pointermove",
|
||
]
|
||
)
|
||
|
||
current_time += hover_time
|
||
|
||
# 点击事件
|
||
trajectory.append(["down", end_x, end_y, current_time, "pointerdown"])
|
||
|
||
trajectory.append(["focus", current_time + 1])
|
||
|
||
click_duration = random.randint(80, 130)
|
||
trajectory.append(["up", end_x, end_y, current_time + click_duration, "pointerup"])
|
||
|
||
return trajectory
|
||
|
||
|
||
# 处理原始轨迹数组
|
||
def process_mouse_trajectory(
|
||
events: list[Any], max_records: int | None = None
|
||
) -> dict[str, Any]:
|
||
"""
|
||
处理鼠标/触摸轨迹数据,将绝对坐标转换为相对坐标和时间差
|
||
|
||
参数:
|
||
events: 原始事件数据列表
|
||
max_records: 最大保留记录数(None表示保留全部)
|
||
|
||
返回:
|
||
处理后的事件列表
|
||
"""
|
||
if not events or len(events) == 0:
|
||
return {"data": [], "first_event": None, "last_event": None, "total_events": 0}
|
||
|
||
# 初始化变量
|
||
prev_x = 0 # 上一个X坐标
|
||
prev_y = 0 # 上一个Y坐标
|
||
prev_time = 0 # 上一个时间戳
|
||
result = [] # 结果数组
|
||
first_event = None # 第一个事件
|
||
last_event = None # 最后一个事件
|
||
|
||
# 移动类事件(包含坐标信息)
|
||
MOVE_EVENTS = ["move", "mousemove", "touchmove", "pointermove"]
|
||
|
||
# 点击类事件(仅时间信息)
|
||
CLICK_EVENTS = [
|
||
"down",
|
||
"up",
|
||
"click",
|
||
"mousedown",
|
||
"mouseup",
|
||
"touchstart",
|
||
"touchend",
|
||
"pointerdown",
|
||
"pointerup",
|
||
]
|
||
|
||
# 特殊事件(仅时间信息)
|
||
TIME_ONLY_EVENTS = ["focus", "blur", "keydown", "keyup"]
|
||
|
||
# 如果设置了最大记录数,只处理最后N条
|
||
start_index = 0
|
||
if max_records and len(events) > max_records:
|
||
start_index = len(events) - max_records
|
||
|
||
# 遍历事件
|
||
for i in range(start_index, len(events)):
|
||
event = events[i]
|
||
event_type = event[0]
|
||
|
||
# 处理移动类事件(包含X, Y坐标)
|
||
if event_type in MOVE_EVENTS:
|
||
x = event[1]
|
||
y = event[2]
|
||
timestamp = event[3]
|
||
|
||
# 记录第一个和最后一个事件
|
||
if first_event is None:
|
||
first_event = event
|
||
last_event = event
|
||
|
||
# 计算相对坐标差值
|
||
delta_x = x - prev_x
|
||
delta_y = y - prev_y
|
||
|
||
# 计算时间差
|
||
if prev_time == 0:
|
||
time_diff = 0 # 第一个事件时间差为0
|
||
else:
|
||
time_diff = timestamp - prev_time
|
||
|
||
# 添加到结果数组
|
||
result.append([event_type, [delta_x, delta_y], time_diff])
|
||
|
||
# 更新上一次的值
|
||
prev_x = x
|
||
prev_y = y
|
||
prev_time = timestamp
|
||
|
||
# 处理点击类事件(包含坐标但只记录时间差)
|
||
elif event_type in CLICK_EVENTS:
|
||
timestamp = event[3] if len(event) > 3 else event[1]
|
||
|
||
# 计算时间差
|
||
if prev_time == 0:
|
||
time_diff = 0
|
||
else:
|
||
time_diff = timestamp - prev_time
|
||
|
||
# 添加到结果数组(坐标差为[0,0])
|
||
result.append([event_type, [0, 0], time_diff])
|
||
|
||
prev_time = timestamp
|
||
|
||
# 处理仅时间类事件(如focus)
|
||
elif event_type in TIME_ONLY_EVENTS:
|
||
timestamp = event[1]
|
||
|
||
# 计算时间差
|
||
if prev_time == 0:
|
||
time_diff = 0
|
||
else:
|
||
time_diff = timestamp - prev_time
|
||
|
||
# 添加到结果数组(仅包含时间差)
|
||
result.append([event_type, time_diff])
|
||
|
||
prev_time = timestamp
|
||
|
||
return {
|
||
"data": result,
|
||
"first_event": first_event,
|
||
"last_event": last_event,
|
||
"total_events": len(result),
|
||
}
|
||
|
||
|
||
def compress_trajectory(e: list[Any]) -> str:
|
||
"""
|
||
压缩轨迹数据的完整实现
|
||
|
||
Args:
|
||
e: 轨迹数据列表
|
||
|
||
Returns:
|
||
压缩后的 Base64 编码字符串
|
||
"""
|
||
# 事件类型映射
|
||
p = {
|
||
"move": 0,
|
||
"down": 1,
|
||
"up": 2,
|
||
"scroll": 3,
|
||
"focus": 4,
|
||
"blur": 5,
|
||
"unload": 6,
|
||
"unknown": 7,
|
||
}
|
||
|
||
def h(e, t):
|
||
"""
|
||
填充二进制字符串
|
||
|
||
Args:
|
||
e: 数值
|
||
t: 目标长度
|
||
|
||
Returns:
|
||
填充后的二进制字符串
|
||
"""
|
||
n = bin(e)[2:] # 转为二进制并去掉 '0b' 前缀
|
||
r = ""
|
||
o = len(n) + 1
|
||
while o <= t:
|
||
r += "0"
|
||
o += 1
|
||
return r + n
|
||
|
||
def f(e):
|
||
"""
|
||
压缩事件类型数组
|
||
|
||
Args:
|
||
e: 事件类型列表
|
||
|
||
Returns:
|
||
压缩后的二进制字符串
|
||
"""
|
||
t = []
|
||
n = len(e)
|
||
r = 0
|
||
|
||
# 游程编码(Run-Length Encoding)
|
||
while r < n:
|
||
o = e[r]
|
||
i = 0
|
||
while True:
|
||
if 16 <= i:
|
||
break
|
||
s = r + i + 1
|
||
if n <= s:
|
||
break
|
||
if e[s] != o:
|
||
break
|
||
i += 1
|
||
|
||
r = r + 1 + i
|
||
a = p[o]
|
||
if i != 0:
|
||
t.append(8 | a) # 设置重复标志位
|
||
t.append(i - 1)
|
||
else:
|
||
t.append(a)
|
||
|
||
# 编码长度信息
|
||
_ = h(32768 | n, 16)
|
||
c = ""
|
||
for l in range(len(t)):
|
||
c += h(t[l], 4)
|
||
|
||
return _ + c
|
||
|
||
def c(e, t):
|
||
"""
|
||
对数组每个元素应用函数
|
||
|
||
Args:
|
||
e: 输入数组
|
||
t: 转换函数
|
||
|
||
Returns:
|
||
转换后的数组
|
||
"""
|
||
n = []
|
||
for r in range(len(e)):
|
||
n.append(t(e[r]))
|
||
return n
|
||
|
||
def d(e, t):
|
||
"""
|
||
压缩数值数组(游程编码 + 变长编码)
|
||
|
||
Args:
|
||
e: 数值数组
|
||
t: 是否为坐标数据(需要过滤符号位)
|
||
|
||
Returns:
|
||
压缩后的二进制字符串
|
||
"""
|
||
|
||
# 第一步:限制数值范围到 [-32767, 32767]
|
||
def limit_value(val):
|
||
limit = 32767
|
||
return max(-limit, min(limit, val))
|
||
|
||
e = c(e, limit_value)
|
||
|
||
# 第二步:游程编码(Run-Length Encoding)
|
||
n = len(e)
|
||
r = 0
|
||
o = []
|
||
|
||
while r < n:
|
||
i = 1
|
||
s = e[r]
|
||
a = abs(s)
|
||
|
||
# 统计连续相同的值
|
||
while r + i < n and e[r + i] == s and a < 127 and i < 127:
|
||
i += 1
|
||
|
||
if i > 1:
|
||
# 重复值编码格式:
|
||
# 位15: 符号标志 (1=负数49152, 0=正数32768)
|
||
# 位14-7: 重复次数 (i)
|
||
# 位6-0: 绝对值 (a)
|
||
o.append((49152 if s < 0 else 32768) | (i << 7) | a)
|
||
else:
|
||
o.append(s)
|
||
|
||
r += i
|
||
|
||
e = o
|
||
|
||
# 第三步:变长编码
|
||
r = [] # 存储每个数字的十六进制位数
|
||
o = [] # 存储实际数值
|
||
|
||
for val in e:
|
||
# 计算需要多少个十六进制位(每位4 bit)
|
||
if val == 0:
|
||
bits = 1
|
||
else:
|
||
# 方法1:使用对数(与原JS一致)
|
||
bits = math.ceil(math.log(abs(val) + 1) / math.log(16))
|
||
# 方法2:直接计算(更快)
|
||
# bits = len(format(abs(val), 'x'))
|
||
|
||
bits = max(1, bits)
|
||
|
||
r.append(h(bits - 1, 2)) # 2位十六进制存储位数信息
|
||
o.append(h(abs(val), 4 * bits)) # 实际值
|
||
|
||
i = "".join(r) # 元数据
|
||
s = "".join(o) # 数据
|
||
|
||
# 第四步:符号位编码(仅用于坐标数据)
|
||
if t:
|
||
# 过滤掉:0值 和 已编码符号的压缩值(位15=1)
|
||
filtered = [x for x in e if x != 0 and (x >> 15) != 1]
|
||
n = "".join(["1" if x < 0 else "0" for x in filtered])
|
||
else:
|
||
n = ""
|
||
|
||
# 最终格式:[头部16位][元数据][数据][符号位]
|
||
# 头部:最高位置1 + 数组长度
|
||
return h(32768 | len(e), 16) + i + s + n
|
||
|
||
# 主函数:数据分离
|
||
t = [] # 事件类型
|
||
n = [] # 时间差
|
||
r = [] # X坐标
|
||
o = [] # Y坐标
|
||
|
||
for i in range(len(e)):
|
||
a = e[i]
|
||
length = len(a)
|
||
|
||
t.append(a[0])
|
||
n.append(a[1] if length == 2 else a[2])
|
||
|
||
if length == 3:
|
||
r.append(a[1][0])
|
||
o.append(a[1][1])
|
||
|
||
# 压缩各部分
|
||
c_str = f(t) + d(n, False) + d(r, True) + d(o, True)
|
||
|
||
# 填充到6的倍数
|
||
l = len(c_str)
|
||
if l % 6 != 0:
|
||
c_str += h(0, 6 - l % 6)
|
||
|
||
# Base64编码
|
||
def u(e):
|
||
"""Base64编码"""
|
||
t = ""
|
||
n = len(e) // 6
|
||
base64_chars = (
|
||
"()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"
|
||
)
|
||
|
||
for r in range(n):
|
||
# 每次取6位二进制
|
||
binary_str = e[6 * r : 6 * (r + 1)]
|
||
index = int(binary_str, 2)
|
||
t += base64_chars[index]
|
||
|
||
return t
|
||
|
||
return u(c_str)
|
||
|
||
|
||
class TrajectoryEncoder:
|
||
def __init__(self) -> None:
|
||
self.CHARSET = (
|
||
"()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"
|
||
)
|
||
self.BASE = len(self.CHARSET) # 64
|
||
self.DIRECTION_CHARS = "stuvwxyz~"
|
||
self.DIRECTION_PATTERNS = [
|
||
[1, 0], # s
|
||
[2, 0], # t
|
||
[1, -1], # u
|
||
[1, 1], # v
|
||
[0, 1], # w
|
||
[0, -1], # x
|
||
[3, 0], # y
|
||
[2, -1], # z
|
||
[2, 1], # ~
|
||
]
|
||
|
||
def encode_number(self, num):
|
||
"""编码单个数值为64进制"""
|
||
abs_num = abs(num)
|
||
high_index = abs_num // self.BASE
|
||
low_index = abs_num % self.BASE
|
||
|
||
result = ""
|
||
|
||
# 负数标记
|
||
if num < 0:
|
||
result += "!"
|
||
|
||
# 高位(当值>=64时)
|
||
if high_index > 0 and high_index < self.BASE:
|
||
result += "$"
|
||
result += self.CHARSET[high_index]
|
||
|
||
# 低位
|
||
result += self.CHARSET[low_index]
|
||
|
||
return result
|
||
|
||
def compress_trajectory(self, points):
|
||
"""压缩轨迹:计算相邻点差值"""
|
||
compressed = []
|
||
time_accumulator = 0
|
||
|
||
for i in range(len(points) - 1):
|
||
dx = points[i + 1][0] - points[i][0] # 不要用abs
|
||
dy = points[i + 1][1] - points[i][1] # 不要用abs
|
||
dt = abs(points[i + 1][2] - points[i][2]) # 时间可以用abs
|
||
|
||
# 跳过完全相同的点
|
||
if dx == 0 and dy == 0 and dt == 0:
|
||
continue
|
||
|
||
# 位置不变只累积时间
|
||
if dx == 0 and dy == 0:
|
||
time_accumulator += dt
|
||
else:
|
||
compressed.append([dx, dy, dt + time_accumulator])
|
||
time_accumulator = 0
|
||
|
||
# 处理剩余时间
|
||
if time_accumulator != 0:
|
||
compressed.append([0, 0, time_accumulator])
|
||
|
||
return compressed
|
||
|
||
def get_direction_code(self, dx, dy):
|
||
"""识别是否匹配方向模式"""
|
||
for i, pattern in enumerate(self.DIRECTION_PATTERNS):
|
||
if dx == pattern[0] and dy == pattern[1]:
|
||
return self.DIRECTION_CHARS[i]
|
||
return None
|
||
|
||
def encode(self, trajectory):
|
||
"""
|
||
编码轨迹
|
||
trajectory: [[x, y, timestamp], ...]
|
||
返回: 编码后的字符串
|
||
"""
|
||
compressed = self.compress_trajectory(trajectory)
|
||
|
||
x_encoded = []
|
||
y_encoded = []
|
||
t_encoded = []
|
||
|
||
for dx, dy, dt in compressed:
|
||
direction_code = self.get_direction_code(dx, dy)
|
||
|
||
if direction_code:
|
||
# 匹配到方向模式,只记录y
|
||
y_encoded.append(direction_code)
|
||
else:
|
||
# 不匹配,完整编码x和y
|
||
x_encoded.append(self.encode_number(dx))
|
||
y_encoded.append(self.encode_number(dy))
|
||
|
||
# 时间总是编码
|
||
t_encoded.append(self.encode_number(dt))
|
||
|
||
# 拼接:x坐标 !! y坐标 !! 时间戳
|
||
return (
|
||
"".join(x_encoded) + "!!" + "".join(y_encoded) + "!!" + "".join(t_encoded)
|
||
)
|
||
|
||
def encrypt_string(self, e, t, n):
|
||
"""
|
||
JS加密函数的Python实现
|
||
|
||
参数:
|
||
e: 原始字符串
|
||
t: 加密参数数组
|
||
n: 十六进制字符串
|
||
"""
|
||
if not t or not n:
|
||
return e
|
||
|
||
o = 0 # 偏移量
|
||
i = e # 结果字符串
|
||
s = t[0] # 12
|
||
a = t[2] # 98
|
||
_ = t[4] # 43
|
||
|
||
# 每次读取2个字符(十六进制)
|
||
while o < len(n):
|
||
r = n[o : o + 2] # 取2个字符
|
||
if len(r) < 2:
|
||
break
|
||
o += 2
|
||
|
||
# 解析十六进制
|
||
c = int(r, 16)
|
||
|
||
# 转换为字符
|
||
l = chr(c)
|
||
|
||
# 计算插入位置: (s * c^2 + a * c + _) % len(e)
|
||
u = (s * c * c + a * c + _) % len(e)
|
||
|
||
# 在位置u插入字符
|
||
i = i[:u] + l + i[u:]
|
||
|
||
return i
|
||
|
||
|
||
def H(t: int, e: str) -> str:
|
||
# 解析后缀
|
||
n = e[-2:]
|
||
r = []
|
||
for char in n:
|
||
o = ord(char)
|
||
r.append(o - 87 if o > 57 else o - 48)
|
||
n = 36 * r[0] + r[1]
|
||
|
||
# 计算目标值
|
||
a = (t) + n
|
||
|
||
# 构建字符池
|
||
_ = [[], [], [], [], []]
|
||
c = {}
|
||
u = 0
|
||
for char in e[:-2]:
|
||
if char not in c:
|
||
c[char] = 1
|
||
_[u].append(char)
|
||
u = (u + 1) % 5
|
||
|
||
# 生成结果
|
||
f = a
|
||
d = 4
|
||
p = ""
|
||
g = [1, 2, 5, 10, 50]
|
||
|
||
while f > 0:
|
||
if f >= g[d]:
|
||
h = int(random.random() * len(_[d]))
|
||
p += _[d][h]
|
||
f -= g[d]
|
||
else:
|
||
_.pop(d)
|
||
g.pop(d)
|
||
d -= 1
|
||
|
||
return p
|