myl 0.8.6__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {myl-0.8.6 → myl-0.9.2}/.github/workflows/lint.yaml +2 -2
- {myl-0.8.6 → myl-0.9.2}/.github/workflows/pypi.yaml +2 -2
- {myl-0.8.6 → myl-0.9.2}/.github/workflows/release.yaml +2 -2
- {myl-0.8.6 → myl-0.9.2}/.gitignore +2 -0
- {myl-0.8.6 → myl-0.9.2}/PKG-INFO +20 -4
- {myl-0.8.6 → myl-0.9.2}/README.md +11 -3
- myl-0.9.2/flake.lock +116 -0
- myl-0.9.2/flake.nix +105 -0
- {myl-0.8.6 → myl-0.9.2}/myl.egg-info/PKG-INFO +20 -4
- {myl-0.8.6 → myl-0.9.2}/myl.egg-info/SOURCES.txt +3 -0
- myl-0.9.2/myl.egg-info/requires.txt +4 -0
- myl-0.9.2/myl.py +508 -0
- {myl-0.8.6 → myl-0.9.2}/pyproject.toml +16 -4
- myl-0.9.2/version.txt +1 -0
- myl-0.8.6/myl.egg-info/requires.txt +0 -3
- myl-0.8.6/myl.py +0 -213
- {myl-0.8.6 → myl-0.9.2}/.github/dependabot.yml +0 -0
- {myl-0.8.6 → myl-0.9.2}/LICENSE +0 -0
- {myl-0.8.6 → myl-0.9.2}/myl.egg-info/dependency_links.txt +0 -0
- {myl-0.8.6 → myl-0.9.2}/myl.egg-info/entry_points.txt +0 -0
- {myl-0.8.6 → myl-0.9.2}/myl.egg-info/top_level.txt +0 -0
- {myl-0.8.6 → myl-0.9.2}/setup.cfg +0 -0
@@ -17,10 +17,10 @@ jobs:
|
|
17
17
|
permissions:
|
18
18
|
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
19
19
|
steps:
|
20
|
-
- uses: actions/checkout@
|
20
|
+
- uses: actions/checkout@v4
|
21
21
|
|
22
22
|
- name: Set up Python
|
23
|
-
uses: actions/setup-python@
|
23
|
+
uses: actions/setup-python@v5
|
24
24
|
with:
|
25
25
|
python-version: '3.x'
|
26
26
|
|
@@ -13,7 +13,7 @@ jobs:
|
|
13
13
|
contents: write
|
14
14
|
steps:
|
15
15
|
- name: Checkout code
|
16
|
-
uses: actions/checkout@
|
16
|
+
uses: actions/checkout@v4
|
17
17
|
with:
|
18
18
|
fetch-depth: 0
|
19
19
|
|
@@ -40,6 +40,6 @@ jobs:
|
|
40
40
|
echo "$EOF" >> $GITHUB_OUTPUT
|
41
41
|
|
42
42
|
- name: Create Release
|
43
|
-
uses: softprops/action-gh-release@
|
43
|
+
uses: softprops/action-gh-release@v2
|
44
44
|
with:
|
45
45
|
body: ${{ steps.changelog.outputs.changelog }}
|
{myl-0.8.6 → myl-0.9.2}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: myl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.2
|
4
4
|
Summary: Dead simple IMAP CLI client
|
5
5
|
Author-email: Philipp Schmitt <philipp@schmitt.co>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -678,11 +678,19 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
678
678
|
Public License instead of this License. But first, please read
|
679
679
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
680
680
|
|
681
|
+
Project-URL: homepage, https://github.com/pschmitt/myl
|
682
|
+
Project-URL: documentation, https://github.com/pschmitt/myl/blob/head/readme.md
|
683
|
+
Project-URL: repository, https://github.com/pschmitt/myl
|
684
|
+
Project-URL: issues, https://github.com/pschmitt/myl/issues
|
681
685
|
Keywords: imap,email
|
682
686
|
Classifier: Programming Language :: Python :: 3
|
683
687
|
Requires-Python: >=3.8
|
684
688
|
Description-Content-Type: text/markdown
|
685
689
|
License-File: LICENSE
|
690
|
+
Requires-Dist: imap-tools<2.0.0,>=1.5.0
|
691
|
+
Requires-Dist: myl-discovery>=0.6.1.dev0
|
692
|
+
Requires-Dist: rich<14.0.0,>=13.0.0
|
693
|
+
Requires-Dist: html2text>=2024.2.26
|
686
694
|
|
687
695
|
# 📧 myl
|
688
696
|
|
@@ -713,15 +721,23 @@ straightforward way to interact with IMAP servers.
|
|
713
721
|
|
714
722
|
To install myl, follow these steps:
|
715
723
|
|
716
|
-
```
|
724
|
+
```shell
|
717
725
|
pipx install myl
|
726
|
+
# or:
|
727
|
+
pip install --user myl
|
728
|
+
```
|
729
|
+
|
730
|
+
on nix you can do this:
|
731
|
+
|
732
|
+
```shell
|
733
|
+
nix run github:pschmitt/myl -- --help
|
718
734
|
```
|
719
735
|
|
720
736
|
## 🛠️ Usage
|
721
737
|
|
722
738
|
Here's how you can use myl:
|
723
739
|
|
724
|
-
```
|
740
|
+
```shell
|
725
741
|
myl --help
|
726
742
|
```
|
727
743
|
|
@@ -729,7 +745,7 @@ This command will display the help information for the `myl` command.
|
|
729
745
|
|
730
746
|
Here are some examples of using flags with the `myl` command:
|
731
747
|
|
732
|
-
```
|
748
|
+
```shell
|
733
749
|
# Connect to an IMAP server
|
734
750
|
myl --server imap.example.com --port 143 --starttls --username "$username" --password "$password"
|
735
751
|
|
@@ -27,15 +27,23 @@ straightforward way to interact with IMAP servers.
|
|
27
27
|
|
28
28
|
To install myl, follow these steps:
|
29
29
|
|
30
|
-
```
|
30
|
+
```shell
|
31
31
|
pipx install myl
|
32
|
+
# or:
|
33
|
+
pip install --user myl
|
34
|
+
```
|
35
|
+
|
36
|
+
on nix you can do this:
|
37
|
+
|
38
|
+
```shell
|
39
|
+
nix run github:pschmitt/myl -- --help
|
32
40
|
```
|
33
41
|
|
34
42
|
## 🛠️ Usage
|
35
43
|
|
36
44
|
Here's how you can use myl:
|
37
45
|
|
38
|
-
```
|
46
|
+
```shell
|
39
47
|
myl --help
|
40
48
|
```
|
41
49
|
|
@@ -43,7 +51,7 @@ This command will display the help information for the `myl` command.
|
|
43
51
|
|
44
52
|
Here are some examples of using flags with the `myl` command:
|
45
53
|
|
46
|
-
```
|
54
|
+
```shell
|
47
55
|
# Connect to an IMAP server
|
48
56
|
myl --server imap.example.com --port 143 --starttls --username "$username" --password "$password"
|
49
57
|
|
myl-0.9.2/flake.lock
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
{
|
2
|
+
"nodes": {
|
3
|
+
"flake-utils": {
|
4
|
+
"inputs": {
|
5
|
+
"systems": "systems"
|
6
|
+
},
|
7
|
+
"locked": {
|
8
|
+
"lastModified": 1726560853,
|
9
|
+
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
10
|
+
"owner": "numtide",
|
11
|
+
"repo": "flake-utils",
|
12
|
+
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
13
|
+
"type": "github"
|
14
|
+
},
|
15
|
+
"original": {
|
16
|
+
"owner": "numtide",
|
17
|
+
"repo": "flake-utils",
|
18
|
+
"type": "github"
|
19
|
+
}
|
20
|
+
},
|
21
|
+
"flake-utils_2": {
|
22
|
+
"inputs": {
|
23
|
+
"systems": "systems_2"
|
24
|
+
},
|
25
|
+
"locked": {
|
26
|
+
"lastModified": 1726560853,
|
27
|
+
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
28
|
+
"owner": "numtide",
|
29
|
+
"repo": "flake-utils",
|
30
|
+
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
31
|
+
"type": "github"
|
32
|
+
},
|
33
|
+
"original": {
|
34
|
+
"owner": "numtide",
|
35
|
+
"repo": "flake-utils",
|
36
|
+
"type": "github"
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"myl-discovery": {
|
40
|
+
"inputs": {
|
41
|
+
"flake-utils": "flake-utils_2",
|
42
|
+
"nixpkgs": [
|
43
|
+
"nixpkgs"
|
44
|
+
]
|
45
|
+
},
|
46
|
+
"locked": {
|
47
|
+
"lastModified": 1730392848,
|
48
|
+
"narHash": "sha256-7RBwu5zJXV+90NBwGZwO459FswogX1zcgsEuOi2Eqlc=",
|
49
|
+
"owner": "pschmitt",
|
50
|
+
"repo": "myl-discovery",
|
51
|
+
"rev": "6cc8eab2e61efbf17ab48356282d35a11b9dd7a4",
|
52
|
+
"type": "github"
|
53
|
+
},
|
54
|
+
"original": {
|
55
|
+
"owner": "pschmitt",
|
56
|
+
"repo": "myl-discovery",
|
57
|
+
"type": "github"
|
58
|
+
}
|
59
|
+
},
|
60
|
+
"nixpkgs": {
|
61
|
+
"locked": {
|
62
|
+
"lastModified": 1730200266,
|
63
|
+
"narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=",
|
64
|
+
"owner": "NixOS",
|
65
|
+
"repo": "nixpkgs",
|
66
|
+
"rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd",
|
67
|
+
"type": "github"
|
68
|
+
},
|
69
|
+
"original": {
|
70
|
+
"owner": "NixOS",
|
71
|
+
"ref": "nixos-unstable",
|
72
|
+
"repo": "nixpkgs",
|
73
|
+
"type": "github"
|
74
|
+
}
|
75
|
+
},
|
76
|
+
"root": {
|
77
|
+
"inputs": {
|
78
|
+
"flake-utils": "flake-utils",
|
79
|
+
"myl-discovery": "myl-discovery",
|
80
|
+
"nixpkgs": "nixpkgs"
|
81
|
+
}
|
82
|
+
},
|
83
|
+
"systems": {
|
84
|
+
"locked": {
|
85
|
+
"lastModified": 1681028828,
|
86
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
87
|
+
"owner": "nix-systems",
|
88
|
+
"repo": "default",
|
89
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
90
|
+
"type": "github"
|
91
|
+
},
|
92
|
+
"original": {
|
93
|
+
"owner": "nix-systems",
|
94
|
+
"repo": "default",
|
95
|
+
"type": "github"
|
96
|
+
}
|
97
|
+
},
|
98
|
+
"systems_2": {
|
99
|
+
"locked": {
|
100
|
+
"lastModified": 1681028828,
|
101
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
102
|
+
"owner": "nix-systems",
|
103
|
+
"repo": "default",
|
104
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
105
|
+
"type": "github"
|
106
|
+
},
|
107
|
+
"original": {
|
108
|
+
"owner": "nix-systems",
|
109
|
+
"repo": "default",
|
110
|
+
"type": "github"
|
111
|
+
}
|
112
|
+
}
|
113
|
+
},
|
114
|
+
"root": "root",
|
115
|
+
"version": 7
|
116
|
+
}
|
myl-0.9.2/flake.nix
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
{
|
2
|
+
description = "Flake for myl IMAP CLI client and myl-discovery, compatible with multiple systems";
|
3
|
+
|
4
|
+
inputs = {
|
5
|
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
6
|
+
flake-utils.url = "github:numtide/flake-utils";
|
7
|
+
myl-discovery = {
|
8
|
+
url = "github:pschmitt/myl-discovery";
|
9
|
+
inputs.nixpkgs.follows = "nixpkgs";
|
10
|
+
};
|
11
|
+
};
|
12
|
+
|
13
|
+
outputs =
|
14
|
+
{
|
15
|
+
self,
|
16
|
+
nixpkgs,
|
17
|
+
flake-utils,
|
18
|
+
myl-discovery,
|
19
|
+
...
|
20
|
+
}:
|
21
|
+
flake-utils.lib.eachDefaultSystem (
|
22
|
+
system:
|
23
|
+
let
|
24
|
+
pkgs = nixpkgs.legacyPackages.${system};
|
25
|
+
|
26
|
+
myl = pkgs.python3Packages.buildPythonApplication {
|
27
|
+
pname = "myl";
|
28
|
+
version = builtins.readFile ./version.txt;
|
29
|
+
pyproject = true;
|
30
|
+
|
31
|
+
src = ./.;
|
32
|
+
|
33
|
+
buildInputs = [
|
34
|
+
pkgs.python3Packages.setuptools
|
35
|
+
pkgs.python3Packages.setuptools-scm
|
36
|
+
];
|
37
|
+
|
38
|
+
propagatedBuildInputs = with pkgs.python3Packages; [
|
39
|
+
html2text
|
40
|
+
imap-tools
|
41
|
+
myl-discovery.packages.${system}.myl-discovery
|
42
|
+
rich
|
43
|
+
];
|
44
|
+
|
45
|
+
meta = {
|
46
|
+
description = "Dead simple IMAP CLI client";
|
47
|
+
homepage = "https://pypi.org/project/myl/";
|
48
|
+
license = pkgs.lib.licenses.gpl3Only;
|
49
|
+
maintainers = with pkgs.lib.maintainers; [ pschmitt ];
|
50
|
+
platforms = pkgs.lib.platforms.all;
|
51
|
+
};
|
52
|
+
};
|
53
|
+
|
54
|
+
devShell = pkgs.mkShell {
|
55
|
+
name = "myl-devshell";
|
56
|
+
|
57
|
+
buildInputs = [
|
58
|
+
pkgs.python3
|
59
|
+
pkgs.python3Packages.setuptools
|
60
|
+
pkgs.python3Packages.setuptools-scm
|
61
|
+
pkgs.python3Packages.html2text
|
62
|
+
pkgs.python3Packages.imap-tools
|
63
|
+
self.packages.${system}.myl-discovery
|
64
|
+
pkgs.python3Packages.rich
|
65
|
+
];
|
66
|
+
|
67
|
+
# Additional development tools
|
68
|
+
nativeBuildInputs = [
|
69
|
+
pkgs.gh # GitHub CLI
|
70
|
+
pkgs.git
|
71
|
+
pkgs.python3Packages.ipython
|
72
|
+
pkgs.neovim
|
73
|
+
];
|
74
|
+
|
75
|
+
# Environment variables and shell hooks
|
76
|
+
shellHook = ''
|
77
|
+
export PYTHONPATH=${self.packages.${system}.myl}/lib/python3.x/site-packages
|
78
|
+
echo -e "\e[34mWelcome to the myl development shell!\e[0m"
|
79
|
+
# Activate a virtual environment if desired
|
80
|
+
# source .venv/bin/activate
|
81
|
+
'';
|
82
|
+
|
83
|
+
# Optional: Set up a Python virtual environment
|
84
|
+
# if you prefer using virtualenv or similar tools
|
85
|
+
# you can uncomment and configure the following lines
|
86
|
+
# shellHook = ''
|
87
|
+
# if [ ! -d .venv ]; then
|
88
|
+
# python3 -m venv .venv
|
89
|
+
# source .venv/bin/activate
|
90
|
+
# pip install --upgrade pip
|
91
|
+
# else
|
92
|
+
# source .venv/bin/activate
|
93
|
+
# fi
|
94
|
+
# '';
|
95
|
+
};
|
96
|
+
in
|
97
|
+
{
|
98
|
+
# pkgs
|
99
|
+
packages.myl = myl;
|
100
|
+
defaultPackage = myl;
|
101
|
+
|
102
|
+
devShells.default = devShell;
|
103
|
+
}
|
104
|
+
);
|
105
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: myl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.2
|
4
4
|
Summary: Dead simple IMAP CLI client
|
5
5
|
Author-email: Philipp Schmitt <philipp@schmitt.co>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -678,11 +678,19 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
678
678
|
Public License instead of this License. But first, please read
|
679
679
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
680
680
|
|
681
|
+
Project-URL: homepage, https://github.com/pschmitt/myl
|
682
|
+
Project-URL: documentation, https://github.com/pschmitt/myl/blob/head/readme.md
|
683
|
+
Project-URL: repository, https://github.com/pschmitt/myl
|
684
|
+
Project-URL: issues, https://github.com/pschmitt/myl/issues
|
681
685
|
Keywords: imap,email
|
682
686
|
Classifier: Programming Language :: Python :: 3
|
683
687
|
Requires-Python: >=3.8
|
684
688
|
Description-Content-Type: text/markdown
|
685
689
|
License-File: LICENSE
|
690
|
+
Requires-Dist: imap-tools<2.0.0,>=1.5.0
|
691
|
+
Requires-Dist: myl-discovery>=0.6.1.dev0
|
692
|
+
Requires-Dist: rich<14.0.0,>=13.0.0
|
693
|
+
Requires-Dist: html2text>=2024.2.26
|
686
694
|
|
687
695
|
# 📧 myl
|
688
696
|
|
@@ -713,15 +721,23 @@ straightforward way to interact with IMAP servers.
|
|
713
721
|
|
714
722
|
To install myl, follow these steps:
|
715
723
|
|
716
|
-
```
|
724
|
+
```shell
|
717
725
|
pipx install myl
|
726
|
+
# or:
|
727
|
+
pip install --user myl
|
728
|
+
```
|
729
|
+
|
730
|
+
on nix you can do this:
|
731
|
+
|
732
|
+
```shell
|
733
|
+
nix run github:pschmitt/myl -- --help
|
718
734
|
```
|
719
735
|
|
720
736
|
## 🛠️ Usage
|
721
737
|
|
722
738
|
Here's how you can use myl:
|
723
739
|
|
724
|
-
```
|
740
|
+
```shell
|
725
741
|
myl --help
|
726
742
|
```
|
727
743
|
|
@@ -729,7 +745,7 @@ This command will display the help information for the `myl` command.
|
|
729
745
|
|
730
746
|
Here are some examples of using flags with the `myl` command:
|
731
747
|
|
732
|
-
```
|
748
|
+
```shell
|
733
749
|
# Connect to an IMAP server
|
734
750
|
myl --server imap.example.com --port 143 --starttls --username "$username" --password "$password"
|
735
751
|
|
myl-0.9.2/myl.py
ADDED
@@ -0,0 +1,508 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
from importlib.metadata import version, PackageNotFoundError
|
5
|
+
import argparse
|
6
|
+
import logging
|
7
|
+
import ssl
|
8
|
+
import sys
|
9
|
+
from json import dumps as json_dumps
|
10
|
+
|
11
|
+
import html2text
|
12
|
+
from imap_tools.consts import MailMessageFlags
|
13
|
+
from imap_tools.mailbox import (
|
14
|
+
BaseMailBox,
|
15
|
+
MailBox,
|
16
|
+
MailBoxTls,
|
17
|
+
MailBoxUnencrypted,
|
18
|
+
)
|
19
|
+
from imap_tools.query import AND
|
20
|
+
from myldiscovery import autodiscover
|
21
|
+
from rich import print, print_json
|
22
|
+
from rich.console import Console
|
23
|
+
from rich.logging import RichHandler
|
24
|
+
from rich.table import Table
|
25
|
+
|
26
|
+
try:
|
27
|
+
__version__ = version("myl")
|
28
|
+
except PackageNotFoundError:
|
29
|
+
pass
|
30
|
+
|
31
|
+
LOGGER = logging.getLogger(__name__)
|
32
|
+
IMAP_PORT = 993
|
33
|
+
GMAIL_IMAP_SERVER = "imap.gmail.com"
|
34
|
+
GMAIL_IMAP_PORT = IMAP_PORT
|
35
|
+
GMAIL_SENT_FOLDER = "[Gmail]/Sent Mail"
|
36
|
+
# GMAIL_ALL_FOLDER = "[Gmail]/All Mail"
|
37
|
+
|
38
|
+
|
39
|
+
class MissingServerException(Exception):
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
def error_msg(msg):
|
44
|
+
print(f"[red]{msg}[/red]", file=sys.stderr)
|
45
|
+
|
46
|
+
|
47
|
+
def mail_to_dict(msg, date_format="%Y-%m-%d %H:%M:%S"):
|
48
|
+
return {
|
49
|
+
"uid": msg.uid,
|
50
|
+
"subject": msg.subject,
|
51
|
+
"from": msg.from_,
|
52
|
+
"to": msg.to,
|
53
|
+
"date": msg.date.strftime(date_format),
|
54
|
+
"timestamp": str(int(msg.date.timestamp())),
|
55
|
+
"unread": mail_is_unread(msg),
|
56
|
+
"flags": msg.flags,
|
57
|
+
"content": {
|
58
|
+
"raw": msg.obj.as_string(),
|
59
|
+
"html": msg.html,
|
60
|
+
"text": msg.text,
|
61
|
+
},
|
62
|
+
"attachments": msg.attachments,
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
def mail_to_json(msg, date_format="%Y-%m-%d %H:%M:%S"):
|
67
|
+
return json_dumps(mail_to_dict(msg, date_format))
|
68
|
+
|
69
|
+
|
70
|
+
def mail_is_unread(msg):
|
71
|
+
return MailMessageFlags.SEEN not in msg.flags
|
72
|
+
|
73
|
+
|
74
|
+
def parse_args():
|
75
|
+
parser = argparse.ArgumentParser()
|
76
|
+
subparsers = parser.add_subparsers(
|
77
|
+
dest="command", help="Available commands"
|
78
|
+
)
|
79
|
+
parser.add_argument(
|
80
|
+
"-V",
|
81
|
+
"--version",
|
82
|
+
action="version",
|
83
|
+
version=f"%(prog)s {__version__}",
|
84
|
+
)
|
85
|
+
|
86
|
+
# Default command: list all emails
|
87
|
+
subparsers.add_parser("list", help="List all emails")
|
88
|
+
|
89
|
+
# Get/show email command
|
90
|
+
get_parser = subparsers.add_parser(
|
91
|
+
"get", help="Retrieve a specific email or attachment"
|
92
|
+
)
|
93
|
+
get_parser.add_argument("MAILID", help="Mail ID to fetch", type=int)
|
94
|
+
get_parser.add_argument(
|
95
|
+
"ATTACHMENT",
|
96
|
+
help="Name of the attachment to fetch",
|
97
|
+
nargs="?",
|
98
|
+
default=None,
|
99
|
+
)
|
100
|
+
|
101
|
+
# Delete email command
|
102
|
+
delete_parser = subparsers.add_parser("delete", help="Delete an email")
|
103
|
+
delete_parser.add_argument(
|
104
|
+
"MAILIDS", help="Mail ID(s) to delete", type=int, nargs="+"
|
105
|
+
)
|
106
|
+
|
107
|
+
# Mark email as read/unread
|
108
|
+
mark_read_parser = subparsers.add_parser(
|
109
|
+
"read", help="mark an email as read"
|
110
|
+
)
|
111
|
+
mark_read_parser.add_argument(
|
112
|
+
"MAILIDS", help="Mail ID(s) to mark as read", type=int, nargs="+"
|
113
|
+
)
|
114
|
+
mark_unread_parser = subparsers.add_parser(
|
115
|
+
"unread", help="mark an email as unread"
|
116
|
+
)
|
117
|
+
mark_unread_parser.add_argument(
|
118
|
+
"MAILIDS", help="Mail ID(s) to mark as unread", type=int, nargs="+"
|
119
|
+
)
|
120
|
+
|
121
|
+
# Optional arguments
|
122
|
+
parser.add_argument(
|
123
|
+
"-d", "--debug", help="Enable debug mode", action="store_true"
|
124
|
+
)
|
125
|
+
|
126
|
+
# IMAP connection settings
|
127
|
+
parser.add_argument(
|
128
|
+
"-s", "--server", help="IMAP server address", required=False
|
129
|
+
)
|
130
|
+
parser.add_argument(
|
131
|
+
"--google",
|
132
|
+
"--gmail",
|
133
|
+
help="Use Google IMAP settings (overrides --port, --server etc.)",
|
134
|
+
action="store_true",
|
135
|
+
default=False,
|
136
|
+
)
|
137
|
+
parser.add_argument(
|
138
|
+
"-a",
|
139
|
+
"--auto",
|
140
|
+
help="Autodiscovery of the required server and port",
|
141
|
+
action="store_true",
|
142
|
+
default=True,
|
143
|
+
)
|
144
|
+
parser.add_argument(
|
145
|
+
"-P", "--port", help="IMAP server port", default=IMAP_PORT
|
146
|
+
)
|
147
|
+
parser.add_argument("--ssl", help="SSL", action="store_true", default=True)
|
148
|
+
parser.add_argument(
|
149
|
+
"--starttls", help="STARTTLS", action="store_true", default=False
|
150
|
+
)
|
151
|
+
parser.add_argument(
|
152
|
+
"--insecure",
|
153
|
+
help="Disable cert validation",
|
154
|
+
action="store_true",
|
155
|
+
default=False,
|
156
|
+
)
|
157
|
+
|
158
|
+
# Credentials
|
159
|
+
parser.add_argument(
|
160
|
+
"-u", "--username", help="IMAP username", required=True
|
161
|
+
)
|
162
|
+
password_group = parser.add_mutually_exclusive_group(required=True)
|
163
|
+
password_group.add_argument("-p", "--password", help="IMAP password")
|
164
|
+
password_group.add_argument(
|
165
|
+
"--password-file",
|
166
|
+
help="IMAP password (file path)",
|
167
|
+
type=argparse.FileType("r"),
|
168
|
+
)
|
169
|
+
|
170
|
+
# Display preferences
|
171
|
+
parser.add_argument(
|
172
|
+
"-c",
|
173
|
+
"--count",
|
174
|
+
help="Number of messages to fetch",
|
175
|
+
default=10,
|
176
|
+
type=int,
|
177
|
+
)
|
178
|
+
parser.add_argument(
|
179
|
+
"-t", "--no-title", help="Do not show title", action="store_true"
|
180
|
+
)
|
181
|
+
parser.add_argument(
|
182
|
+
"--date-format", help="Date format", default="%H:%M %d/%m/%Y"
|
183
|
+
)
|
184
|
+
|
185
|
+
# IMAP actions
|
186
|
+
parser.add_argument(
|
187
|
+
"-m",
|
188
|
+
"--mark-seen",
|
189
|
+
help="Mark seen",
|
190
|
+
action="store_true",
|
191
|
+
default=False,
|
192
|
+
)
|
193
|
+
|
194
|
+
# Email filtering
|
195
|
+
parser.add_argument("-f", "--folder", help="IMAP folder", default="INBOX")
|
196
|
+
parser.add_argument(
|
197
|
+
"--sent",
|
198
|
+
help="Sent email",
|
199
|
+
action="store_true",
|
200
|
+
)
|
201
|
+
parser.add_argument("-S", "--search", help="Search string", default="ALL")
|
202
|
+
parser.add_argument(
|
203
|
+
"--unread",
|
204
|
+
help="Limit to unread emails",
|
205
|
+
action="store_true",
|
206
|
+
default=False,
|
207
|
+
)
|
208
|
+
|
209
|
+
# Output preferences
|
210
|
+
parser.add_argument(
|
211
|
+
"-H",
|
212
|
+
"--html",
|
213
|
+
help="Show HTML email",
|
214
|
+
action="store_true",
|
215
|
+
default=False,
|
216
|
+
)
|
217
|
+
parser.add_argument(
|
218
|
+
"-j",
|
219
|
+
"--json",
|
220
|
+
help="JSON output",
|
221
|
+
action="store_true",
|
222
|
+
default=False,
|
223
|
+
)
|
224
|
+
parser.add_argument(
|
225
|
+
"-r",
|
226
|
+
"--raw",
|
227
|
+
help="Show the raw email",
|
228
|
+
action="store_true",
|
229
|
+
default=False,
|
230
|
+
)
|
231
|
+
|
232
|
+
return parser.parse_args()
|
233
|
+
|
234
|
+
|
235
|
+
def mb_connect(console, args) -> BaseMailBox:
|
236
|
+
imap_password = args.password or (
|
237
|
+
args.password_file and args.password_file.read()
|
238
|
+
)
|
239
|
+
|
240
|
+
if args.google:
|
241
|
+
args.server = GMAIL_IMAP_SERVER
|
242
|
+
args.port = GMAIL_IMAP_PORT
|
243
|
+
args.starttls = False
|
244
|
+
|
245
|
+
if args.sent or args.folder == "Sent":
|
246
|
+
args.folder = GMAIL_SENT_FOLDER
|
247
|
+
# elif args.folder == "INBOX":
|
248
|
+
# args.folder = GMAIL_ALL_FOLDER
|
249
|
+
else:
|
250
|
+
if args.auto:
|
251
|
+
try:
|
252
|
+
settings = autodiscover(
|
253
|
+
args.username,
|
254
|
+
password=imap_password,
|
255
|
+
insecure=args.insecure,
|
256
|
+
).get("imap", {})
|
257
|
+
except Exception:
|
258
|
+
error_msg("Failed to autodiscover IMAP settings")
|
259
|
+
if args.debug:
|
260
|
+
console.print_exception(show_locals=True)
|
261
|
+
raise
|
262
|
+
|
263
|
+
LOGGER.debug(f"Discovered settings: {settings})")
|
264
|
+
args.server = settings.get("server")
|
265
|
+
args.port = settings.get("port", IMAP_PORT)
|
266
|
+
args.starttls = settings.get("starttls")
|
267
|
+
args.ssl = settings.get("ssl")
|
268
|
+
|
269
|
+
if args.sent:
|
270
|
+
args.folder = "Sent"
|
271
|
+
|
272
|
+
if not args.server:
|
273
|
+
error_msg(
|
274
|
+
"No server specified\n"
|
275
|
+
"You need to either:\n"
|
276
|
+
"- specify a server using --server HOSTNAME\n"
|
277
|
+
"- set --google if you are using a Gmail account\n"
|
278
|
+
"- use --auto to attempt autodiscovery"
|
279
|
+
)
|
280
|
+
raise MissingServerException()
|
281
|
+
|
282
|
+
ssl_context = ssl.create_default_context()
|
283
|
+
if args.insecure:
|
284
|
+
ssl_context.check_hostname = False
|
285
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
286
|
+
|
287
|
+
mb_kwargs = {"host": args.server, "port": args.port}
|
288
|
+
if args.ssl:
|
289
|
+
mb = MailBox
|
290
|
+
mb_kwargs["ssl_context"] = ssl_context
|
291
|
+
elif args.starttls:
|
292
|
+
mb = MailBoxTls
|
293
|
+
mb_kwargs["ssl_context"] = ssl_context
|
294
|
+
else:
|
295
|
+
mb = MailBoxUnencrypted
|
296
|
+
|
297
|
+
mailbox = mb(**mb_kwargs)
|
298
|
+
mailbox.login(args.username, imap_password, args.folder)
|
299
|
+
return mailbox
|
300
|
+
|
301
|
+
|
302
|
+
def display_single_mail(
|
303
|
+
mailbox: BaseMailBox,
|
304
|
+
mail_id: int,
|
305
|
+
attachment: str | None = None,
|
306
|
+
mark_seen: bool = False,
|
307
|
+
raw: bool = False,
|
308
|
+
html: bool = False,
|
309
|
+
json: bool = False,
|
310
|
+
):
|
311
|
+
LOGGER.debug("Fetch mail %s", mail_id)
|
312
|
+
msg = next(mailbox.fetch(f"UID {mail_id}", mark_seen=mark_seen))
|
313
|
+
LOGGER.debug("Fetched mail %s", msg)
|
314
|
+
|
315
|
+
if attachment:
|
316
|
+
for att in msg.attachments:
|
317
|
+
if att.filename == attachment:
|
318
|
+
sys.stdout.buffer.write(att.payload)
|
319
|
+
return 0
|
320
|
+
print(
|
321
|
+
f"attachment {attachment} not found",
|
322
|
+
file=sys.stderr,
|
323
|
+
)
|
324
|
+
return 1
|
325
|
+
|
326
|
+
if html:
|
327
|
+
output = msg.text
|
328
|
+
if raw:
|
329
|
+
output = msg.html
|
330
|
+
else:
|
331
|
+
output = html2text.html2text(msg.html)
|
332
|
+
print(output)
|
333
|
+
elif raw:
|
334
|
+
print(msg.obj.as_string())
|
335
|
+
return 0
|
336
|
+
elif json:
|
337
|
+
print_json(mail_to_json(msg))
|
338
|
+
return 0
|
339
|
+
else:
|
340
|
+
print(msg.text)
|
341
|
+
|
342
|
+
for att in msg.attachments:
|
343
|
+
print(f"📎 Attachment: {att.filename}", file=sys.stderr)
|
344
|
+
return 0
|
345
|
+
|
346
|
+
|
347
|
+
def display_emails(
|
348
|
+
mailbox,
|
349
|
+
console,
|
350
|
+
no_title=False,
|
351
|
+
search="ALL",
|
352
|
+
unread_only=False,
|
353
|
+
count=10,
|
354
|
+
mark_seen=False,
|
355
|
+
json=False,
|
356
|
+
date_format="%H:%M %d/%m/%Y",
|
357
|
+
):
|
358
|
+
json_data = []
|
359
|
+
table = Table(
|
360
|
+
show_header=not no_title,
|
361
|
+
header_style="bold",
|
362
|
+
expand=True,
|
363
|
+
show_lines=False,
|
364
|
+
show_edge=False,
|
365
|
+
pad_edge=False,
|
366
|
+
box=None,
|
367
|
+
row_styles=["", "dim"],
|
368
|
+
)
|
369
|
+
table.add_column("ID", style="red", no_wrap=True)
|
370
|
+
table.add_column("Subject", style="green", no_wrap=True, ratio=3)
|
371
|
+
table.add_column("From", style="blue", no_wrap=True, ratio=2)
|
372
|
+
table.add_column("Date", style="cyan", no_wrap=True)
|
373
|
+
|
374
|
+
if unread_only:
|
375
|
+
search = AND(seen=False)
|
376
|
+
|
377
|
+
for msg in mailbox.fetch(
|
378
|
+
criteria=search,
|
379
|
+
reverse=True,
|
380
|
+
bulk=True,
|
381
|
+
limit=count,
|
382
|
+
mark_seen=mark_seen,
|
383
|
+
headers_only=False, # required for attachments
|
384
|
+
):
|
385
|
+
subj_prefix = "🆕 " if mail_is_unread(msg) else ""
|
386
|
+
subj_prefix += "📎 " if len(msg.attachments) > 0 else ""
|
387
|
+
subject = (
|
388
|
+
msg.subject.replace("\n", "") if msg.subject else "<no-subject>"
|
389
|
+
)
|
390
|
+
if json:
|
391
|
+
json_data.append(mail_to_dict(msg))
|
392
|
+
else:
|
393
|
+
table.add_row(
|
394
|
+
msg.uid if msg.uid else "???",
|
395
|
+
f"{subj_prefix}{subject}",
|
396
|
+
msg.from_,
|
397
|
+
(msg.date.strftime(date_format) if msg.date else "???"),
|
398
|
+
)
|
399
|
+
if table.row_count >= count:
|
400
|
+
break
|
401
|
+
|
402
|
+
if json:
|
403
|
+
print_json(json_dumps(json_data))
|
404
|
+
else:
|
405
|
+
console.print(table)
|
406
|
+
if table.row_count == 0:
|
407
|
+
print(
|
408
|
+
"[yellow italic]No messages[/yellow italic]",
|
409
|
+
file=sys.stderr,
|
410
|
+
)
|
411
|
+
return 0
|
412
|
+
|
413
|
+
|
414
|
+
def delete_emails(mailbox: BaseMailBox, mail_ids: list):
|
415
|
+
LOGGER.warning("Deleting mails %s", mail_ids)
|
416
|
+
mailbox.delete([str(x) for x in mail_ids])
|
417
|
+
return 0
|
418
|
+
|
419
|
+
|
420
|
+
def set_seen(mailbox: BaseMailBox, mail_ids: list, value=True):
|
421
|
+
LOGGER.info(
|
422
|
+
"Marking mails as %s: %s", "read" if value else "unread", mail_ids
|
423
|
+
)
|
424
|
+
mailbox.flag(
|
425
|
+
[str(x) for x in mail_ids],
|
426
|
+
flag_set=(MailMessageFlags.SEEN),
|
427
|
+
value=value,
|
428
|
+
)
|
429
|
+
return 0
|
430
|
+
|
431
|
+
|
432
|
+
def mark_read(mailbox: BaseMailBox, mail_ids: list):
|
433
|
+
return set_seen(mailbox, mail_ids, value=True)
|
434
|
+
|
435
|
+
|
436
|
+
def mark_unread(mailbox: BaseMailBox, mail_ids: list):
|
437
|
+
return set_seen(mailbox, mail_ids, value=False)
|
438
|
+
|
439
|
+
|
440
|
+
def main() -> int:
|
441
|
+
console = Console()
|
442
|
+
args = parse_args()
|
443
|
+
logging.basicConfig(
|
444
|
+
format="%(message)s",
|
445
|
+
handlers=[RichHandler(console=console)],
|
446
|
+
level=logging.DEBUG if args.debug else logging.INFO,
|
447
|
+
)
|
448
|
+
LOGGER.debug(args)
|
449
|
+
|
450
|
+
try:
|
451
|
+
with mb_connect(console, args) as mailbox:
|
452
|
+
# inbox display
|
453
|
+
if args.command in ["list", None]:
|
454
|
+
return display_emails(
|
455
|
+
mailbox=mailbox,
|
456
|
+
console=console,
|
457
|
+
no_title=args.no_title,
|
458
|
+
search=args.search,
|
459
|
+
unread_only=args.unread,
|
460
|
+
count=args.count,
|
461
|
+
mark_seen=args.mark_seen,
|
462
|
+
json=args.json,
|
463
|
+
date_format=args.date_format,
|
464
|
+
)
|
465
|
+
|
466
|
+
# single email
|
467
|
+
# FIXME $ myl 219 raises an argparse error
|
468
|
+
elif args.command in ["get", "show", "display"]:
|
469
|
+
return display_single_mail(
|
470
|
+
mailbox=mailbox,
|
471
|
+
mail_id=args.MAILID,
|
472
|
+
attachment=args.ATTACHMENT,
|
473
|
+
mark_seen=args.mark_seen,
|
474
|
+
raw=args.raw,
|
475
|
+
html=args.html,
|
476
|
+
json=args.json,
|
477
|
+
)
|
478
|
+
|
479
|
+
# mark emails as read
|
480
|
+
elif args.command in ["read"]:
|
481
|
+
return mark_read(
|
482
|
+
mailbox=mailbox,
|
483
|
+
mail_ids=args.MAILIDS,
|
484
|
+
)
|
485
|
+
|
486
|
+
elif args.command in ["unread"]:
|
487
|
+
return mark_unread(
|
488
|
+
mailbox=mailbox,
|
489
|
+
mail_ids=args.MAILIDS,
|
490
|
+
)
|
491
|
+
|
492
|
+
# delete email
|
493
|
+
elif args.command in ["delete", "remove"]:
|
494
|
+
return delete_emails(
|
495
|
+
mailbox=mailbox,
|
496
|
+
mail_ids=args.MAILIDS,
|
497
|
+
)
|
498
|
+
else:
|
499
|
+
error_msg(f"Unknown command: {args.command}")
|
500
|
+
return 1
|
501
|
+
|
502
|
+
except Exception:
|
503
|
+
console.print_exception(show_locals=True)
|
504
|
+
return 1
|
505
|
+
|
506
|
+
|
507
|
+
if __name__ == "__main__":
|
508
|
+
sys.exit(main())
|
@@ -16,11 +16,23 @@ classifiers = [
|
|
16
16
|
"Programming Language :: Python :: 3",
|
17
17
|
]
|
18
18
|
dependencies = [
|
19
|
-
"imap-tools
|
20
|
-
"myl-discovery
|
21
|
-
"rich
|
19
|
+
"imap-tools >= 1.5.0, < 2.0.0",
|
20
|
+
"myl-discovery >= 0.6.1.dev0",
|
21
|
+
"rich >= 13.0.0, <14.0.0",
|
22
|
+
"html2text >= 2024.2.26"
|
22
23
|
]
|
23
|
-
|
24
|
+
dynamic = ["version"]
|
25
|
+
|
26
|
+
[tool.setuptools_scm]
|
27
|
+
version_file = "version.txt"
|
28
|
+
version_scheme = "only-version"
|
29
|
+
local_scheme = "no-local-version"
|
30
|
+
|
31
|
+
[project.urls]
|
32
|
+
homepage = "https://github.com/pschmitt/myl"
|
33
|
+
documentation = "https://github.com/pschmitt/myl/blob/head/readme.md"
|
34
|
+
repository = "https://github.com/pschmitt/myl"
|
35
|
+
issues = "https://github.com/pschmitt/myl/issues"
|
24
36
|
|
25
37
|
[tool.black]
|
26
38
|
line-length = 79
|
myl-0.9.2/version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.2
|
myl-0.8.6/myl.py
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
import logging
|
3
|
-
import sys
|
4
|
-
|
5
|
-
import imap_tools
|
6
|
-
from myldiscovery import autodiscover
|
7
|
-
from rich import print
|
8
|
-
from rich.console import Console
|
9
|
-
from rich.logging import RichHandler
|
10
|
-
from rich.table import Table
|
11
|
-
|
12
|
-
LOGGER = logging.getLogger(__name__)
|
13
|
-
IMAP_PORT = 993
|
14
|
-
GMAIL_IMAP_SERVER = "imap.gmail.com"
|
15
|
-
GMAIL_IMAP_PORT = IMAP_PORT
|
16
|
-
GMAIL_SENT_FOLDER = "[Gmail]/Sent Mail"
|
17
|
-
# GMAIL_ALL_FOLDER = "[Gmail]/All Mail"
|
18
|
-
|
19
|
-
|
20
|
-
def error_msg(msg):
|
21
|
-
print(f"[red]{msg}[/red]", file=sys.stderr)
|
22
|
-
|
23
|
-
|
24
|
-
def parse_args():
|
25
|
-
parser = argparse.ArgumentParser()
|
26
|
-
parser.add_argument("-d", "--debug", help="Debug", action="store_true")
|
27
|
-
parser.add_argument(
|
28
|
-
"-s", "--server", help="IMAP server address", required=False
|
29
|
-
)
|
30
|
-
parser.add_argument(
|
31
|
-
"--google",
|
32
|
-
"--gmail",
|
33
|
-
help="Use Google IMAP settings (overrides --port, --server etc.)",
|
34
|
-
action="store_true",
|
35
|
-
default=False,
|
36
|
-
)
|
37
|
-
parser.add_argument(
|
38
|
-
"-a",
|
39
|
-
"--auto",
|
40
|
-
help="Autodiscovery of the required server and port",
|
41
|
-
action="store_true",
|
42
|
-
default=False,
|
43
|
-
)
|
44
|
-
parser.add_argument(
|
45
|
-
"-P", "--port", help="IMAP server port", default=IMAP_PORT
|
46
|
-
)
|
47
|
-
parser.add_argument(
|
48
|
-
"--starttls", help="Start TLS", action="store_true", default=False
|
49
|
-
)
|
50
|
-
parser.add_argument(
|
51
|
-
"-c",
|
52
|
-
"--count",
|
53
|
-
help="Number of messages to fetch",
|
54
|
-
default=10,
|
55
|
-
type=int,
|
56
|
-
)
|
57
|
-
parser.add_argument(
|
58
|
-
"-m", "--mark-seen", help="Mark seen", action="store_true"
|
59
|
-
)
|
60
|
-
parser.add_argument(
|
61
|
-
"-u", "--username", help="IMAP username", required=True
|
62
|
-
)
|
63
|
-
parser.add_argument(
|
64
|
-
"-p", "--password", help="IMAP password", required=True
|
65
|
-
)
|
66
|
-
parser.add_argument(
|
67
|
-
"-t", "--no-title", help="Do not show title", action="store_true"
|
68
|
-
)
|
69
|
-
parser.add_argument("-f", "--folder", help="IMAP folder", default="INBOX")
|
70
|
-
parser.add_argument(
|
71
|
-
"--sent",
|
72
|
-
help="Sent email",
|
73
|
-
action="store_true",
|
74
|
-
)
|
75
|
-
parser.add_argument("-S", "--search", help="Search string", default="ALL")
|
76
|
-
parser.add_argument("-w", "--wrap", help="Wrap text", action="store_true")
|
77
|
-
parser.add_argument("-H", "--html", help="Show HTML", action="store_true")
|
78
|
-
parser.add_argument(
|
79
|
-
"-r",
|
80
|
-
"--raw",
|
81
|
-
help="Show the raw email",
|
82
|
-
action="store_true",
|
83
|
-
default=False,
|
84
|
-
)
|
85
|
-
parser.add_argument("MAILID", help="Mail ID to fetch", nargs="?")
|
86
|
-
parser.add_argument(
|
87
|
-
"ATTACHMENT", help="Name of the attachment to fetch", nargs="?"
|
88
|
-
)
|
89
|
-
|
90
|
-
return parser.parse_args()
|
91
|
-
|
92
|
-
|
93
|
-
def main():
|
94
|
-
console = Console()
|
95
|
-
args = parse_args()
|
96
|
-
logging.basicConfig(
|
97
|
-
format="%(message)s",
|
98
|
-
handlers=[RichHandler(console=console)],
|
99
|
-
level=logging.DEBUG if args.debug else logging.INFO,
|
100
|
-
)
|
101
|
-
LOGGER.debug(args)
|
102
|
-
|
103
|
-
if args.google:
|
104
|
-
args.server = GMAIL_IMAP_SERVER
|
105
|
-
args.port = GMAIL_IMAP_PORT
|
106
|
-
args.starttls = False
|
107
|
-
|
108
|
-
if args.sent or args.folder == "Sent":
|
109
|
-
args.folder = GMAIL_SENT_FOLDER
|
110
|
-
# elif args.folder == "INBOX":
|
111
|
-
# args.folder = GMAIL_ALL_FOLDER
|
112
|
-
else:
|
113
|
-
if args.auto:
|
114
|
-
try:
|
115
|
-
settings = autodiscover(
|
116
|
-
args.username, password=args.password
|
117
|
-
).get("imap")
|
118
|
-
except Exception:
|
119
|
-
error_msg("Failed to autodiscover IMAP settings")
|
120
|
-
if args.debug:
|
121
|
-
console.print_exception(show_locals=True)
|
122
|
-
return 1
|
123
|
-
LOGGER.debug(f"Discovered settings: {settings})")
|
124
|
-
args.server = settings.get("server")
|
125
|
-
args.port = settings.get("port", IMAP_PORT)
|
126
|
-
args.starttls = settings.get("starttls")
|
127
|
-
|
128
|
-
if args.sent:
|
129
|
-
args.folder = "Sent"
|
130
|
-
|
131
|
-
if not args.server:
|
132
|
-
error_msg(
|
133
|
-
"No server specified\n"
|
134
|
-
"You need to either:\n"
|
135
|
-
"- specify a server using --server HOSTNAME\n"
|
136
|
-
"- set --google if you are using a Gmail account\n"
|
137
|
-
"- use --auto to attempt autodiscovery"
|
138
|
-
)
|
139
|
-
return 2
|
140
|
-
|
141
|
-
table = Table(
|
142
|
-
expand=True,
|
143
|
-
show_header=not args.no_title,
|
144
|
-
header_style="bold",
|
145
|
-
show_lines=False,
|
146
|
-
box=None,
|
147
|
-
)
|
148
|
-
table.add_column("ID", style="red", no_wrap=not args.wrap, max_width=10)
|
149
|
-
table.add_column(
|
150
|
-
"Subject", style="green", no_wrap=not args.wrap, max_width=30
|
151
|
-
)
|
152
|
-
table.add_column("From", style="blue", no_wrap=not args.wrap, max_width=30)
|
153
|
-
table.add_column("Date", style="cyan", no_wrap=not args.wrap)
|
154
|
-
|
155
|
-
mb = imap_tools.MailBoxTls if args.starttls else imap_tools.MailBox
|
156
|
-
|
157
|
-
try:
|
158
|
-
with mb(args.server, port=args.port).login(
|
159
|
-
args.username, args.password, args.folder
|
160
|
-
) as mailbox:
|
161
|
-
if args.MAILID:
|
162
|
-
msg = next(
|
163
|
-
mailbox.fetch(
|
164
|
-
f"UID {args.MAILID}", mark_seen=args.mark_seen
|
165
|
-
)
|
166
|
-
)
|
167
|
-
if args.ATTACHMENT:
|
168
|
-
for att in msg.attachments:
|
169
|
-
if att.filename == args.ATTACHMENT:
|
170
|
-
sys.stdout.buffer.write(att.payload)
|
171
|
-
return 0
|
172
|
-
print(
|
173
|
-
f"Attachment {args.ATTACHMENT} not found",
|
174
|
-
file=sys.stderr,
|
175
|
-
)
|
176
|
-
return 1
|
177
|
-
else:
|
178
|
-
if args.raw:
|
179
|
-
print(msg.obj.as_string())
|
180
|
-
return 0
|
181
|
-
print(msg.text if not args.html else msg.html)
|
182
|
-
for att in msg.attachments:
|
183
|
-
print(f"📎 Attachment: {att.filename}", file=sys.stderr)
|
184
|
-
return 0
|
185
|
-
|
186
|
-
for msg in mailbox.fetch(
|
187
|
-
criteria=args.search,
|
188
|
-
reverse=True,
|
189
|
-
bulk=True,
|
190
|
-
limit=args.count,
|
191
|
-
mark_seen=args.mark_seen,
|
192
|
-
headers_only=False, # required for attachments
|
193
|
-
):
|
194
|
-
subj_prefix = "📎 " if len(msg.attachments) > 0 else ""
|
195
|
-
table.add_row(
|
196
|
-
msg.uid if msg.uid else "???",
|
197
|
-
subj_prefix
|
198
|
-
+ (msg.subject if msg.subject else "<no-subject>"),
|
199
|
-
msg.from_,
|
200
|
-
msg.date.strftime("%H:%M %d/%m/%Y") if msg.date else "???",
|
201
|
-
)
|
202
|
-
if len(table.rows) >= args.count:
|
203
|
-
break
|
204
|
-
|
205
|
-
console.print(table)
|
206
|
-
return 0
|
207
|
-
except Exception:
|
208
|
-
console.print_exception(show_locals=True)
|
209
|
-
return 1
|
210
|
-
|
211
|
-
|
212
|
-
if __name__ == "__main__":
|
213
|
-
sys.exit(main())
|
File without changes
|
{myl-0.8.6 → myl-0.9.2}/LICENSE
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|