inti
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
import random
|
||||
import math
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
# 生成类人的鼠标轨迹
|
||||
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: Optional[int] = None) -> dict[str,Any]:
|
||||
"""
|
||||
处理鼠标/触摸轨迹数据,将绝对坐标转换为相对坐标和时间差
|
||||
|
||||
参数:
|
||||
events: 原始事件数据列表
|
||||
max_records: 最大保留记录数(None表示保留全部)
|
||||
|
||||
返回:
|
||||
处理后的事件列表
|
||||
"""
|
||||
if not events or len(events) == 0:
|
||||
return []
|
||||
|
||||
# 初始化变量
|
||||
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 = round(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
|
||||
Reference in New Issue
Block a user