200行代碼,一行行教你自制微信機器人

昨天的一篇文章「自制微信機器人:羣發消息、自動接收好友」一經推出,收到了很多小夥伴的私聊,包括建議、諮詢、學習的。

今天干脆把這套代碼的思路講清楚了,也好之後和大家形成更有效深入的討論。

1.

當初決定自己寫這麼個機器人有幾個原因:

1) 用一個windows客戶端工具運營公衆號,真的很侷限。雖然工具的功能很強大,能自動添加好友,自動拉好友入羣,關鍵字回覆等等,但是有一個繞不開的點,它是一款客戶端工具,一款exe軟件。

2) 我是Mac,爲了用這個工具,就要開着虛擬機去操作。

3) 爲了能一直自動添加好友,邀請入羣,自動回覆等一系列操作,電腦就不能合上。

4) 在外面突然想到一個點,想操作羣發了,GG,無能爲力。

5) 其他……

2.

基於以上的原因,就想着自己來一套算了。畢竟可以定製化的話,之後想要什麼就很方便了,而且在服務器端掛個python服務要比開個windows 就爲了掛一個exe要很多。

那麼首先需要確定需求,wxRobot我是準備長期維護、迭代的,所以顯然不可能像網上那些個腳本一樣,一個文件打通關。

另外功能自定義,就需要有版本引入,先做什麼,痛點是什麼都需要明確。我說下自己的選擇:

1) 痛點是不能自主化的管理公衆號、微信號。

2) 最急迫解決的是之前exe工具用到的功能,畢竟這也是我用這個工具的原因。那麼有哪些功能呢?

  1. 羣發消息
  2. 自動添加好友
  3. 邀請好友入羣
  4. 關鍵字回覆

3.

既然是個項目,那麼該有的組件一個不能少,看一下我的目錄結構,這也是我自己總結出的common structure,大家可以參考一下,如果有好的建議歡迎大佬不吝留言。

app:項目業務模塊。如果有多個模塊就添加子目錄,例如:一個網站下的博客模塊、投票模塊等。

core:核心組件。例如:數據庫組件、類-文件組件等。

doc:文檔。存放所有的文檔,一般我會有固定的幾個:CHANGELOG.md、BUGLIST.md、TODOLIST.md。

etc:配置文件。可以細分基本配置、業務配置等。

static:靜態文件。

test:單元測試。

tmp:不需要進入版本控制的東西。

utility:輔助組件。和core相輔。

4.

我把業務分爲兩塊,filehelper算一個,好友相關的算一個。

好友相關的好理解,諸如添加好友、自動回覆、邀請入羣等。filehelper是什麼呢?說白了,我們除了簡單的自動回覆、添加外,一定還希望做的更多吧?比如交互式指令。那這個filehelper就承擔了指令收發的角色。

所有的業務模塊都基於一個BaseHandle,這樣底層的一些單元我就可以統一管控了:

class BaseHandle:
    def __init__(self):
        '''
        self._meta = {
            'obj':{ # 消息發送對象
                'ul': [], # unlimit group
                'l': [], # limit group
                'r': [] # restrict
            },
            'reply':{
                'text': '',
                'article': '',
            }
        }
        '''
        self._usage = ''
        self._meta = {}
        self.current_cmd = None

    @property
    def usage(self):
        return self._usage

    @property
    def meta(self):
        return self._meta

再來看看FileHandle這個類,這也是當前版本最豐富的模塊。這裏面有兩端邏輯:1.自動更新羣組信息。 2.註冊羣發相關命令。

自動更新羣組信息的目的是因爲itchat模塊會將所有聯繫人以及羣組信息存儲在本地的一個pkl文件中(pickle縮寫?),如果想提升羣發消息前獲取羣組列表的速度,那麼就應該把數據放在內存裏(反正也沒多少數據),以下我把主要邏輯都羅列出來了,具體的代碼太長了,暫時就不放出來了:

