Compare commits
10 Commits
185b2abae4
...
61fc828d6b
| Author | SHA1 | Date | |
|---|---|---|---|
| 61fc828d6b | |||
| 45aa6c4ff4 | |||
| c15bc9fb4a | |||
| 16d1de5f52 | |||
| a27afe47e9 | |||
| f40669b63f | |||
| 2e9c1d3425 | |||
| 2040e711cd | |||
| b7878de586 | |||
| 15ff6a17c6 |
174
README.md
Normal file
174
README.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# 邮件批量发送脚本
|
||||||
|
|
||||||
|
- [参数配置](#参数配置)
|
||||||
|
- [安装流程](#安装流程)
|
||||||
|
- [使用说明](#使用说明)
|
||||||
|
- [可选功能](#可选功能)
|
||||||
|
|
||||||
|
## 参数配置
|
||||||
|
|
||||||
|
配置批处理文件 (.bat) 时可以选择传入的参数。
|
||||||
|
|
||||||
|
留空使用默认设定值。
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: main.py [-h] [--column-address COLUMN_ADDRESS] [--column-name COLUMN_NAME] [--column-code COLUMN_CODE] [--column-pays COLUMN_PAYS]
|
||||||
|
[--column-sent COLUMN_SENT] [-a ADDRESS] [-p PASSWORD] [-t TIMEOUT] [-i INTERVAL] [-r ATTEMPTS]
|
||||||
|
[url]
|
||||||
|
|
||||||
|
Bulk Email Sending(邮件批量发送脚本)
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
url 邮箱网页端链接
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help 打印帮助信息并退出
|
||||||
|
--column-address COLUMN_ADDRESS 字段“邮箱”列名称
|
||||||
|
--column-name COLUMN_NAME 字段“主要联系人”列名称
|
||||||
|
--column-code COLUMN_CODE 字段“客户编号”列名称
|
||||||
|
--column-pays COLUMN_PAYS 字段“国家地区”列名称
|
||||||
|
--column-sent COLUMN_SENT 字段“已发送”列名称
|
||||||
|
-a, --address ADDRESS 邮箱地址
|
||||||
|
-p, --password PASSWORD 邮箱密码
|
||||||
|
-t, --timeout TIMEOUT 超时时长(秒);默认值:60
|
||||||
|
-i, --interval INTERVAL 间隔时长(秒);默认值:10
|
||||||
|
-r, --attempts ATTEMPTS 允许尝试次数;默认值:3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装流程
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 在执行初始化脚本之前请确保 Python 已经正确安装并添加到 PATH 系统变量中。\
|
||||||
|
> (在安装向导中勾选 `Add Python to PATH` 选项)
|
||||||
|
|
||||||
|
### 系统要求
|
||||||
|
|
||||||
|
- Windows 10+
|
||||||
|
- Python 3.10+
|
||||||
|
- Git for windows
|
||||||
|
- Windows Terminal(可选)
|
||||||
|
|
||||||
|
### 安装步骤
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> 在已经安装了 Windows Terminal 的 Windows 11 环境下,右键桌面(或文件资源管理器窗口),在弹出的菜单中可以找到 `在终端中打开` 的选项,点击可以打开终端。
|
||||||
|
|
||||||
|
1. 打开终端,克隆仓库到本地:
|
||||||
|
|
||||||
|
```console
|
||||||
|
git clone https://git.autistic.men/Ultimatron-France/bulk-email-sending-script.git 邮件批量发送
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 进入 `邮件批量发送` 文件夹下的 `profiles` 文件夹,双击运行 `setup-virtualenv` 批处理文件进行环境初始化。
|
||||||
|
|
||||||
|
3. 等待初始化完成,终端显示 `Press any key to continue...` 时关闭终端。此时可以删除 `setup-virtualenv` 文件(可选)。
|
||||||
|
|
||||||
|
4. 编辑 `profiles` 文件夹下的 `profile-example` 批处理文件,分别将 `--address` 和 `--password` 参数对应的值更改为实际使用的邮箱账号密码,保存并退出文本编辑器。这个文件将作为脚本的**启动配置文件**。
|
||||||
|
|
||||||
|
5. (可选)将 `profile-example` 重命名为实际使用的邮箱账号名称,方便记忆。
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 由于脚本在初始化阶段需要连接到 Google 服务,在运行前请确保 VPN 已打开。
|
||||||
|
|
||||||
|
### 基本操作步骤
|
||||||
|
|
||||||
|
1. 准备需要发送的**邮件**和**客户列表**。客户列表必须包含 `邮箱` 字段(表头名称可自定义),并且以 XLSX 格式保存。
|
||||||
|
|
||||||
|
2. 双击运行 `profiles` 文件夹下的[启动配置文件](#操作步骤),等待脚本初始化(初次运行会自动下载必要文件,需要一段时间)。
|
||||||
|
|
||||||
|
3. 脚本初始化完成,此时会打开带有两个标签页(第一个是配置页面,第二个是 IONOS 邮箱页面)的新浏览器实例,自动化程序尝试登录到 IONOS 邮件系统。
|
||||||
|
|
||||||
|
4. 登录完成,程序自动跳转到配置页面。在配置页面上单击 `Open` 按钮,在弹出的对话框中选择需要发送的客户列表,单击 `打开` 按钮。
|
||||||
|
|
||||||
|
5. 客户列表读取完成,程序自动跳转到 IONOS 邮箱页面。单击**草稿箱**按钮,在左侧列表中勾选需要发送的邮件(不支持多选)。
|
||||||
|
|
||||||
|
6. 打开配置页面,按需调整参数配置,单击 `Send` 按钮,随后立即打开 IONOS 邮箱页面,自动化程序开始运行。
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> 自动化程序运行时请避免最小化程序窗口、直接关闭窗口或在 IONOS 邮箱页面进行任何操作。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 无论是执行 `Send` 或是 `Resume` 操作,必须及时打开 IONOS 邮箱页面,否则自动化程序可能无法正常工作。
|
||||||
|
|
||||||
|
7. 自动化程序运行时,用户可以打开**配置页面**,单击页面上的操作按钮执行[相应操作](#用户界面说明)。
|
||||||
|
|
||||||
|
8. 自动化程序运行结束,程序自动跳转到配置页面,在页面上方有弹窗显示已发送数量、警告数和错误数。此时可关闭程序窗口,或进行下一轮发送操作。
|
||||||
|
|
||||||
|
### 用户界面说明
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>基本信息 (Basic Information)</summary>
|
||||||
|
|
||||||
|
| 显示名称 | 说明 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **File Name** | 文件名称 | |
|
||||||
|
| **From** | 发件人 | |
|
||||||
|
| **Progress** | 当前程序进度(已发送数量) | |
|
||||||
|
| **Uptime** | 程序运行时长 | |
|
||||||
|
| **Status** | 程序当前状态 | `Idle`, `Ready`, `Running`, `Standby` |
|
||||||
|
| **Subject** | 邮件主题 | |
|
||||||
|
| **Recipient** | 收件人 | |
|
||||||
|
| **Timezone** | 当前时区 | 由 `Locale` 参数控制 |
|
||||||
|
| **Remaining** | 待发送数量 | |
|
||||||
|
| **Open** | 打开文件 | 仅在 `Idle` 状态下可见 |
|
||||||
|
| **Send** | 开始发送 | 仅在 `Ready` 状态下可见 |
|
||||||
|
| **Pause** | 暂停 | 仅在 `Running` 状态下可见 |
|
||||||
|
| **Resume** | 恢复 | 仅在 `Standby` 状态下可见 |
|
||||||
|
| **Skip** | 跳过 | |
|
||||||
|
| **Cancel** | 取消 | |
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>基本参数 (Parameters)</summary>
|
||||||
|
|
||||||
|
| 显示名称 | 说明 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Timeout** | 超时时长 | 单位:秒 |
|
||||||
|
| **Interval** | 间隔时长 | 单位:秒 |
|
||||||
|
| **Max Occurrence** | 最大允许重复次数 | 由 `Avoid spamming` 选项控制 |
|
||||||
|
| **Attempts** | 操作最大尝试次数 | |
|
||||||
|
| **Locale** | 语言和时区 | 仅当 `Greet recipients` 选项启用时有效果 |
|
||||||
|
| **Greet recipients** | 使用问候语 | |
|
||||||
|
| **Enable task slicing** | 启用任务切片功能 | |
|
||||||
|
| **Avoid spamming** | 避免重复发送 | |
|
||||||
|
| **Save to file** | 保存发送记录 | 默认启用 |
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>列表字段配置 (Columns)</summary>
|
||||||
|
|
||||||
|
| 显示名称 | 说明 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Recipient Address** | 收件人邮箱地址 | 必填 |
|
||||||
|
| **Recipient Name** | 收件人名称 | |
|
||||||
|
| **Reference Code** | 客户编号 | |
|
||||||
|
| **Country** | 国家地区 | |
|
||||||
|
| **Remarks** | 发送标记 | |
|
||||||
|
| **Variables** | 自定义变量 | |
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>任务切片 (Slicing)</summary>
|
||||||
|
|
||||||
|
| 显示名称 | 说明 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Group By** | 分组依据 | |
|
||||||
|
| **Chunk Size** | 块大小 | 最小值:500 |
|
||||||
|
| **Offset** | 偏移量 | 最小值:0 |
|
||||||
|
| **Max** | 最大值 | |
|
||||||
|
| **Subcategory** | 子类目 | 由 `Group By` 选项控制 |
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 可选功能
|
||||||
|
|
||||||
|
### 使用问候语 (Greet Recipients)
|
||||||
|
|
||||||
|
### 任务切片 (Task Slicing)
|
||||||
|
|
||||||
|
### 避免重复发送 (Avoid Spamming)
|
||||||
|
|
||||||
|
### 保存发送记录 (Save to File)
|
||||||
|
|
||||||
|
### 列表字段调整
|
||||||
30
index.html
30
index.html
@@ -52,7 +52,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<button type="button" class="inline-flex pure-button pure-button-primary" id="send">
|
<button type="button" class="inline-flex pure-button pure-button-primary" id="send">
|
||||||
<span class="text">Open</span>
|
<span class="text">Open</span>
|
||||||
<span class="icon spin hidden">
|
<span class="icon spin" hidden>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-loader">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-loader">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M12 6l0 -3" />
|
<path d="M12 6l0 -3" />
|
||||||
@@ -121,13 +121,13 @@
|
|||||||
<legend>Columns</legend>
|
<legend>Columns</legend>
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="column_address">Receipient Address</label>
|
<label for="column_address">Recipient Address</label>
|
||||||
<select id="column_address" class="columns pure-u-23-24" required>
|
<select id="column_address" class="columns pure-u-23-24" required>
|
||||||
<option value=""> </option>
|
<option value=""> </option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<label for="column_name">Receipient Name</label>
|
<label for="column_name">Recipient Name</label>
|
||||||
<select id="column_name" class="columns pure-u-23-24">
|
<select id="column_name" class="columns pure-u-23-24">
|
||||||
<option value=""> </option>
|
<option value=""> </option>
|
||||||
</select>
|
</select>
|
||||||
@@ -150,6 +150,12 @@
|
|||||||
<option value=""> </option>
|
<option value=""> </option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
|
<label for="column_vars">Variables</label>
|
||||||
|
<select id="column_vars" class="columns pure-u-23-24">
|
||||||
|
<option value=""> </option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<br>
|
<br>
|
||||||
@@ -231,13 +237,13 @@ $$$('#send', 'click', async (e) => {
|
|||||||
case 'STANDBY':
|
case 'STANDBY':
|
||||||
Connection.send('RESUME');
|
Connection.send('RESUME');
|
||||||
$('#send > span.text').innerText = 'Pause';
|
$('#send > span.text').innerText = 'Pause';
|
||||||
$('#send > span.icon').classList.remove('hidden');
|
$('#send > span.icon').removeAttribute('hidden');
|
||||||
$('#send').disabled = true;
|
$('#send').disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'RUNNING':
|
case 'RUNNING':
|
||||||
Connection.send('ONHOLD');
|
Connection.send('ONHOLD');
|
||||||
$('#send > span.text').innerText = 'Resume';
|
$('#send > span.text').innerText = 'Resume';
|
||||||
$('#send > span.icon').classList.add('hidden');
|
$('#send > span.icon').setAttribute('hidden', '');
|
||||||
$('#send').disabled = true;
|
$('#send').disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'READY':
|
case 'READY':
|
||||||
@@ -268,7 +274,7 @@ $$$('#send', 'click', async (e) => {
|
|||||||
Timer.setTimestamp(true);
|
Timer.setTimestamp(true);
|
||||||
|
|
||||||
$('#send > span.text').innerText = 'Pause';
|
$('#send > span.text').innerText = 'Pause';
|
||||||
$('#send > span.icon').classList.remove('hidden');
|
$('#send > span.icon').removeAttribute('hidden');
|
||||||
$('#skip').disabled = false;
|
$('#skip').disabled = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -283,7 +289,7 @@ $$$('#send', 'click', async (e) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
$('#send > span.icon').classList.remove('hidden');
|
$('#send > span.icon').removeAttribute('hidden');
|
||||||
let [handle] = await showOpenFilePicker(PickerOptions);
|
let [handle] = await showOpenFilePicker(PickerOptions);
|
||||||
let file = await handle.getFile();
|
let file = await handle.getFile();
|
||||||
|
|
||||||
@@ -303,7 +309,7 @@ $$$('#send', 'click', async (e) => {
|
|||||||
$('#cancel').disabled = false;
|
$('#cancel').disabled = false;
|
||||||
$('#send').disabled = true;
|
$('#send').disabled = true;
|
||||||
} finally {
|
} finally {
|
||||||
$('#send > span.icon').classList.add('hidden');
|
$('#send > span.icon').setAttribute('hidden', '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -490,7 +496,7 @@ function main(url, parameters, locales) {
|
|||||||
case 'FAILED':
|
case 'FAILED':
|
||||||
Status = 'STANDBY';
|
Status = 'STANDBY';
|
||||||
$('#send > span.text').innerText = 'Resume';
|
$('#send > span.text').innerText = 'Resume';
|
||||||
$('#send > span.icon').classList.add('hidden');
|
$('#send > span.icon').setAttribute('hidden', '');
|
||||||
Timer.setTimedelta();
|
Timer.setTimedelta();
|
||||||
break;
|
break;
|
||||||
case 'FINISH':
|
case 'FINISH':
|
||||||
@@ -499,7 +505,7 @@ function main(url, parameters, locales) {
|
|||||||
case 'CANCEL':
|
case 'CANCEL':
|
||||||
Status = null;
|
Status = null;
|
||||||
$('#send > span.text').innerText = 'Open';
|
$('#send > span.text').innerText = 'Open';
|
||||||
$('#send > span.icon').classList.add('hidden');
|
$('#send > span.icon').setAttribute('hidden', '');
|
||||||
$('#cancel').disabled = true;
|
$('#cancel').disabled = true;
|
||||||
$('#skip').disabled = true;
|
$('#skip').disabled = true;
|
||||||
|
|
||||||
@@ -590,10 +596,6 @@ label {
|
|||||||
color: orangered;
|
color: orangered;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gaps {
|
.gaps {
|
||||||
row-gap: 0.4em;
|
row-gap: 0.4em;
|
||||||
}
|
}
|
||||||
|
|||||||
35
main.py
35
main.py
@@ -37,6 +37,7 @@ parser.add_argument('--column-name', type=str, default='主要联系人')
|
|||||||
parser.add_argument('--column-code', type=str, default='客户编号')
|
parser.add_argument('--column-code', type=str, default='客户编号')
|
||||||
parser.add_argument('--column-pays', type=str, default='国家地区')
|
parser.add_argument('--column-pays', type=str, default='国家地区')
|
||||||
parser.add_argument('--column-sent', type=str, default='已发送')
|
parser.add_argument('--column-sent', type=str, default='已发送')
|
||||||
|
parser.add_argument('--column-vars', type=str, default='变量值')
|
||||||
parser.add_argument('-a', '--address', type=str)
|
parser.add_argument('-a', '--address', type=str)
|
||||||
parser.add_argument('-p', '--password', type=str)
|
parser.add_argument('-p', '--password', type=str)
|
||||||
parser.add_argument('-t', '--timeout', type=int, default=60)
|
parser.add_argument('-t', '--timeout', type=int, default=60)
|
||||||
@@ -278,6 +279,7 @@ def main(driver: WebDriver):
|
|||||||
cc = parameters.get('column_code')
|
cc = parameters.get('column_code')
|
||||||
cs = parameters.get('column_sent')
|
cs = parameters.get('column_sent')
|
||||||
cp = parameters.get('column_pays')
|
cp = parameters.get('column_pays')
|
||||||
|
cv = parameters.get('column_vars')
|
||||||
|
|
||||||
if parameters.get('slice'):
|
if parameters.get('slice'):
|
||||||
if subcategory := parameters.get('subcategory'):
|
if subcategory := parameters.get('subcategory'):
|
||||||
@@ -298,11 +300,6 @@ def main(driver: WebDriver):
|
|||||||
if cs not in frame:
|
if cs not in frame:
|
||||||
frame[cs] = None
|
frame[cs] = None
|
||||||
|
|
||||||
seriesdict = frame.iloc[index].to_dict(orient='list')
|
|
||||||
recipients = seriesdict.get(ca, [None] * limit)
|
|
||||||
names = seriesdict.get(cn, [None] * limit)
|
|
||||||
codes = seriesdict.get(cc, [None] * limit)
|
|
||||||
|
|
||||||
rate = 60 / parameters.get('interval')
|
rate = 60 / parameters.get('interval')
|
||||||
length = limit - frame.loc[index, cs].count()
|
length = limit - frame.loc[index, cs].count()
|
||||||
|
|
||||||
@@ -340,12 +337,11 @@ def main(driver: WebDriver):
|
|||||||
current = cursor
|
current = cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
|
||||||
name = names[current]
|
|
||||||
code = codes[current]
|
|
||||||
axis = index[current]
|
axis = index[current]
|
||||||
|
name = dict(frame.iloc[axis]).get(cn)
|
||||||
|
code = dict(frame.iloc[axis]).get(cc)
|
||||||
|
recipient = str(frame.loc[axis, ca]).strip()
|
||||||
occurrence = pandas.Series.count(frame[frame[cc] == code][cs]) if code is not None else 0
|
occurrence = pandas.Series.count(frame[frame[cc] == code][cs]) if code is not None else 0
|
||||||
recipient = str(recipients[current]).strip()
|
|
||||||
|
|
||||||
if (remarks := frame.loc[axis, cs]) is not None and str(remarks).strip():
|
if (remarks := frame.loc[axis, cs]) is not None and str(remarks).strip():
|
||||||
tell(f'已跳过项目 {recipient}')
|
tell(f'已跳过项目 {recipient}')
|
||||||
@@ -430,6 +426,19 @@ def main(driver: WebDriver):
|
|||||||
click(wrapper)
|
click(wrapper)
|
||||||
to.send_keys(recipient + Keys.ENTER)
|
to.send_keys(recipient + Keys.ENTER)
|
||||||
|
|
||||||
|
# 引入变量值
|
||||||
|
if cv in frame and (variables := frame.loc[axis, cv]):
|
||||||
|
clean = False
|
||||||
|
target = locate("div.io-ox-mail-compose-window div[data-extension-id='subject'] input")
|
||||||
|
attribute = target.get_attribute('value')
|
||||||
|
|
||||||
|
for i, value in enumerate(str(variables).split(',')):
|
||||||
|
attribute = attribute.replace(f'$${i}', value.strip())
|
||||||
|
|
||||||
|
target.send_keys(Keys.BACKSPACE * len(attribute))
|
||||||
|
target.send_keys(attribute)
|
||||||
|
clean = target.get_attribute('value') == attribute
|
||||||
|
|
||||||
token = locate("div.io-ox-mail-compose-window .mail-input .tokenfield .token")
|
token = locate("div.io-ox-mail-compose-window .mail-input .tokenfield .token")
|
||||||
target = token.get_attribute('innerText').strip()
|
target = token.get_attribute('innerText').strip()
|
||||||
|
|
||||||
@@ -502,6 +511,7 @@ def main(driver: WebDriver):
|
|||||||
tell('程序中断', level=1)
|
tell('程序中断', level=1)
|
||||||
status = Status.TERMINATED
|
status = Status.TERMINATED
|
||||||
|
|
||||||
|
outbox.put(Command('setProgress', cursor, name, recipient, sent, warnings, errors))
|
||||||
progress = cursor / limit * 100
|
progress = cursor / limit * 100
|
||||||
tell('当前进度:%.2f %%' % progress)
|
tell('当前进度:%.2f %%' % progress)
|
||||||
tell(f'已发送 {sent} 封;发送失败 {errors} 封;跳过重复项 {warnings} 个')
|
tell(f'已发送 {sent} 封;发送失败 {errors} 封;跳过重复项 {warnings} 个')
|
||||||
@@ -541,7 +551,7 @@ async def handler(request: ws.WebSocketRequest):
|
|||||||
message = await connection.get_message()
|
message = await connection.get_message()
|
||||||
inbox.put(message)
|
inbox.put(message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tell('Receiver', e, level=0)
|
inbox.shutdown(immediate=True)
|
||||||
break
|
break
|
||||||
|
|
||||||
async def sender():
|
async def sender():
|
||||||
@@ -550,16 +560,13 @@ async def handler(request: ws.WebSocketRequest):
|
|||||||
message = await trio.to_thread.run_sync(outbox.get)
|
message = await trio.to_thread.run_sync(outbox.get)
|
||||||
await connection.send_message(str(message))
|
await connection.send_message(str(message))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tell('Sender', e, level=0)
|
outbox.shutdown(immediate=True)
|
||||||
break
|
break
|
||||||
|
|
||||||
async with trio.open_nursery() as nursery:
|
async with trio.open_nursery() as nursery:
|
||||||
nursery.start_soon(receiver)
|
nursery.start_soon(receiver)
|
||||||
nursery.start_soon(sender)
|
nursery.start_soon(sender)
|
||||||
|
|
||||||
outbox.shutdown(immediate=True)
|
|
||||||
inbox.shutdown(immediate=True)
|
|
||||||
|
|
||||||
async def backend(listen='127.0.0.1', port=0):
|
async def backend(listen='127.0.0.1', port=0):
|
||||||
global server
|
global server
|
||||||
listeners = await trio.open_tcp_listeners(port, host=listen)
|
listeners = await trio.open_tcp_listeners(port, host=listen)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
cd /D .\..\
|
cd /D .\..\
|
||||||
|
set SE_PROXY=http://127.0.0.1:10809
|
||||||
.\venv\Scripts\pythonw.exe .\main.py --address "user@example.com" --password "example" --interval 10
|
.\venv\Scripts\pythonw.exe .\main.py --address "user@example.com" --password "example" --interval 10
|
||||||
@pause
|
@pause
|
||||||
Reference in New Issue
Block a user