前言:java.awt.Robot可以控制鼠标和键盘,本文基于此通过模拟认为添加微信好友的过程实现批量添加微信好友,并最终输出微信号/手机号是否有好友及好友的基本信息,本文代码示例禁用学习交流使用;

1 批量添加好友代码:
1.1 从txt 读取要搜索的好友并添加 AutoAddBat:

package org.example.wechat;

/**
 * 微信添加好友工具
 *
 * @Description TODO
 * @Date 2023/2/2 16:19
 * @Author lgx
 * @Version 1.0
 */

import org.example.wechat.dto.FriendBaseDto;
import org.example.wechat.enums.FriendSourceTypeEnum;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class AutoAddBat {
    // 微信界面坐标点
    static Map<String, List<Integer>> mapPostion = new LinkedHashMap<>(100);
    // 添加好友时填写的备注信息
    static Map<String, String> mapMyMsg = new LinkedHashMap<>(10);

    /**
     * 微信界面操作坐标点初始化
     * 注意:微信窗口每次被移动后都需要进行坐标位置的更新
     */
    static {
        mapPostion.put("添加好友按钮", getPostion(608, 165));
        mapPostion.put("输入内容搜索好友", getPostion(437, 169));
        mapPostion.put("点击搜索好友", getPostion(533, 228));
        mapPostion.put("复制搜索好友的昵称位置", getPostion(774, 226));
        mapPostion.put("复制搜索好友的昵称复制位置", getPostion(780, 235));
        mapPostion.put("复制搜索好友的地区位置", getPostion(789, 249));
        mapPostion.put("复制搜索好友的地区复制位置", getPostion(810, 260));
        mapPostion.put("添加清除上一次搜索好友按钮", getPostion(574, 168));
        mapPostion.put("添加通讯录按钮", getPostion(777, 336));
        mapPostion.put("填写发送添加朋友申请", getPostion(1035, 336));
        mapPostion.put("填写备注名", getPostion(1035, 415));
        mapPostion.put("填写标签", getPostion(1035, 487));
        mapPostion.put("确定添加好友", getPostion(857, 806));
        mapPostion.put("取消添加好友", getPostion(971, 826));


        mapMyMsg.put("添加朋友申请信息", "我是 xxxx");
        mapMyMsg.put("添加朋友备注信息", "他是 xxxx");
        mapMyMsg.put("添加朋友标签信息", "他所在的标签");
    }

    private static List<Integer> getPostion(int postionOne, int postionTwo) {
        List<Integer> postions = new ArrayList<>(2);
        postions.add(postionOne);
        postions.add(postionTwo);
        return postions;
    }

    public static void main(String[] args) throws InterruptedException {
        // 获取要搜索的
        List<FriendBaseDto> searchFriends = getSearchFriend(FriendSourceTypeEnum.从txt导入, "F:\\addwechat\\addWechatSource.txt");
        if (null == searchFriends || searchFriends.isEmpty()) {
            System.out.println("没有好友需要被添加");
        }
        AtomicBoolean isFirst = new AtomicBoolean(true);
        Robot robot = getRobot();
        searchFriends.stream().forEach(e -> {
            // 添加好友
            try {
                addFriendByOnece(robot, isFirst.get(), e);
            } catch (Exception ex) {
                e.setErrorMsg(ex.getMessage());
            }
            isFirst.set(false);
        });
        searchFriends.stream().forEach(e->{
            if (!e.isHasFriend()){
                System.out.println(e.getFriendSearch() + ":没有搜索到对应的好友");
            }else {
                System.out.print(e.getFriendSearch() + ":有对应的好友,");
                System.out.println("对应好友信息:"+e.toString());
            }
        });
    }

    /**
     * 获取要搜索的好友
     *
     * @param typeEnum
     * @return
     */
    private static List<FriendBaseDto> getSearchFriend(FriendSourceTypeEnum typeEnum, String fileUrl) {
        List<FriendBaseDto> friendBaseDtos = new ArrayList<>(1000);
        switch (typeEnum) {
            case 从txt导入:
                //
                friendBaseDtos.addAll(getSourceFriendBytxt(fileUrl));
                break;
            case 从docx导入:

                break;
            case 从excel导入:

                break;
            case 自定义:
                break;

        }
        return friendBaseDtos;
    }

    /**
     * 从txt导入
     *
     * @param fileName
     * @return
     */
    private static Collection<? extends FriendBaseDto> getSourceFriendBytxt(String fileName) {
        List<FriendBaseDto> friendBaseDtos = new ArrayList<>(1000);
        try {
            File myFile = new File(fileName);//通过字符串创建File类型对象,指向该字符串路径下的文件

            if (myFile.isFile() && myFile.exists()) { //判断文件是否存在

                InputStreamReader Reader = new InputStreamReader(new FileInputStream(myFile), "UTF-8");
                //考虑到编码格式,new FileInputStream(myFile)文件字节输入流,以字节为单位对文件中的数据进行读取
                //new InputStreamReader(FileInputStream a, "编码类型")
                //将文件字节输入流转换为文件字符输入流并给定编码格式

                BufferedReader bufferedReader = new BufferedReader(Reader);
                //BufferedReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
                //通过BuffereReader包装实现高效读取

                String lineTxt = null;
                FriendBaseDto oneDto = null;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                    //buffereReader.readLine()按行读取写成字符串
                    if ("".equals(lineTxt)) {
                        continue;
                    }
                    // System.out.println(lineTxt);
                    oneDto = new FriendBaseDto();
                    oneDto.setFriendSearch(lineTxt.trim());
                    friendBaseDtos.add(oneDto);
                }
                Reader.close();
            } else {
                System.out.println("找不到指定的文件");
            }
        } catch (Exception e) {

            System.out.println("读取文件内容出错");

            e.printStackTrace();
        }
        return friendBaseDtos;
    }

    /**
     * 添加微信好友
     *
     * @param robot
     */
    private static void addFriendByOnece(Robot robot, boolean isFirst, FriendBaseDto oneFriend) {
        if (isFirst) {
            // 第一次 打开微信
            openWxWindow(robot);
        }
        // 搜索好友
        startAddFrendOne(robot, oneFriend.getFriendSearch(), isFirst);
        // 搜索到的好友信息
        Map<String, Object> msgFriend = getFriendMsg(robot);
        // 判断是否搜索到对应的好友
        if (!(Boolean) msgFriend.get("hasFriend")) {
            // 没有好友
            // System.out.println("friendNickName+\"没有对应好友\" = " + oneFriend.getFriendSearch() + "没有对应好友");
            oneFriend.setHasFriend(false);
            return;
        }
        // 输出好友信息
        oneFriend.setHasFriend(true);
        msgFriend.entrySet().stream().forEach(e -> {
            System.out.println("e.getKey() +\":\"+e.getValue() = " + e.getKey() + ":" + e.getValue());
        });
        oneFriend.setNickName(msgFriend.get("昵称") + "");
        oneFriend.setArea(msgFriend.get("地区") + "");
        // 添加好友
        Map<String, Object> addFriendResult = addFriend(robot);
        // 输出结果
        addFriendResult.entrySet().stream().forEach(e -> {
            System.out.println("e.getKey() +\":\"+e.getValue() = " + e.getKey() + ":" + e.getValue());
        });
        oneFriend.setApplyMsg(addFriendResult.get("设置申请的信息") + "");
        oneFriend.setFriendRemark(addFriendResult.get("设置备注名称") + "");
        oneFriend.setFriendLabel(addFriendResult.get("设置标签") + "");
    }


    /**
     * 打开微信
     */
    private static void openWxWindow(Robot robot) {
        //打开微信 Ctrl+Alt+W
        assert robot != null;
        robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_ALT);
        robot.keyPress(KeyEvent.VK_W);
        //释放Ctrl按键,像Ctrl,退格键,删除键这样的功能性按键,在按下后一定要释放
        robot.keyRelease(KeyEvent.VK_CONTROL);
        robot.keyRelease(KeyEvent.VK_ALT);

        // 该延迟不能少,否则无法搜索
        robot.delay(1000);
    }

    /**
     * 搜索好友
     *
     * @param robot
     */
    private static void startAddFrendOne(Robot robot, String friendNickName, boolean isFirst) {
        if (null == friendNickName || "".equals(friendNickName.trim())) {
            System.out.println("要搜索的好友信息不能为空");
        }
        if (isFirst) {
            // 第一次需要先点击添加好友按钮
            // 鼠标移动到添加好友位置-- 模拟点击添加好友按钮
            robot.mouseMove(mapPostion.get("添加好友按钮").get(0), mapPostion.get("添加好友按钮").get(1));
            point(mapPostion.get("添加好友按钮").get(0), mapPostion.get("添加好友按钮").get(1), robot);
            robot.delay(500);
            // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
            robot.mousePress(MouseEvent.BUTTON1_MASK);
            robot.mouseRelease(MouseEvent.BUTTON1_MASK);
            robot.delay(500);
        } else {
            // 不是第一次需要清空之前搜索的好友
            clearFrinedSearchTxt(robot);
        }


        String Im = friendNickName;
        AutoIn(Im, mapPostion.get("输入内容搜索好友").get(0), mapPostion.get("输入内容搜索好友").get(1), robot);
        robot.delay(500);

        // 输入完毕后开始进行搜索
        robot.mouseMove(mapPostion.get("点击搜索好友").get(0), mapPostion.get("点击搜索好友").get(1));
        point(mapPostion.get("点击搜索好友").get(0), mapPostion.get("点击搜索好友").get(1), robot);
        robot.delay(500);
        // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
        robot.mousePress(MouseEvent.BUTTON1_MASK);
        robot.mouseRelease(MouseEvent.BUTTON1_MASK);
        robot.delay(1000);
    }

    /**
     * 清除上一次搜索好友内容
     *
     * @param robot
     */
    private static void clearFrinedSearchTxt(Robot robot) {
        AutoClick(mapPostion.get("添加清除上一次搜索好友按钮").get(0), mapPostion.get("添加清除上一次搜索好友按钮").get(1), robot);
    }

    /**
     * 好友信息
     *
     * @return
     */
    private static Map<String, Object> getFriendMsg(Robot robot) {
        Map<String, Object> msgFriend = new HashMap<>(5);
        // 好友昵称位置
        robot.mouseMove(mapPostion.get("复制搜索好友的昵称位置").get(0), mapPostion.get("复制搜索好友的昵称位置").get(1));
        point(mapPostion.get("复制搜索好友的昵称位置").get(0), mapPostion.get("复制搜索好友的昵称位置").get(1), robot);
        robot.delay(500);
        // 鼠标右键点击
        robot.mousePress(MouseEvent.BUTTON3_MASK);
        robot.mouseRelease(MouseEvent.BUTTON3_MASK);
        robot.delay(500);

        robot.mouseMove(mapPostion.get("复制搜索好友的昵称复制位置").get(0), mapPostion.get("复制搜索好友的昵称复制位置").get(1));
        point(mapPostion.get("复制搜索好友的昵称复制位置").get(0), mapPostion.get("复制搜索好友的昵称复制位置").get(1), robot);
        robot.delay(500);
        AutoClick(mapPostion.get("复制搜索好友的昵称复制位置").get(0), mapPostion.get("复制搜索好友的昵称复制位置").get(1), robot);
        robot.delay(500);

        String nickName = printCtrlC();
        if ("".equals(nickName)) {
            msgFriend.put("hasFriend", false);
            return msgFriend;
        } else {
            msgFriend.put("hasFriend", true);
            msgFriend.put("昵称", nickName);
        }

        // 好友地区位置
        robot.mouseMove(mapPostion.get("复制搜索好友的地区位置").get(0), mapPostion.get("复制搜索好友的地区位置").get(1));
        point(mapPostion.get("复制搜索好友的地区位置").get(0), mapPostion.get("复制搜索好友的地区位置").get(1), robot);
        robot.delay(500);
        // 鼠标右键点击
        robot.mousePress(MouseEvent.BUTTON3_MASK);
        robot.mouseRelease(MouseEvent.BUTTON3_MASK);
        robot.delay(500);

        robot.mouseMove(mapPostion.get("复制搜索好友的地区复制位置").get(0), mapPostion.get("复制搜索好友的地区复制位置").get(1));
        point(mapPostion.get("复制搜索好友的地区复制位置").get(0), mapPostion.get("复制搜索好友的地区复制位置").get(1), robot);
        robot.delay(500);
        AutoClick(mapPostion.get("复制搜索好友的地区复制位置").get(0), mapPostion.get("复制搜索好友的地区复制位置").get(1), robot);
        robot.delay(500);

        String area = printCtrlC();
        if ("".equals(area)) {
            msgFriend.put("地区", "");
        } else {
            msgFriend.put("地区", area);
        }

        return msgFriend;
    }

    /**
     * 添加好友
     *
     * @param robot
     */
    private static Map<String, Object> addFriend(Robot robot) {
        Map<String, Object> msgFriend = new HashMap<>(5);
        // 点击添加通讯录
        robot.mouseMove(mapPostion.get("添加通讯录按钮").get(0), mapPostion.get("添加通讯录按钮").get(1));
        point(mapPostion.get("添加通讯录按钮").get(0), mapPostion.get("添加通讯录按钮").get(1), robot);
        robot.delay(500);
        // Thread.sleep(1* 1000); // 休眠 1 秒
        // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
        robot.mousePress(MouseEvent.BUTTON1_MASK);
//        robot.delay(1000);
        robot.mouseRelease(MouseEvent.BUTTON1_MASK);
        robot.delay(500);

        // 设置申请的信息 我是xxx
        String myApplyMsg = mapMyMsg.get("添加朋友申请信息");
        AutoIm(myApplyMsg, mapPostion.get("填写发送添加朋友申请").get(0), mapPostion.get("填写发送添加朋友申请").get(1), robot);
        msgFriend.put("设置申请的信息", myApplyMsg);
        // 设置备注名称
        String friendRemark = mapMyMsg.get("添加朋友备注信息");
        AutoIm(friendRemark, mapPostion.get("填写备注名").get(0), mapPostion.get("填写备注名").get(1), robot);
        msgFriend.put("设置备注名称", friendRemark);
        // 设置标签
        String friendTag = mapMyMsg.get("添加朋友标签信息");
        AutoIm(friendTag, mapPostion.get("填写标签").get(0), mapPostion.get("填写标签").get(1), robot);
        msgFriend.put("设置标签", friendTag);
        // 确定添加
        // AutoClick(mapPostion.get("确定添加好友").get(0), mapPostion.get("确定添加好友").get(1), robot);
        // 取消
        AutoClick(mapPostion.get("取消添加好友").get(0), mapPostion.get("取消添加好友").get(1), robot);
        return msgFriend;
    }


    /**
     * 键盘赋值的内容
     *
     * @return
     */
    public static String printCtrlC() {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        DataFlavor flavor = DataFlavor.stringFlavor;
        if (clip.isDataFlavorAvailable(flavor)) {
            try {
                String s = (String) clip.getData(flavor);
                return s;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    /**
     * 函数的目的是确定鼠标的坐标,不然鼠标的坐标老对不上
     * 1、输入的值(鼠标想去的坐标)
     * 2、循环判断。当前的鼠标的坐标和想要去的坐标的区别,如果当前坐标和想去的绝对值小于等2,就退出去。
     *
     * @param x
     * @param y
     * @throws InterruptedException
     */
    private static void point(int x, int y, Robot robot) {
        robot.mouseMove(x, y);
        Point point = MouseInfo.getPointerInfo().getLocation();
        while (Math.abs(x - point.getX()) > 2 || Math.abs(y - point.getY()) > 2) {
            robot.mouseMove(x, y);
            point = MouseInfo.getPointerInfo().getLocation();
        }
    }

    private static void AutoIn(String Im, int x, int y, Robot robot) {
        point(x, y, robot);//使鼠标位置定位到对应位置上
        // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
        robot.mousePress(MouseEvent.BUTTON1_MASK);
        robot.mouseRelease(MouseEvent.BUTTON1_MASK);
        // 将我是xxx发送到剪切板
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable tText = new StringSelection(Im);
        clip.setContents(tText, null);
        // 以下两行按下了ctrl+v,完成粘贴功能
        robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_V);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        robot.delay(1000);
        clip.setContents( new StringSelection(""), null);
    }

    /**
     * 点击
     *
     * @param x
     * @param y
     * @param robot
     * @throws InterruptedException
     */
    private static void AutoClick(int x, int y, Robot robot) {
        robot.mouseMove(x, y);
        point(x, y, robot);
        robot.delay(500);
        // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
        robot.mousePress(MouseEvent.BUTTON1_MASK);
        robot.mouseRelease(MouseEvent.BUTTON1_MASK);
        robot.delay(500);
    }

    /**
     * 添加好友之我是xxx
     * 1、先把之前的删除了
     * 2、剪切板里获取自己输入的
     *
     * @return
     */
    private static void AutoIm(String Im, int x, int y, Robot robot) {
        point(x, y, robot);//使鼠标位置定位到对应位置上
        // 鼠标(按下/松开)事件,buttons可以填写 MouseEvent.BUTTON1_MASK 等
        robot.mousePress(MouseEvent.BUTTON1_MASK);
        robot.mouseRelease(MouseEvent.BUTTON1_MASK);
        //先清除自带的(最多20次)
        for (int i = 0; i < 20; i++) {
            robot.keyPress(KeyEvent.VK_BACK_SPACE);
            robot.delay(100);
            robot.keyRelease(KeyEvent.VK_BACK_SPACE);
        }
        // 将我是xxx发送到剪切板
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable tText = new StringSelection(Im);
        clip.setContents(tText, null);
        // 以下两行按下了ctrl+v,完成粘贴功能
        robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_V);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        robot.delay(1000);
        clip.setContents( new StringSelection(""), null);
    }

    //获取Robot
    private static Robot getRobot() {
        // 创建Robot对象
        Robot robot = null;
        try {
            robot = new Robot();
        } catch (AWTException e) {
            e.printStackTrace();
        }
        return robot;
    }
}

