skip to content

Search

macos多开尝试.md

9 min read Updated:

多开 自动化测试

扩大收益的多开操作 哔站演示视屏 https://www.bilibili.com/video/BV1oSRaYcEdR/?share_source=copy_web&vd_source=9bac5bbae707472744b0b82e29f64577 youtube演示视频 https://youtu.be/VGrMBFtRh6Y?si=6NoBSK_UaVDZRqmy

alt text

在追求更高收益的过程中,多开账号成为一种常见的策略。例如,如果一个账号的收益达到100,那么开设 100 个账号就能实现 10,000 的收益。这种利润的直接扩大非常诱人。

多开的挑战 然而,进行多开操作并非没有成本。以指纹浏览器为例,它需要支付一定费用,包括 IP 和其他配置,这让人觉得比较麻烦。因此,我在思考是否存在一些低成本的手段来实现多开操作。

解决方案探索 最近,我在 Twitter 上看到一位老师分享了一些解决方案,因此也尝试在 Mac 上编写脚本,以实现群控操作。以下是一些关键点:

项目优化:通过脚本可以实现的多开操作 目前版本比较简陋 结论 尽管多开账号的功能在一定程度上能提高效率,但考虑到风险,很多人仍倾向于手动操作。总的来说,这种方案在实际应用中可能只是聊胜于无。

如果你对这个项目感兴趣,可以查看我在最上面链接中分享的演示视频。

hammerspoon安装: brew install —cask hammerspoon 安装好之后可以: mkdir -p ~/.hammerspoon && touch ~/.hammerspoon/init.lua 编辑lua脚本 内容填入 之后reload即可 显示这个插件的快捷键了 alt text

alt text
-- 初始化参数
local cols, rows = 3, 2  -- 3列2行网格
local mainWindow = nil	-- 主窗口对象
local mirrorWindows = {}  -- 镜像窗口集合
local isRecording = false -- 记录状态
local isReplaying = false -- 重放状态
local isAborting = false  -- 中断状态
local currentReplayTimer = nil -- 当前重放计时器

