Skip to content

Commit 38136ac

Browse files
committed
Docs update
1 parent 9a8b42c commit 38136ac

File tree

8 files changed

+197
-27
lines changed

8 files changed

+197
-27
lines changed

docs/EWS.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# EFB WeChat Slave Channel
2+
3+
EFB WeChat Slave Channel is a slave channel for EFB based on [ItChat](https://github.com/littlecodersh/ItChat) and WeChat Web <span style="font-size: 0.5em;">(rev.eng.)</span> API.
4+
5+
## Specs
6+
* Unique name: `eh_wechat_slave`
7+
* Import path: `plugins.eh_wechat_slave`
8+
* Class name: `WeChatChannel`
9+
10+
## Installation
11+
### Dependencies
12+
#### Python dependencies
13+
```
14+
itchat
15+
pymagic
16+
pillow
17+
xmltodict
18+
```
19+
> **Note**
20+
> Please refer to [Pillow documentation](https://pillow.readthedocs.io/en/3.0.x/installation.html) for details on installing Pillow.
21+
22+
#### Non-python dependencies
23+
```
24+
libmagic
25+
```
26+
and all other required by Pillow.
27+
28+
### Configuration
29+
* Copy `eh_wechat_slave.py` to "plugins" directory,
30+
* Append `("plugins.we_wechat_slave", "WeChatChannel")` to `slaves` dict in `config.py`
31+
* No other configuration is required
32+
33+
### Start up
34+
* Scan QR code with your *mobile WeChat client*, then tap "Accept", if required.
35+
36+
## Feature availability
37+
### Supported message types
38+
* TO WeChat
39+
* Text
40+
* Image (Sticker sent as Image)
41+
* File
42+
* FROM WeChat
43+
* Text
44+
* Image
45+
* Sticker
46+
* Video
47+
* Location
48+
* Voice
49+
* Link
50+
* Card
51+
* File
52+
* System notices
53+
54+
### Command messages, Extra Functions
55+
* Add "Card" as friend or accept friend request
56+
* Change "alias" of friends
57+
58+
## FAQ
59+
* **How do I log in to another WeChat Account?**
60+
Please remove the `itchat.pkl` file in the EFB root directory, and restart EFB for QR code scanning.
61+
* **Can I log in 2 WeChat accounts concurrently?**
62+
No. The feature is not yet available to EWS.
63+
* **I want to report a bug.**
64+
**I have some suggestions.**
65+
**Can I submit a pull request?**
66+
All bug reports, suggestions and pull requests are welcomed. Please read through and understand the [Contribution guideline](CONTRIBUTION.md) before submitting.

docs/home.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
![EFB](https://images.1a23.com/upload/images/SPET.png)
66

7-
_Codename_ **EH Forwarder Bot** (EFB) is a extensible chat tunnel framework which allows users to contact people from other chat platforms, and ultimately remotely control their accounts in other platforms.
7+
_Codename_ **EH Forwarder Bot** (EFB) is an extensible chat tunnel framework which allows users to contact people from other chat platforms, and ultimately remotely control their accounts in other platforms.
88

99
## Navigation
1010
* [Installation](installation.md)

docs/installation.md

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ EFB framework itself does not require any external modules for it to work. Howev
33

44
You may follow the following instructions to install dependencies for **all** channels maintained by me, or you may refer to the documentations of each respective channel for their installation instructions.
55

6+
## Storage directory
7+
8+
In order to process files and media (pictures, voices, videos, etc.), a storage folder is used to temporarily save and process them. Please create a `storage` folder, if not existing, and give write and read permission to it.
9+
10+
Script for \*nix users:
11+
```bash
12+
mkdir storage
13+
chmod +rw ./storage
14+
```
15+
616
## Non-python dependencies
717

818
* __gcc__

docs/slave-channel.md

+36
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,39 @@ Slave channels can send messages that offer the user options to take action. Thi
9090
Aside from sending a message with "Command" type (refer to the specification in "Message" documentation), the slave channel have to also provide a method for the command to be issued.
9191

9292
The method can take any argument of Python's standard data structure, and returns a string which is shown to the user as the result of the action.
93+
94+
## Extra functions
95+
96+
In some cases, your slave channel may allow the user to achieve extra functionality, such as creating groups, managing group members, etc.
97+
98+
You can receive such commands issued in a CLI-like style. An "extra function" method should only take one string parameter aside from `self`, and wrap it with `@extra` decorator from `utils` module. The extra decorator takes 2 arguments: `name`, a short name of the function, and `desc` a description of the function and its usage.
99+
100+
> **More on `desc`**
101+
`desc` should describe what the function does and how to use it. It's more like the help text for an CLI program. Since method of calling an extra function depends on the implementation of the master channel, you should use `{function_name}` as the function name in `desc`, and master channel will replace it with respective name.
102+
103+
The method should in the end return a string, which will be shown to the user as the result. Depends on the functionality of the function, it may be just a simple success message, or a long chunk of results.
104+
105+
Example:
106+
```python
107+
@extra(name="New group",
108+
desc="Create a new group.\n" +
109+
"Usage:\n" +
110+
" {function_name} group_name|member_id_1|member_id_2|...\n" +
111+
" e.g.: {function_name} My Group|123456|654321|393901"
112+
" group_name: Name of new group, \"|\" is not allowed.\n" +
113+
" member_id_n: ID of group members, you should fill at least one.")
114+
def new_group(self, param):
115+
param = param.split('|')
116+
if len(param) < 2:
117+
return "Group name and at least one member ID should be provided."
118+
group_name = param[0]
119+
members = param[1:]
120+
try:
121+
self.myChat.create_group(group_name, members)
122+
except Exception as e:
123+
return repr(e)
124+
return "Group %s created with members %s." % (group_name, members).
125+
```
126+
127+
## Media processing
128+
Please refer to the _Media Processing_ section in the [Workflow](workflow.md) page.

docs/workflow.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ To deliver user's message to a slave channel, you should first gather all the in
4141
## Slave Channel
4242
Slave channel has rather less things to do, get and enqueue incoming message, and send message to the platform. Also you may need to generate a list of possible recipients for the Master channel. That should be most of it.
4343

44-
# Commands
44+
## Commands
4545
Once there's any message from a slave channel that allows the user to take action, the slave channel will enclose detail of actions (namely method names and arguments) into an `EFBMsg` object and send it to the master channel.
4646

4747
Master channel will use it's own method to ask the user to make a decision, and with the decision, the master channel will call the respective method of the slave channel with the argument given.
4848

4949
The method of slave channel returns a string as the result which is then reflected back to the user.
50+
51+
## Media processing

plugins/eh_telegram_master/__init__.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ def _utf8_byte_truncate(self, text, max_bytes):
112112
return utf8[:i].decode()
113113

114114
def callback_query_dispatcher(self, bot, update):
115+
"""
116+
Dispatch a callback query based on the message session status.
117+
118+
Args:
119+
bot (telegram.bot): bot
120+
update (telegram.Update): update
121+
"""
115122
# Get essential information about the query
116123
query = update.callback_query
117124
chat_id = query.message.chat.id
@@ -137,6 +144,12 @@ def _reply_error(bot, update, errmsg):
137144
return bot.sendMessage(update.message.chat.id, errmsg, reply_to_message_id=update.message.message_id)
138145

139146
def process_msg(self, msg):
147+
"""
148+
Process a message from slave channel and deliver it to the user.
149+
150+
Args:
151+
msg (EFBMsg): The message.
152+
"""
140153
chat_uid = "%s.%s" % (msg.channel_id, msg.origin['uid'])
141154
tg_chat = db.get_chat_assoc(slave_uid=chat_uid) or False
142155
msg_prefix = ""
@@ -263,7 +276,21 @@ def process_msg(self, msg):
263276
db.add_msg_log(**msg_log)
264277

265278
def slave_chats_pagination(self, message_id, offset=0):
266-
279+
"""
280+
Generate a list of (list of) `InlineKeyboardButton`s of chats in slave channels,
281+
based on the status of message located by `message_id` and the paging from
282+
`offset` value.
283+
284+
Args:
285+
message_id (int): Message ID for generating the buttons list.
286+
offset (int): Offset for pagination
287+
288+
Returns:
289+
tuple (str, list of list of InlineKeyboardButton):
290+
A tuple: legend, chat_btn_list
291+
`legend` is the legend of all Emoji headings in the entire list.
292+
`chat_btn_list` is a list which can be fit into `telegram.InlineKeyboardMarkup`.
293+
"""
267294
legend = [
268295
"%s: Linked" % utils.Emojis.LINK_EMOJI,
269296
"%s: User" % utils.Emojis.USER_EMOJI,

plugins/eh_wechat_slave.py

+51-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import itchat
2-
import requests
32
import re
43
import xmltodict
54
import logging
@@ -82,14 +81,14 @@ def get_uid(self, UserName=None, NickName=None):
8281
NickName (str): Display Name (`NickName`) of the chat.
8382
8483
Returns:
85-
int|str|bool: Unique ID of the chat. `False` if not found.
84+
str|bool: Unique ID of the chat. `False` if not found.
8685
"""
8786
if not (UserName or NickName):
8887
self.logger.error('No name provided.')
8988
return False
9089
r = self.search_user(UserName=UserName, name=NickName)
9190
if r:
92-
return r[0]['AttrStatus'] or r[0]['Uin'] or crc32(r[0]['NickName'].encode("utf-8"))
91+
return str(r[0]['AttrStatus'] or r[0]['Uin'] or crc32(r[0]['NickName'].encode("utf-8")))
9392
else:
9493
return False
9594

@@ -98,7 +97,7 @@ def get_UserName(self, uid, refresh=False):
9897
Get WeChat `UserName` of a chat by UID.
9998
10099
Args:
101-
uid (str|int): UID of the chat.
100+
uid (str): UID of the chat.
102101
refresh (bool): Refresh the chat list from WeChat, `False` by default.
103102
104103
Returns:
@@ -110,39 +109,69 @@ def get_UserName(self, uid, refresh=False):
110109
return False
111110

112111
def search_user(self, UserName=None, uid=None, wid=None, name=None, ActualUserName=None, refresh=False):
112+
"""
113+
Search for a WeChat "User" (a user, a group/chat room or an MPS account,
114+
by `UserName`, unique ID, WeChat ID, name/alias, and/or group member `UserName`.
115+
116+
At least one of `UserName`, `uid`, `wid`, or `name` should be provided.
117+
118+
When matching for a group, all of `UserName`, `uid`, `wid`, and `name` will be used
119+
to match for group and member.
120+
121+
Args:
122+
UserName (str): UserName of a "User"
123+
uid (str): Unique ID generated by the channel
124+
wid (str): WeChat ID.
125+
name (str): Name or Alias
126+
ActualUserName (str): UserName of a group member, used only when a group is matched.
127+
refresh (bool): Refresh the user list, False by default.
128+
129+
Returns:
130+
list of dict: A list of matching users in ItChat user dict format.
131+
"""
113132
result = []
133+
UserName = None if UserName is None else str(UserName)
134+
uid = None if uid is None else str(uid)
135+
wid = None if wid is None else str(wid)
136+
name = None if name is None else str(name)
137+
ActualUserName = None if ActualUserName is None else str(ActualUserName)
138+
139+
if all(i is None for i in [UserName, uid, wid, name]):
140+
raise ValueError("At least one of [UserName, uid, wid, name] should be given.")
141+
114142
for i in itchat.get_friends(refresh) + itchat.get_mps(refresh):
115-
if str(i['UserName']) == str(UserName) or \
116-
str(i['AttrStatus']) == str(uid) or \
117-
str(i['Alias']) == str(wid) or \
118-
str(i['NickName']) == str(name) or \
119-
str(i['DisplayName']) == str(name) or \
120-
str(crc32(i['NickName'].encode("utf-8"))) == str(uid):
143+
if str(i['UserName']) == UserName or \
144+
str(i['AttrStatus']) == uid or \
145+
str(i['Alias']) == wid or \
146+
str(i['NickName']) == name or \
147+
str(i['DisplayName']) == name or \
148+
str(crc32(i['NickName'].encode("utf-8"))) == uid:
121149
result.append(i.copy())
122150
for i in itchat.get_chatrooms(refresh):
123151
if not i['MemberList']:
124152
i = itchat.update_chatroom(i['UserName'])
125-
if str(i['UserName']) == str(UserName) or \
126-
str(i['Uin']) == str(uid) or \
127-
str(i['Alias']) == str(wid) or \
128-
str(i['NickName']) == str(name) or \
129-
str(i['DisplayName']) == str(name) or \
130-
str(crc32(i['NickName'].encode("utf-8"))) == str(uid):
153+
if str(i['UserName']) == UserName or \
154+
str(i['Uin']) == uid or \
155+
str(i['Alias']) == wid or \
156+
str(i['NickName']) == name or \
157+
str(i['DisplayName']) == name or \
158+
str(crc32(i['NickName'].encode("utf-8"))) == uid:
131159
result.append(i.copy())
132160
result[-1]['MemberList'] = []
133161
if ActualUserName:
134162
for j in itchat.search_chatrooms(userName=i['UserName'])['MemberList']:
135-
if str(j['UserName']) == str(ActualUserName) or \
136-
str(j['AttrStatus']) == str(uid) or \
137-
str(j['NickName']) == str(name) or \
138-
str(j['DisplayName']) == str(name):
163+
if str(j['UserName']) == ActualUserName or \
164+
str(j['AttrStatus']) == uid or \
165+
str(j['NickName']) == name or \
166+
str(j['DisplayName']) == name:
139167
result[-1]['MemberList'].append(j)
140168
if not result and not refresh:
141169
return self.search_user(UserName, uid, wid, name, ActualUserName, refresh=True)
142170
return result
143171

144172
def poll(self):
145173
self.usersdata = itchat.get_friends(True) + itchat.get_chatrooms()
174+
146175
@itchat.msg_register(['Text'], isFriendChat=True, isMpChat=True)
147176
def wcText(msg):
148177
self.textMsg(msg)
@@ -471,8 +500,7 @@ def send_message(self, msg):
471500
# Extra functions
472501

473502
@extra(name="Show chat list",
474-
desc="Get a list of chat from Wechat.\nUsage:\n {function_name} [-r]\n -r: Force refresh",
475-
emoji="📃")
503+
desc="Get a list of chat from Wechat.\nUsage:\n {function_name} [-r]\n -r: Force refresh")
476504
def get_chat_list(self, param=""):
477505
refresh = False
478506
if param:
@@ -505,8 +533,7 @@ def get_chat_list(self, param=""):
505533
@extra(name="Set alias",
506534
desc="Set alias for a contact in WeChat. You may not set alias to a group or a MPS contact.\n" + \
507535
"Usage:\n {function_name} [-r] id [alias]\n id: Chad ID (You may obtain it from \"Show chat list\" function.\n" + \
508-
" alias: Alias to be set. Omit to remove.\n -r: Force refresh",
509-
emoji="📃")
536+
" alias: Alias to be set. Omit to remove.\n -r: Force refresh")
510537
def get_chat_list(self, param=""):
511538
refresh = False
512539
if param:

utils.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ def get_source_emoji(t):
1919

2020
def extra(**kw):
2121
def attr_dec(f):
22+
if not "name" in kw or not "desc" in kw:
23+
raise ValueError("Key `name` and `desc` is required for extra functions.")
2224
f.__setattr__("extra_fn", True)
2325
for i in kw:
2426
f.__setattr__(i, kw[i])

0 commit comments

Comments
 (0)