1.2 好友信息 FriendBaseDto:

package org.example.wechat.dto;

import lombok.Data;

/**
 * @Description TODO
 * @Date 2023/2/3 14:40
 * @Author lgx
 * @Version 1.0
 */
@Data
public class FriendBaseDto {
    // 要搜索的好友号
    private String  friendSearch;
    // 改好友号是否搜到了好友
    private boolean  hasFriend = false;
    // 好友的昵称信息
    private String  nickName;
    // 好友的地区信息
    private String  area;
    // 申请添加好友是申请信息
    private String  applyMsg;
    // 好友的备注
    private String  friendRemark;
    // 好友的标签
    private String  friendLabel;
    // 搜索添加过程产生的异常信息
    private String  errorMsg;
}

1.3 好友原始文件枚举:FriendSourceTypeEnum

package org.example.wechat.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Description TODO
 * @Date 2023/2/3 15:41
 * @Author lgx
 * @Version 1.0
 */
@Getter
@AllArgsConstructor
public enum  FriendSourceTypeEnum {
    从txt导入 (1, "从txt导入"),
    从docx导入 (2, "从docx导入"),
    从excel导入 (3, "从excel导入"),
    自定义 (4, "自定义"),;



    private Integer type;
    private String typeName;


}

1.4 结果:

搜索的文件信息示例:

好友列表页面java java实现添加好友_java


搜索的结果信息示例:

好友列表页面java java实现添加好友_微信_02

2 微信元素位置的获取:

使用 ctrl+alt+a 使用微信的截图工具,:

好友列表页面java java实现添加好友_开发语言_03

3 总结:
3.1 本位使用robot 通过在微信界面对应位置点击和输入信息完成微信好友的模拟添加,当微信界面移动时,需要重新获取到每个元素具体的位置;
3.2 微信批量添加好友会有限制,当短时间内在微信中大量添加微信好友,会被微信提示 搜索过于频繁,稍后重试;