Add Telegram bot config — @jamesjongsma_bot live (2026-02-18)
This commit is contained in:
parent
b332f741f7
commit
0f17a89871
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,89 @@
|
||||||
|
Here is imapsync 2.229 on host forge, a linux system with 1.0/62.7 free GiB of RAM, 11.66% used by processes.
|
||||||
|
with Perl 5.38.2 and Mail::IMAPClient 3.43
|
||||||
|
Transfer started at Wednesday 18 February 2026-02-18 10:33:40 -0500 EST
|
||||||
|
PID is 704456 my PPID is 1104
|
||||||
|
Log file is LOG_imapsync/2026_02_18_10_33_40_713_tj@jongsma.me_tj.txt ( to change it, use --logfile path ; or use --nolog to turn off logging )
|
||||||
|
Load is 0.96 1.06 1.02 3/754 on 8 cores
|
||||||
|
Current directory is /home/johan/clawd
|
||||||
|
Real user id is johan (uid 1000)
|
||||||
|
Effective user id is johan (euid 1000)
|
||||||
|
$RCSfile: imapsync,v $ $Revision: 2.229 $ $Date: 2022/09/14 18:08:24 $
|
||||||
|
Command line used, run by /usr/bin/perl:
|
||||||
|
/tmp/imapsync --host1 127.0.0.1 --port1 1143 --user1 tj@jongsma.me --password1 MASKED --tls1 --sslargs1 SSL_verify_mode=0 --host2 100.70.148.118 --port2 993 --user2 tj --password2 MASKED --ssl2 --sslargs2 SSL_verify_mode=0 --exclude All Mail --exclude Starred --exclude Labels --automap
|
||||||
|
Temp directory is /tmp ( to change it use --tmpdir dirpath )
|
||||||
|
kill -QUIT 704456 # special behavior: call to sub catch_exit
|
||||||
|
kill -TERM 704456 # special behavior: call to sub catch_exit
|
||||||
|
kill -INT 704456 # special behavior: call to sub catch_reconnect
|
||||||
|
kill -HUP 704456 # special behavior: call to sub catch_print
|
||||||
|
kill -USR1 704456 # special behavior: call to sub toggle_sleep
|
||||||
|
File /tmp/imapsync.pid does not exist
|
||||||
|
PID file is /tmp/imapsync.pid ( to change it, use --pidfile filepath ; to avoid it use --pidfile "" )
|
||||||
|
Writing my PID 704456 in /tmp/imapsync.pid
|
||||||
|
Writing also my logfile name in /tmp/imapsync.pid : LOG_imapsync/2026_02_18_10_33_40_713_tj@jongsma.me_tj.txt
|
||||||
|
Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):
|
||||||
|
Authen::NTLM 1.09
|
||||||
|
CGI 4.63
|
||||||
|
Compress::Zlib 2.204
|
||||||
|
Crypt::OpenSSL::RSA 0.33
|
||||||
|
Data::Uniqid 0.12
|
||||||
|
Digest::HMAC_MD5 1.04
|
||||||
|
Digest::HMAC_SHA1 1.04
|
||||||
|
Digest::MD5 2.58_01
|
||||||
|
Encode 3.19
|
||||||
|
Encode::IMAPUTF7 1.07
|
||||||
|
File::Copy::Recursive 0.45
|
||||||
|
File::Spec 3.88
|
||||||
|
Getopt::Long 2.54
|
||||||
|
HTML::Entities 3.81
|
||||||
|
IO::Socket 1.52
|
||||||
|
IO::Socket::INET 1.52
|
||||||
|
IO::Socket::INET6 2.73
|
||||||
|
IO::Socket::IP 0.4101
|
||||||
|
IO::Socket::SSL 2.085
|
||||||
|
IO::Tee 0.65
|
||||||
|
JSON 4.10
|
||||||
|
JSON::WebToken 0.10
|
||||||
|
LWP 6.76
|
||||||
|
MIME::Base64 3.16_01
|
||||||
|
Mail::IMAPClient 3.43
|
||||||
|
Net::Ping 2.76
|
||||||
|
Net::SSLeay 1.94
|
||||||
|
Term::ReadKey 2.38
|
||||||
|
Test::MockObject Not installed
|
||||||
|
Time::HiRes 1.9775
|
||||||
|
URI::Escape 5.27
|
||||||
|
Unicode::String 2.10
|
||||||
|
|
||||||
|
Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.
|
||||||
|
SSL debug mode level is --debugssl 1 (can be set from 0 meaning no debug to 4 meaning max debug)
|
||||||
|
Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=0", meaning for host2 SSL_VERIFY_NONE, ie, do not check the server certificate.
|
||||||
|
Host2: Use --sslargs2 SSL_verify_mode=1 to have SSL_VERIFY_PEER, ie, check the server certificate. of host2
|
||||||
|
Info: turned ON syncinternaldates, will set the internal dates (arrival dates) on host2 same as host1.
|
||||||
|
Host1: will try to use LOGIN authentication on host1
|
||||||
|
Host2: will try to use LOGIN authentication on host2
|
||||||
|
Host1: imap connection timeout is 120 seconds
|
||||||
|
Host2: imap connection timeout is 120 seconds
|
||||||
|
Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.
|
||||||
|
Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.
|
||||||
|
Host1: IMAP server [127.0.0.1] port [1143] user [tj@jongsma.me]
|
||||||
|
Host2: IMAP server [100.70.148.118] port [993] user [tj]
|
||||||
|
Host1: connecting and login on host1 [127.0.0.1] port [1143] with user [tj@jongsma.me]
|
||||||
|
Host1 IP address: 127.0.0.1 Local IP address: 127.0.0.1
|
||||||
|
Host1 banner: * OK [CAPABILITY AUTH=PLAIN ID IDLE IMAP4rev1 MOVE STARTTLS UIDPLUS UNSELECT] Proton Mail Bridge 03.21.02 - gluon session ID 1867
|
||||||
|
Host1 capability before authentication: AUTH=PLAIN ID IDLE IMAP4rev1 STARTTLS AUTH
|
||||||
|
Host1: Socket successfully converted to SSL
|
||||||
|
Host1: success login on [127.0.0.1] with user [tj@jongsma.me] auth [LOGIN] or [LOGIN]
|
||||||
|
Host2: connecting and login on host2 [100.70.148.118] port [993] with user [tj]
|
||||||
|
Host2 IP address: 100.70.148.118 Local IP address: 100.117.7.9
|
||||||
|
Host2 banner: * OK [CAPABILITY IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS AUTH=PLAIN AUTH=OAUTHBEARER AUTH=XOAUTH2] Stalwart IMAP4rev2 at your service.
|
||||||
|
Host2 capability before authentication: IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS AUTH=PLAIN AUTH=OAUTHBEARER AUTH=XOAUTH2 UTF8 AUTH
|
||||||
|
Host2 info: authmech [LOGIN] user [tj] authuser [] IsUnconnected []
|
||||||
|
Host2 failure: Error login on [100.70.148.118] with user [tj] auth [LOGIN]: 2 NO [AUTHENTICATIONFAILED] Authentication failed
|
||||||
|
Host2: failed login on [100.70.148.118] with user [tj] auth [LOGIN]
|
||||||
|
++++ Listing 1 errors encountered during the sync ( avoid this listing with --noerrorsdump ).
|
||||||
|
Err 1/1: Host2 failure: Error login on [100.70.148.118] with user [tj] auth [LOGIN]: 2 NO [AUTHENTICATIONFAILED] Authentication failed
|
||||||
|
The most frequent error is ERR_AUTHENTICATION_FAILURE_USER2. Check the credentials for tj.
|
||||||
|
Exiting with return value 162 (EXIT_AUTHENTICATION_FAILURE_USER2) 1/50 nb_errors/max_errors PID 704456
|
||||||
|
Removing pidfile /tmp/imapsync.pid
|
||||||
|
Disconnecting from host1 127.0.0.1 user1 tj@jongsma.me
|
||||||
|
Log file is LOG_imapsync/2026_02_18_10_33_40_713_tj@jongsma.me_tj.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging )
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
Here is imapsync 2.229 on host forge, a linux system with 1.3/62.7 free GiB of RAM, 11.10% used by processes.
|
||||||
|
with Perl 5.38.2 and Mail::IMAPClient 3.43
|
||||||
|
Transfer started at Wednesday 18 February 2026-02-18 10:42:40 -0500 EST
|
||||||
|
PID is 710983 my PPID is 1104
|
||||||
|
Log file is LOG_imapsync/2026_02_18_10_42_40_710_tj@jongsma.me_tj.txt ( to change it, use --logfile path ; or use --nolog to turn off logging )
|
||||||
|
Load is 0.58 0.56 0.76 1/737 on 8 cores
|
||||||
|
Current directory is /home/johan/clawd
|
||||||
|
Real user id is johan (uid 1000)
|
||||||
|
Effective user id is johan (euid 1000)
|
||||||
|
$RCSfile: imapsync,v $ $Revision: 2.229 $ $Date: 2022/09/14 18:08:24 $
|
||||||
|
Command line used, run by /usr/bin/perl:
|
||||||
|
/tmp/imapsync --host1 127.0.0.1 --port1 1143 --user1 tj@jongsma.me --password1 MASKED --tls1 --sslargs1 SSL_verify_mode=0 --host2 127.0.0.1 --port2 9993 --user2 tj --password2 MASKED --ssl2 --sslargs2 SSL_verify_mode=0 --exclude All Mail --exclude Starred --exclude Labels --automap
|
||||||
|
Temp directory is /tmp ( to change it use --tmpdir dirpath )
|
||||||
|
kill -QUIT 710983 # special behavior: call to sub catch_exit
|
||||||
|
kill -TERM 710983 # special behavior: call to sub catch_exit
|
||||||
|
kill -INT 710983 # special behavior: call to sub catch_reconnect
|
||||||
|
kill -HUP 710983 # special behavior: call to sub catch_print
|
||||||
|
kill -USR1 710983 # special behavior: call to sub toggle_sleep
|
||||||
|
File /tmp/imapsync.pid does not exist
|
||||||
|
PID file is /tmp/imapsync.pid ( to change it, use --pidfile filepath ; to avoid it use --pidfile "" )
|
||||||
|
Writing my PID 710983 in /tmp/imapsync.pid
|
||||||
|
Writing also my logfile name in /tmp/imapsync.pid : LOG_imapsync/2026_02_18_10_42_40_710_tj@jongsma.me_tj.txt
|
||||||
|
Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):
|
||||||
|
Authen::NTLM 1.09
|
||||||
|
CGI 4.63
|
||||||
|
Compress::Zlib 2.204
|
||||||
|
Crypt::OpenSSL::RSA 0.33
|
||||||
|
Data::Uniqid 0.12
|
||||||
|
Digest::HMAC_MD5 1.04
|
||||||
|
Digest::HMAC_SHA1 1.04
|
||||||
|
Digest::MD5 2.58_01
|
||||||
|
Encode 3.19
|
||||||
|
Encode::IMAPUTF7 1.07
|
||||||
|
File::Copy::Recursive 0.45
|
||||||
|
File::Spec 3.88
|
||||||
|
Getopt::Long 2.54
|
||||||
|
HTML::Entities 3.81
|
||||||
|
IO::Socket 1.52
|
||||||
|
IO::Socket::INET 1.52
|
||||||
|
IO::Socket::INET6 2.73
|
||||||
|
IO::Socket::IP 0.4101
|
||||||
|
IO::Socket::SSL 2.085
|
||||||
|
IO::Tee 0.65
|
||||||
|
JSON 4.10
|
||||||
|
JSON::WebToken 0.10
|
||||||
|
LWP 6.76
|
||||||
|
MIME::Base64 3.16_01
|
||||||
|
Mail::IMAPClient 3.43
|
||||||
|
Net::Ping 2.76
|
||||||
|
Net::SSLeay 1.94
|
||||||
|
Term::ReadKey 2.38
|
||||||
|
Test::MockObject Not installed
|
||||||
|
Time::HiRes 1.9775
|
||||||
|
URI::Escape 5.27
|
||||||
|
Unicode::String 2.10
|
||||||
|
|
||||||
|
Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.
|
||||||
|
SSL debug mode level is --debugssl 1 (can be set from 0 meaning no debug to 4 meaning max debug)
|
||||||
|
Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=0", meaning for host2 SSL_VERIFY_NONE, ie, do not check the server certificate.
|
||||||
|
Host2: Use --sslargs2 SSL_verify_mode=1 to have SSL_VERIFY_PEER, ie, check the server certificate. of host2
|
||||||
|
Info: turned ON syncinternaldates, will set the internal dates (arrival dates) on host2 same as host1.
|
||||||
|
Host1: will try to use LOGIN authentication on host1
|
||||||
|
Host2: will try to use LOGIN authentication on host2
|
||||||
|
Host1: imap connection timeout is 120 seconds
|
||||||
|
Host2: imap connection timeout is 120 seconds
|
||||||
|
Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.
|
||||||
|
Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.
|
||||||
|
Host1: IMAP server [127.0.0.1] port [1143] user [tj@jongsma.me]
|
||||||
|
Host2: IMAP server [127.0.0.1] port [9993] user [tj]
|
||||||
|
Host1: connecting and login on host1 [127.0.0.1] port [1143] with user [tj@jongsma.me]
|
||||||
|
Host1 IP address: 127.0.0.1 Local IP address: 127.0.0.1
|
||||||
|
Host1 banner: * OK [CAPABILITY AUTH=PLAIN ID IDLE IMAP4rev1 MOVE STARTTLS UIDPLUS UNSELECT] Proton Mail Bridge 03.21.02 - gluon session ID 1868
|
||||||
|
Host1 capability before authentication: AUTH=PLAIN ID IDLE IMAP4rev1 STARTTLS AUTH
|
||||||
|
Host1: Socket successfully converted to SSL
|
||||||
|
Host1: success login on [127.0.0.1] with user [tj@jongsma.me] auth [LOGIN] or [LOGIN]
|
||||||
|
Host2: connecting and login on host2 [127.0.0.1] port [9993] with user [tj]
|
||||||
|
DEBUG: ...base/IO/Socket.pm:49: local error: IO::Socket::IP configuration failed
|
||||||
|
Host2 failure: can not open imap connection on host2 [127.0.0.1] with user [tj]: Unable to connect to 127.0.0.1: IO::Socket::IP configuration failed Connection refused
|
||||||
|
++++ Listing 1 errors encountered during the sync ( avoid this listing with --noerrorsdump ).
|
||||||
|
Err 1/1: Host2 failure: can not open imap connection on host2 [127.0.0.1] with user [tj]: Unable to connect to 127.0.0.1: IO::Socket::IP configuration failed Connection refused
|
||||||
|
The most frequent error is ERR_CONNECTION_FAILURE_HOST2. Check that host1 127.0.0.1 on port 9993 is the right IMAP server to be contacted for your mailbox.
|
||||||
|
Exiting with return value 102 (EXIT_CONNECTION_FAILURE_HOST2) 1/50 nb_errors/max_errors PID 710983
|
||||||
|
Removing pidfile /tmp/imapsync.pid
|
||||||
|
Disconnecting from host1 127.0.0.1 user1 tj@jongsma.me
|
||||||
|
Log file is LOG_imapsync/2026_02_18_10_42_40_710_tj@jongsma.me_tj.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging )
|
||||||
File diff suppressed because it is too large
Load Diff
9
TOOLS.md
9
TOOLS.md
|
|
@ -287,6 +287,15 @@ scripts/browser-setup.sh stop # Stop all
|
||||||
|
|
||||||
**Webhook:** POSTs `{"event": "new"}` to `http://localhost:18789/hooks/messages`
|
**Webhook:** POSTs `{"event": "new"}` to `http://localhost:18789/hooks/messages`
|
||||||
|
|
||||||
|
### Telegram Bot (@jamesjongsma_bot)
|
||||||
|
- **Bot:** @jamesjongsma_bot → t.me/jamesjongsma_bot
|
||||||
|
- **Bot ID:** 8510971070
|
||||||
|
- **Token:** `8510971070:AAFFgv_UO_9L0Ulp2DRKHD-IWKkrarJNTIc`
|
||||||
|
- **Owner account:** @johanjongsma (Telegram ID: 8454563068)
|
||||||
|
- **Channel config:** `channels.telegram` in openclaw.json, `dmPolicy: open`
|
||||||
|
- **Status:** Live as of 2026-02-18
|
||||||
|
- **Note:** Created via BotFather from Johan's @johanjongsma account
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
- `/screenshot` → Pull latest screenshot from Mac desktop (uses screenshot skill)
|
- `/screenshot` → Pull latest screenshot from Mac desktop (uses screenshot skill)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"last_updated": "2026-02-18T11:00:10.022130Z",
|
"last_updated": "2026-02-18T16:29:21.574823Z",
|
||||||
"source": "api",
|
"source": "api",
|
||||||
"session_percent": 2,
|
"session_percent": 18,
|
||||||
"session_resets": "2026-02-18T14:59:59.984844+00:00",
|
"session_resets": "2026-02-18T20:00:00.540649+00:00",
|
||||||
"weekly_percent": 59,
|
"weekly_percent": 61,
|
||||||
"weekly_resets": "2026-02-21T18:59:59.984863+00:00",
|
"weekly_resets": "2026-02-21T19:00:00.540673+00:00",
|
||||||
"sonnet_percent": 16
|
"sonnet_percent": 19
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
{
|
||||||
|
"date": "2026-02-18",
|
||||||
|
"timestamp": "2026-02-18T09:00:00-05:00",
|
||||||
|
"openclaw": {
|
||||||
|
"before": "2026.2.15",
|
||||||
|
"latest": "2026.2.17",
|
||||||
|
"after": "2026.2.17",
|
||||||
|
"updated": true
|
||||||
|
},
|
||||||
|
"claude_code": {
|
||||||
|
"before": "2.1.45",
|
||||||
|
"latest": "2.1.45",
|
||||||
|
"updated": false
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"available": 16,
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "cloud-init",
|
||||||
|
"from": "25.2-0ubuntu1~24.04.1",
|
||||||
|
"to": "25.3-0ubuntu1~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gcc-14-base",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libasan8",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libatomic1",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libcc1-0",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libgcc-s1",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libgfortran5",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libgomp1",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libhwasan0",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libitm1",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "liblsan0",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libquadmath0",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libstdc++6",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libtsan2",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libubsan1",
|
||||||
|
"from": "14.2.0-4ubuntu2~24.04",
|
||||||
|
"to": "14.2.0-4ubuntu2~24.04.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemd-hwe-hwdb",
|
||||||
|
"from": "255.1.6",
|
||||||
|
"to": "255.1.7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": true,
|
||||||
|
"reboot_required": true
|
||||||
|
},
|
||||||
|
"gateway_restarted": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Proton Bridge → Stalwart IMAP migration
|
||||||
|
"""
|
||||||
|
import imaplib, ssl, email, time, sys, traceback
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
SRC_HOST = '127.0.0.1'; SRC_PORT = 1143
|
||||||
|
SRC_USER = 'tj@jongsma.me'; SRC_PASS = 'BlcMCKtNDfqv0cq1LmGR9g'
|
||||||
|
DST_HOST = 'mail.jongsma.me'; DST_PORT = 993
|
||||||
|
DST_USER = 'tj'; DST_PASS = 'TJ-Migrate-2026!'
|
||||||
|
|
||||||
|
SKIP_FOLDERS = {'All Mail', 'Starred', 'Labels/Sent Messages',
|
||||||
|
'Labels/Deleted Messages', 'Labels/Snoozed'}
|
||||||
|
SKIP_PREFIXES = ('Labels/',)
|
||||||
|
|
||||||
|
BATCH = 10 # smaller batch = more reliable with Proton Bridge
|
||||||
|
|
||||||
|
def src_ctx():
|
||||||
|
c = ssl.create_default_context()
|
||||||
|
c.check_hostname = False; c.verify_mode = ssl.CERT_NONE
|
||||||
|
return c
|
||||||
|
|
||||||
|
def connect_src():
|
||||||
|
s = imaplib.IMAP4(SRC_HOST, SRC_PORT)
|
||||||
|
s.starttls(src_ctx()); s.login(SRC_USER, SRC_PASS)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def connect_dst():
|
||||||
|
s = imaplib.IMAP4_SSL(DST_HOST, DST_PORT, ssl_context=ssl.create_default_context())
|
||||||
|
s.login(DST_USER, DST_PASS)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def ensure_dst_folder(dst, folder):
|
||||||
|
st, _ = dst.select(folder)
|
||||||
|
if st == 'OK': return
|
||||||
|
dst.create(folder)
|
||||||
|
dst.select(folder)
|
||||||
|
|
||||||
|
def get_dst_message_ids(dst, folder):
|
||||||
|
ids = set()
|
||||||
|
st, _ = dst.select(folder)
|
||||||
|
if st != 'OK': return ids
|
||||||
|
st2, data = dst.search(None, 'ALL')
|
||||||
|
if st2 != 'OK' or not data[0]: return ids
|
||||||
|
msgs = data[0].split()
|
||||||
|
for i in range(0, len(msgs), 200):
|
||||||
|
chunk = b','.join(msgs[i:i+200])
|
||||||
|
try:
|
||||||
|
st3, hd = dst.fetch(chunk, '(BODY[HEADER.FIELDS (MESSAGE-ID)])')
|
||||||
|
for item in hd:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
raw = item[1].decode('utf-8', errors='replace')
|
||||||
|
for line in raw.splitlines():
|
||||||
|
if line.lower().startswith('message-id:'):
|
||||||
|
ids.add(line.split(':',1)[1].strip())
|
||||||
|
except: pass
|
||||||
|
return ids
|
||||||
|
|
||||||
|
def sync_folder(src, dst, folder):
|
||||||
|
copied = skipped = errors = 0
|
||||||
|
src_q = f'"{folder}"' if ' ' in folder or '/' in folder else folder
|
||||||
|
|
||||||
|
st, _ = src.select(src_q, readonly=True)
|
||||||
|
if st != 'OK':
|
||||||
|
print(f' ✗ Cannot select src: {folder}'); return 0, 0, 1
|
||||||
|
|
||||||
|
st2, data = src.search(None, 'ALL')
|
||||||
|
if st2 != 'OK' or not data[0]: return 0, 0, 0
|
||||||
|
msg_ids = data[0].split()
|
||||||
|
src_count = len(msg_ids)
|
||||||
|
|
||||||
|
ensure_dst_folder(dst, folder)
|
||||||
|
existing_ids = get_dst_message_ids(dst, folder)
|
||||||
|
dst.select(folder)
|
||||||
|
# Re-select source after dst operations (connection may have gone idle)
|
||||||
|
src.select(src_q, readonly=True)
|
||||||
|
|
||||||
|
for i in range(0, len(msg_ids), BATCH):
|
||||||
|
chunk = msg_ids[i:i+BATCH]
|
||||||
|
chunk_str = b','.join(chunk)
|
||||||
|
try:
|
||||||
|
st3, msg_data = src.fetch(chunk_str, '(INTERNALDATE FLAGS RFC822)')
|
||||||
|
except Exception as e:
|
||||||
|
errors += len(chunk)
|
||||||
|
# Reconnect source
|
||||||
|
try: src = connect_src(); src.select(src_q, readonly=True)
|
||||||
|
except: pass
|
||||||
|
continue
|
||||||
|
|
||||||
|
for item in msg_data:
|
||||||
|
if not isinstance(item, tuple): continue
|
||||||
|
try:
|
||||||
|
meta = item[0].decode('utf-8', errors='replace')
|
||||||
|
raw_msg = item[1]
|
||||||
|
|
||||||
|
# Stalwart v0.15.5 rejects \Flag syntax in APPEND — skip flags
|
||||||
|
append_flags = None
|
||||||
|
|
||||||
|
# Duplicate check by Message-ID
|
||||||
|
parsed = email.message_from_bytes(raw_msg)
|
||||||
|
mid = parsed.get('Message-ID', '').strip()
|
||||||
|
if mid and mid in existing_ids:
|
||||||
|
skipped += 1; continue
|
||||||
|
|
||||||
|
# Append
|
||||||
|
dst.append(folder, append_flags, None, raw_msg)
|
||||||
|
if mid: existing_ids.add(mid)
|
||||||
|
copied += 1
|
||||||
|
except Exception as e:
|
||||||
|
errors += 1
|
||||||
|
if errors <= 3:
|
||||||
|
sys.stdout.write(f'\n ERR: {e}\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
done = min(i+BATCH, src_count)
|
||||||
|
sys.stdout.write(f'\r {done}/{src_count} | ✓{copied} ⏭{skipped} ✗{errors} ')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
print(f'\r {src_count} src | ✓{copied} ⏭{skipped} ✗{errors} ')
|
||||||
|
return copied, skipped, errors
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print('=== Proton → Stalwart Migration ===')
|
||||||
|
print(f'Started: {datetime.now():%Y-%m-%d %H:%M:%S}')
|
||||||
|
src = connect_src(); dst = connect_dst()
|
||||||
|
|
||||||
|
_, folders_raw = src.list()
|
||||||
|
folders = []
|
||||||
|
for f in folders_raw:
|
||||||
|
d = f.decode()
|
||||||
|
if 'Noselect' in d: continue
|
||||||
|
parts = d.split('" "')
|
||||||
|
if len(parts) < 2: continue
|
||||||
|
folder = parts[-1].strip().strip('"')
|
||||||
|
if folder in SKIP_FOLDERS: continue
|
||||||
|
if any(folder.startswith(p) for p in SKIP_PREFIXES): continue
|
||||||
|
folders.append(folder)
|
||||||
|
|
||||||
|
print(f'Syncing {len(folders)} folders\n')
|
||||||
|
results = []; t0 = time.time()
|
||||||
|
total_c = total_s = total_e = 0
|
||||||
|
|
||||||
|
for folder in folders:
|
||||||
|
print(f'[{folder}]')
|
||||||
|
try:
|
||||||
|
c, s, e = sync_folder(src, dst, folder)
|
||||||
|
except Exception as ex:
|
||||||
|
print(f' ✗ Fatal: {ex}')
|
||||||
|
c, s, e = 0, 0, 1
|
||||||
|
try: src = connect_src(); dst = connect_dst()
|
||||||
|
except: pass
|
||||||
|
results.append((folder, c, s, e))
|
||||||
|
total_c += c; total_s += s; total_e += e
|
||||||
|
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
print('\n' + '='*55)
|
||||||
|
print('MIGRATION REPORT')
|
||||||
|
print('='*55)
|
||||||
|
print(f'Completed: {datetime.now():%Y-%m-%d %H:%M:%S}')
|
||||||
|
print(f'Duration: {int(elapsed//60)}m {int(elapsed%60)}s')
|
||||||
|
print(f'Folders: {len(folders)}')
|
||||||
|
print(f'Copied: {total_c}')
|
||||||
|
print(f'Skipped: {total_s} (duplicates)')
|
||||||
|
print(f'Errors: {total_e}')
|
||||||
|
print()
|
||||||
|
print(f' {"Folder":<45} {"Copied":>7} {"Dup":>5} {"Err":>5}')
|
||||||
|
print(f' {"-"*45} {"-"*7} {"-"*5} {"-"*5}')
|
||||||
|
for folder, c, s, e in results:
|
||||||
|
mark = ' ⚠' if e > 0 else ''
|
||||||
|
print(f' {folder:<45} {c:>7} {s:>5} {e:>5}{mark}')
|
||||||
|
src.logout(); dst.logout()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
IMAPAccount proton
|
||||||
|
Host 127.0.0.1
|
||||||
|
Port 1143
|
||||||
|
User tj@jongsma.me
|
||||||
|
Pass BlcMCKtNDfqv0cq1LmGR9g
|
||||||
|
SSLType STARTTLS
|
||||||
|
AuthMechs PLAIN
|
||||||
|
|
||||||
|
IMAPAccount stalwart
|
||||||
|
Host mail.jongsma.me
|
||||||
|
Port 993
|
||||||
|
User tj
|
||||||
|
Pass TJ-Migrate-2026!
|
||||||
|
SSLType IMAPS
|
||||||
|
|
||||||
|
IMAPStore proton-remote
|
||||||
|
Account proton
|
||||||
|
|
||||||
|
IMAPStore stalwart-remote
|
||||||
|
Account stalwart
|
||||||
|
|
||||||
|
Channel proton-to-stalwart
|
||||||
|
Far :proton-remote:
|
||||||
|
Near :stalwart-remote:
|
||||||
|
Patterns * !"All Mail" !"Labels/*" !"Starred" !"Labels/Sent Messages" !"Labels/Deleted Messages" !"Labels/Snoozed"
|
||||||
|
Create Near
|
||||||
|
CopyArrivalDate yes
|
||||||
|
SyncState *
|
||||||
|
Expunge None
|
||||||
|
Sync Pull
|
||||||
Loading…
Reference in New Issue