class FileHelper(BaseHandle):
    _usage = '''
    '''

    def __init__(self):
        super().__init__()
        self._meta = {
            ...
        }
        self._th_update = threading.Thread(target=self._update_meta, args=(), daemon=True)
        self.auto_update_groups()

    def auto_update_groups(self):
        # 自動更新羣組
        self._th_update.start()

    def _update_meta(self):
        '''
        初始化限時推送的羣組
        '''

        def _filter_restrict_groups(group):
            # 篩選出不能羣發的羣組

        def _filter_limit_groups(group):
            # 篩選出有時間限制的羣組

        def _filter_unlimit_group(groups, limit_groups):
            # 篩選出不受限制的羣組

        while True:
            time.sleep(30)
            # 更新羣組信息

註冊羣發相關命令的思路就是做一個命令註冊器,因爲羣發消息、文章、圖片等行爲類似,針對不同的用戶羣組發送不同的消息體。

所以我就把註冊器的成員分成了:類型(文字、圖片),對象(時間限制羣組、無限制羣組),行爲(羣發、單發)。

被裝飾器註冊的函數就成爲了某個具有單獨意義的指令了。

class FileHelper(BaseHandle):
    ...

    def update_cmd(self, cmd):
        # 更新命令,用於動態註冊函數

    def _register_mass(func):
        @functools.wraps(func)
        def decorator(self, msg, *args, **kwargs):
            _action, _reply, _obj = func.__name__.split('_')
            if self._meta['action'][_action]:
                _to_user = self._meta['obj'][_obj]
                for _group in _to_user:
                    instance.send_msg(msg, _group['UserName'])
                    time.sleep(random.randrange(0, 20))
                self._meta['action'][_action] = False
                self._current_cmd = None
                instance.send_msg('羣發消息發送完畢', self._meta['extra']['UserName'])

        return decorator

    @_register_mass
    def mass_text_ul(self, msg=None):
        pass

    @_register_mass
    def mass_text_l(self, msg):
        pass

    @_register_mass
    def mass_text_test(self, msg):
        pass

    @_register_mass
    def mass_article_ul(self, msg):
        pass

    @_register_mass
    def mass_article_l(self, msg):
        pass

對比着效果圖來看看:

5.

接下來就是添加好友部分了,目前只支持自動接受好友,根據打招呼自動設置備註,關鍵字回覆。

class Friend(BaseHandle):
    _usage = '''
    '''

    def __init__(self):
        super().__init__()
        self._meta = {
            ...
        }

    def is_biz(self, msg):
        # 判斷是不是商務合作

看下效果圖:

6.

講完核心代碼後,再來講下中間經歷的幾個看不到的版本吧。

最一開始就是實現功能咯,沒想很多,但是發現代碼重複太多了,邏輯都差不多,一堆代碼太醜了。優化後的代碼就是第一版中的羣發註冊器函數。

接着原本的BaseHandle基類太重了,想的很好,把itchat方法都重寫在基類裏,這樣就不用在其他地方調用itchat實例了,但是結果就是所有的子類都可以做同樣的動作,就變成了filehelper.send_msg(), friend.send_image()了,這樣對於同一個方法就會產生歧義了。因此就把基類裏所有重寫itchat方法的函數都去了,就保留了業務代碼,並分別移到對應的類裏去,而原本itchat的方法還是用itchat實例去操作。

接着關於itchat實例、FileHelper實例、Friend實例等的共享問題,容易造成重疊,重複使用、互相引用問題。解決辦法目前就是把itchat實例單獨在配置文件裏初始化了,這也同時解決了上一個問題,其他業務類的實例採用單例模式,在類外面暴露一個統一的實例。

7.

好了,這回是真花了功夫把這套代碼講完了,雖然還是相對簡陋了,但迫於時間關係,先發出來了。之後會繼續優化、健碩它。

今天也和一位大佬討論了下這個項目,有很多值得思考的地方。

如果你對這份代碼也感興趣的話,歡迎底部留言,或者給我提PR,或者私聊我~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章