-- 窗口排列函数(带强制前置)
function arrangeBraveWindows()
	local mouseScreen = hs.mouse.getCurrentScreen()
	local screenFrame = mouseScreen:fullFrame()
	local winWidth = screenFrame.w / cols
	local winHeight = screenFrame.h / rows

	mainWindow = nil
	mirrorWindows = {}
	
	-- 收集有效Brave窗口
	local braveWindows = {}
	for _, win in ipairs(hs.window.allWindows()) do
		if win:application() and 
		   win:application():name() == "Brave Browser" and
		   win:isWindow() then
			table.insert(braveWindows, win)
		end
	end

	if #braveWindows == 0 then
		hs.alert("未找到有效Brave窗口", 2)
		return
	end

	-- 批量排列任务
	local layoutTasks = {}
	for i = 1, math.min(#braveWindows, 6) do
		local win = braveWindows[i]
		table.insert(layoutTasks, function(next)
			if not win or not win:isWindow() then
				print("跳过无效窗口:", win and win:title() or "nil")
				return next()
			end
			
			-- 强制前置窗口
			win:raise()
			win:focus()
			hs.timer.usleep(50000) -- 50ms激活等待
			
			local row = math.floor((i - 1) / cols)
			local col = (i - 1) % cols
			local x = screenFrame.x + col * winWidth
			local y = screenFrame.y + row * winHeight
			
			pcall(function()
				win:setFrame({x = x, y = y, w = winWidth, h = winHeight}, 0)
			end)
			
			hs.timer.doAfter(i*0.05, next)
		end)
	end

	-- 任务执行器
	local function runTasks(index)
		if index > #layoutTasks then
			-- 存储窗口数据
			for i, win in ipairs(braveWindows) do
				if not win:isWindow() then goto continue end
				
				local frameData = {
					x = screenFrame.x + ((i-1)%cols)*winWidth,
					y = screenFrame.y + math.floor((i-1)/cols)*winHeight,
					w = winWidth, 
					h = winHeight
				}
				
				if i == 1 then
					mainWindow = {
						winObj = win,
						frame = frameData,
						valid = function(self)
							return self.winObj and 
								   self.winObj:isWindow() and
								   self.winObj:application() and
								   self.winObj:application():isRunning()
						end
					}
				else
					table.insert(mirrorWindows, {
						winObj = win,
						frame = frameData,
						valid = function(self)
							return self.winObj and 
								   self.winObj:isWindow() and
								   self.winObj:application() and
								   self.winObj:application():isRunning()
						end
					})
				end
				::continue::
			end
			hs.alert(string.format("排列完成!有效窗口:%d", #braveWindows), 1.5)
			return
		end
		
		pcall(function()
			layoutTasks[index](function() 
				runTasks(index+1) 
			end)
		end)
	end
	runTasks(1)
end

-- 事件记录系统
local recordedEvents = {}
local recordStartTime = 0
local lastScrollTime = 0

local function recordEvent(eventType, event)
	if not isRecording or not (mainWindow and mainWindow.valid(mainWindow)) then return end

	-- 滚动事件优化处理
	if eventType == "scroll" then
		local now = hs.timer.absoluteTime()
		if now - lastScrollTime < 1e8 then  -- 100ms节流
			-- 替换最后一条滚动记录
			for i = #recordedEvents, 1, -1 do
				if recordedEvents[i].t == "s" then
					recordedEvents[i] = {
						t = "s",
						x = recordedEvents[i].x,
						y = recordedEvents[i].y,
						ts = now - recordStartTime,
						d = { y = event:getScrollDelta().y }
					}
					return
				end
			end
		end
		lastScrollTime = now
	end

	local mousePos = hs.mouse.absolutePosition()
	local frame = mainWindow.frame
	local relX = (mousePos.x - frame.x) / frame.w
	local relY = (mousePos.y - frame.y) / frame.h
	
	local eventData = {
		t = string.sub(eventType, 1, 1),  -- 事件类型简写
		x = math.floor(relX * 100) / 100,
		y = math.floor(relY * 100) / 100,
		ts = hs.timer.absoluteTime() - recordStartTime,
		d = {}
	}

	-- 事件数据填充
	if eventType == "scroll" then
		eventData.d.y = event:getScrollDelta().y
	elseif eventType == "key" then
		eventData.d.k = event:getKeyCode()
	end

	table.insert(recordedEvents, eventData)
end

-- 事件监听器
local eventTap = hs.eventtap.new({
	hs.eventtap.event.types.leftMouseDown,
	hs.eventtap.event.types.leftMouseUp,
	hs.eventtap.event.types.rightMouseDown,
	hs.eventtap.event.types.rightMouseUp,
	hs.eventtap.event.types.scrollWheel,
	hs.eventtap.event.types.keyDown
}, function(event)
	local typeMap = {
		[hs.eventtap.event.types.leftMouseDown]  = "left_click_down",
		[hs.eventtap.event.types.leftMouseUp]	= "left_click_up",
		[hs.eventtap.event.types.rightMouseDown] = "right_click_down",
		[hs.eventtap.event.types.rightMouseUp]   = "right_click_up",
		[hs.eventtap.event.types.scrollWheel]	= "scroll",
		[hs.eventtap.event.types.keyDown]		= "key"
	}
	recordEvent(typeMap[event:getType()], event)
	return false
end)

-- 增强异步重放引擎
local totalWindows = 0
local completedWindows = 0

local function asyncReplay(winData, events, index, callback)
	if isAborting then
		print("重放中断于窗口", completedWindows+1)
		return callback(true)
	end

	if not winData.valid(winData) then
		hs.alert("窗口失效,跳过重放", 1)
		return callback()
	end

	if index > #events then
		completedWindows = completedWindows + 1
		hs.alert(string.format("窗口 %d/%d 完成", completedWindows, totalWindows), 1)
		return callback()
	end

	local event = events[index]
	local delay = index > 1 and (event.ts - events[index-1].ts) / 1e6 or 0

	-- 执行事件前确保窗口前置
	-- winData.winObj:raise()
	-- winData.winObj:focus()
	hs.timer.usleep(30000) -- 30ms激活等待

	local execute = function()
		local frame = winData.frame
		local absX = frame.x + event.x * frame.w
		local absY = frame.y + event.y * frame.h

		if event.t == "l" or event.t == "r" then
			local button = event.t == "r" and hs.eventtap.rightClick or hs.eventtap.leftClick
			button(hs.mouse.absolutePosition({x = absX, y = absY}))
		elseif event.t == "s" then
			hs.eventtap.scrollWheel({
				deltaY = event.d.y * 40,  -- 放大滚动量
				unit = "pixel"			-- 像素级滚动
			}, nil, 0)
		elseif event.t == "k" then
			hs.eventtap.keyStroke({}, event.d.k, 0)
		end
	end

	currentReplayTimer = hs.timer.doAfter(delay / 1000, function()
		pcall(execute)
		if not isAborting then
			asyncReplay(winData, events, index + 1, callback)
		end
	end)
end

-- 镜像控制系统
function toggleMirroring()
	-- 中断处理逻辑
	if isReplaying then
		hs.alert("正在中断操作...", 1)
		isAborting = true
		if currentReplayTimer then
			currentReplayTimer:stop()
		end
		return
	end

	if isRecording then
		-- 停止记录
		isRecording = false
		eventTap:stop()
		hs.alert("记录停止,捕获事件:"..#recordedEvents, 1.5)

		if #recordedEvents == 0 or not mainWindow.valid(mainWindow) then
			recordedEvents = {}
			isAborting = false -- 重置中断状态
			return
		end

		-- 初始化重放状态
		isReplaying = true
		isAborting = false
		totalWindows = #mirrorWindows
		completedWindows = 0
		local originalPos = hs.mouse.absolutePosition()

		local function processNextWindow(index)
			if isAborting or index > #mirrorWindows then
				hs.alert(isAborting and "操作已中断" or "所有镜像完成!", 2)
				hs.mouse.absolutePosition(originalPos)
				recordedEvents = {}
				isReplaying = false
				isAborting = false -- 重置中断状态
				currentReplayTimer = nil
				return
			end

			local winData = mirrorWindows[index]
			hs.alert(string.format("开始镜像窗口 %d/%d", index, #mirrorWindows), 1)
			
			winData.winObj:raise()
			winData.winObj:focus()
			hs.timer.doAfter(0.5, function()  -- 500ms激活等待
				asyncReplay(winData, recordedEvents, 1, function(aborted)
					if not aborted then
						processNextWindow(index + 1)
					end
				end)
			end)
		end

		processNextWindow(1)
	else
		-- 开始记录
		if not (mainWindow and mainWindow.valid(mainWindow)) then
			hs.alert("请先排列窗口!", 2)
			return
		end

		mainWindow.winObj:raise()
		mainWindow.winObj:focus()
		hs.timer.usleep(100000)  -- 100ms确保焦点切换
		
		isRecording = true
		recordStartTime = hs.timer.absoluteTime()
		recordedEvents = {}
		lastScrollTime = 0
		eventTap:start()
		hs.alert("开始记录操作...", 1.5)
	end
end

-- 快捷键绑定
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "A", function()
	if not isReplaying and not isRecording then
		arrangeBraveWindows()
	end
end)

hs.hotkey.bind({"ctrl", "alt", "cmd"}, "S", function()
	if not isReplaying or not isRecording then
		toggleMirroring()
	end
end)

hs.hotkey.bind({"ctrl", "alt", "cmd"}, "D", function()
	if isReplaying or isRecording then
		toggleMirroring()
		hs.alert("强制中断所有操作", 1)
		recordedEvents = {}
		isAborting = false -- 重置中断状态
		isReplaying = false --重置重放
	end
end)

-- 系统初始化提示
hs.alert([[镜像系统 v3.2
⌃⌥⌘A - 窗口排列
⌃⌥⌘S - 开始/停止
⌃⌥⌘D - 强制中断

功能改进:
• 精确像素级滚动镜像
• 窗口激活延迟优化
• 操作中断保护机制
• 防止状态冲突]], 6)