heathrow 0.7.0
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.
- checksums.yaml +7 -0
- data/.gitignore +58 -0
- data/README.md +205 -0
- data/bin/heathrow +42 -0
- data/bin/heathrowd +283 -0
- data/docs/ARCHITECTURE.md +1172 -0
- data/docs/DATABASE_SCHEMA.md +685 -0
- data/docs/DEVELOPMENT_WORKFLOW.md +867 -0
- data/docs/DISCORD_SETUP.md +142 -0
- data/docs/GMAIL_OAUTH_SETUP.md +120 -0
- data/docs/PLUGIN_SYSTEM.md +1370 -0
- data/docs/PROJECT_PLAN.md +1022 -0
- data/docs/README.md +417 -0
- data/docs/REDDIT_SETUP.md +174 -0
- data/docs/REPLY_FORWARD.md +182 -0
- data/docs/WHATSAPP_TELEGRAM_SETUP.md +306 -0
- data/heathrow.gemspec +34 -0
- data/heathrowd.service +21 -0
- data/img/heathrow.svg +95 -0
- data/img/rss_threaded.png +0 -0
- data/img/sources.png +0 -0
- data/lib/heathrow/address_book.rb +42 -0
- data/lib/heathrow/config.rb +332 -0
- data/lib/heathrow/database.rb +731 -0
- data/lib/heathrow/database_new.rb +392 -0
- data/lib/heathrow/event_bus.rb +175 -0
- data/lib/heathrow/logger.rb +122 -0
- data/lib/heathrow/message.rb +176 -0
- data/lib/heathrow/message_composer.rb +399 -0
- data/lib/heathrow/message_organizer.rb +774 -0
- data/lib/heathrow/migrations/001_initial_schema.rb +248 -0
- data/lib/heathrow/notmuch.rb +45 -0
- data/lib/heathrow/oauth2_smtp.rb +254 -0
- data/lib/heathrow/plugin/base.rb +212 -0
- data/lib/heathrow/plugin_manager.rb +141 -0
- data/lib/heathrow/poller.rb +93 -0
- data/lib/heathrow/smtp_sender.rb +204 -0
- data/lib/heathrow/source.rb +39 -0
- data/lib/heathrow/sources/base.rb +74 -0
- data/lib/heathrow/sources/discord.rb +357 -0
- data/lib/heathrow/sources/gmail.rb +294 -0
- data/lib/heathrow/sources/imap.rb +198 -0
- data/lib/heathrow/sources/instagram.rb +307 -0
- data/lib/heathrow/sources/instagram_fetch.py +101 -0
- data/lib/heathrow/sources/instagram_send.py +55 -0
- data/lib/heathrow/sources/instagram_send_marionette.py +104 -0
- data/lib/heathrow/sources/maildir.rb +606 -0
- data/lib/heathrow/sources/messenger.rb +212 -0
- data/lib/heathrow/sources/messenger_fetch.js +297 -0
- data/lib/heathrow/sources/messenger_fetch_marionette.py +138 -0
- data/lib/heathrow/sources/messenger_send.js +32 -0
- data/lib/heathrow/sources/messenger_send.py +100 -0
- data/lib/heathrow/sources/reddit.rb +461 -0
- data/lib/heathrow/sources/rss.rb +299 -0
- data/lib/heathrow/sources/slack.rb +375 -0
- data/lib/heathrow/sources/source_manager.rb +328 -0
- data/lib/heathrow/sources/telegram.rb +498 -0
- data/lib/heathrow/sources/webpage.rb +207 -0
- data/lib/heathrow/sources/weechat.rb +479 -0
- data/lib/heathrow/sources/whatsapp.rb +474 -0
- data/lib/heathrow/ui/application.rb +8098 -0
- data/lib/heathrow/ui/navigation.rb +8 -0
- data/lib/heathrow/ui/panes.rb +8 -0
- data/lib/heathrow/ui/source_wizard.rb +567 -0
- data/lib/heathrow/ui/threaded_view.rb +780 -0
- data/lib/heathrow/ui/views.rb +8 -0
- data/lib/heathrow/version.rb +3 -0
- data/lib/heathrow/wizards/discord_wizard.rb +193 -0
- data/lib/heathrow/wizards/slack_wizard.rb +140 -0
- data/lib/heathrow.rb +55 -0
- metadata +147 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Reply, Forward, and Compose in Heathrow
|
|
2
|
+
|
|
3
|
+
Heathrow provides full email-style reply, forward, and compose functionality using your system's $EDITOR, similar to mutt.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Reply** (`r`) - Reply to the sender
|
|
8
|
+
- **Reply All** (`R`) - Reply to sender and all recipients
|
|
9
|
+
- **Forward** (`f`) - Forward message to new recipients
|
|
10
|
+
- **Compose** (`m`) - Create new message
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
1. Press the appropriate key while viewing a message
|
|
15
|
+
2. Your $EDITOR (default: vim) opens with a pre-formatted template
|
|
16
|
+
3. Edit the message, keeping the To: and Subject: headers
|
|
17
|
+
4. Save and exit to send (`:wq` in vim)
|
|
18
|
+
5. Exit without saving to cancel
|
|
19
|
+
|
|
20
|
+
## SMTP Configuration
|
|
21
|
+
|
|
22
|
+
Heathrow supports OAuth2 SMTP for Gmail and custom domains. Configure your domains in the `OAUTH2_DOMAINS` list inside `smtp_sender.rb`:
|
|
23
|
+
- gmail.com
|
|
24
|
+
- yourdomain.com (add your own)
|
|
25
|
+
|
|
26
|
+
The SmtpSender module (`lib/heathrow/smtp_sender.rb`) automatically detects configured domains and routes mail through your OAuth2 SMTP command (configurable in settings).
|
|
27
|
+
|
|
28
|
+
### OAuth2 Setup
|
|
29
|
+
|
|
30
|
+
Your OAuth2 credentials are stored in:
|
|
31
|
+
- `~/.heathrow/mail/[email].json` - Client credentials
|
|
32
|
+
- `~/.heathrow/mail/[email].txt` - Refresh token
|
|
33
|
+
|
|
34
|
+
The SMTP script handles token refresh automatically.
|
|
35
|
+
|
|
36
|
+
### Other Email Accounts
|
|
37
|
+
|
|
38
|
+
For non-OAuth2 accounts, configure SMTP settings when adding the source:
|
|
39
|
+
- SMTP server address
|
|
40
|
+
- SMTP port (usually 587 for TLS, 25 for plain)
|
|
41
|
+
- Username and password
|
|
42
|
+
|
|
43
|
+
## Message Templates
|
|
44
|
+
|
|
45
|
+
### Reply Template
|
|
46
|
+
```
|
|
47
|
+
To: original-sender@example.com
|
|
48
|
+
Subject: Re: Original Subject
|
|
49
|
+
# Lines starting with # will be ignored
|
|
50
|
+
# Write your message below this line
|
|
51
|
+
#==================================================
|
|
52
|
+
|
|
53
|
+
[Your reply here]
|
|
54
|
+
|
|
55
|
+
On 2025-08-25, sender wrote:
|
|
56
|
+
> Original message quoted
|
|
57
|
+
> with > prefix
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Reply All Template
|
|
61
|
+
```
|
|
62
|
+
To: sender@example.com, recipient1@example.com, recipient2@example.com
|
|
63
|
+
Subject: Re: Original Subject
|
|
64
|
+
[Rest same as reply]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Forward Template
|
|
68
|
+
```
|
|
69
|
+
To: [Enter recipients]
|
|
70
|
+
Subject: Fwd: Original Subject
|
|
71
|
+
# Lines starting with # will be ignored
|
|
72
|
+
# Enter recipients and write your message
|
|
73
|
+
#==================================================
|
|
74
|
+
|
|
75
|
+
[Your introduction]
|
|
76
|
+
|
|
77
|
+
---------- Forwarded message ----------
|
|
78
|
+
From: original-sender@example.com
|
|
79
|
+
Date: 2025-08-25
|
|
80
|
+
Subject: Original Subject
|
|
81
|
+
|
|
82
|
+
[Original message content]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Supported Sources
|
|
86
|
+
|
|
87
|
+
Sources with reply capability:
|
|
88
|
+
- **Gmail** - Full OAuth2 support via configurable SMTP command
|
|
89
|
+
- **IMAP** - Uses OAuth2 SMTP for supported domains or configured SMTP
|
|
90
|
+
- **Discord** - Bot/user messages to channels
|
|
91
|
+
- **Telegram** - Bot messages to chats
|
|
92
|
+
|
|
93
|
+
## Key Bindings
|
|
94
|
+
|
|
95
|
+
| Key | Action | Description |
|
|
96
|
+
|-----|--------|-------------|
|
|
97
|
+
| `r` | Reply | Reply to sender only |
|
|
98
|
+
| `R` | Reply All | Reply to all recipients |
|
|
99
|
+
| `f` | Forward | Forward to new recipients |
|
|
100
|
+
| `m` | Mail/Compose | Create new message |
|
|
101
|
+
| `Ctrl-R` | Refresh | Refresh all panes |
|
|
102
|
+
|
|
103
|
+
## Editor Tips
|
|
104
|
+
|
|
105
|
+
### Vim Configuration
|
|
106
|
+
Your vim is configured with:
|
|
107
|
+
```vim
|
|
108
|
+
setlocal tw=0 fo-=tcal ff=unix
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This ensures:
|
|
112
|
+
- No automatic text wrapping
|
|
113
|
+
- Unix line endings
|
|
114
|
+
- Proper email formatting
|
|
115
|
+
|
|
116
|
+
### Canceling a Message
|
|
117
|
+
- Exit without saving: `:q!` in vim
|
|
118
|
+
- Or make no changes and save (detected as cancel)
|
|
119
|
+
|
|
120
|
+
## Troubleshooting
|
|
121
|
+
|
|
122
|
+
### Message Not Sending
|
|
123
|
+
|
|
124
|
+
1. Check `~/.heathrow/mail/.smtp.log` for errors
|
|
125
|
+
2. Verify OAuth2 credentials exist for your email
|
|
126
|
+
3. For new domains, add to `OAUTH2_DOMAINS` in `smtp_sender.rb`
|
|
127
|
+
|
|
128
|
+
### OAuth2 Token Issues
|
|
129
|
+
|
|
130
|
+
If you see token errors:
|
|
131
|
+
```bash
|
|
132
|
+
# Regenerate token
|
|
133
|
+
oauth2.py --generate_oauth2_token \
|
|
134
|
+
--client_id=[your-client-id] \
|
|
135
|
+
--client_secret=[your-secret] \
|
|
136
|
+
--refresh_token=[your-refresh-token]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### SMTP Server Issues
|
|
140
|
+
|
|
141
|
+
For non-OAuth2 accounts, verify:
|
|
142
|
+
- SMTP server address is correct
|
|
143
|
+
- Port is appropriate (587 for TLS, 25 for plain)
|
|
144
|
+
- Username/password are correct
|
|
145
|
+
- Firewall allows outgoing SMTP
|
|
146
|
+
|
|
147
|
+
## Testing
|
|
148
|
+
|
|
149
|
+
Test the SMTP module:
|
|
150
|
+
```bash
|
|
151
|
+
ruby test_smtp_module.rb
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Test sending from a specific source:
|
|
155
|
+
```bash
|
|
156
|
+
ruby test_gmail_send.rb
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Implementation Details
|
|
160
|
+
|
|
161
|
+
The reply/forward system consists of:
|
|
162
|
+
|
|
163
|
+
1. **MessageComposer** (`lib/heathrow/message_composer.rb`)
|
|
164
|
+
- Creates templates
|
|
165
|
+
- Launches editor
|
|
166
|
+
- Parses composed messages
|
|
167
|
+
|
|
168
|
+
2. **SmtpSender** (`lib/heathrow/smtp_sender.rb`)
|
|
169
|
+
- Routes to OAuth2 SMTP for configured domains
|
|
170
|
+
- Falls back to standard SMTP for others
|
|
171
|
+
- Handles error reporting
|
|
172
|
+
|
|
173
|
+
3. **Source Integration**
|
|
174
|
+
- Each source implements `can_reply?` and `send_message`
|
|
175
|
+
- Threading headers (In-Reply-To, References) preserved
|
|
176
|
+
|
|
177
|
+
## Security Notes
|
|
178
|
+
|
|
179
|
+
- OAuth2 tokens stored in `~/.heathrow/mail/`
|
|
180
|
+
- No passwords stored in Heathrow database for OAuth2 accounts
|
|
181
|
+
- Temporary message files deleted after sending
|
|
182
|
+
- SMTP passwords encrypted in source config
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# WhatsApp and Telegram Setup Guide for Heathrow
|
|
2
|
+
|
|
3
|
+
This guide helps you set up WhatsApp and Telegram sources in Heathrow to receive messages from these platforms.
|
|
4
|
+
|
|
5
|
+
## WhatsApp Setup
|
|
6
|
+
|
|
7
|
+
WhatsApp integration requires a separate API server that implements the WhatsApp Web protocol.
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
|
|
11
|
+
1. **Go Language** - Required for the WhatsApp API server
|
|
12
|
+
```bash
|
|
13
|
+
sudo apt install golang-go
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. **WhatsApp API Server** - A REST API wrapper for WhatsApp Web
|
|
17
|
+
- The server needs to be running before setting up the source
|
|
18
|
+
- Default port: 8080
|
|
19
|
+
|
|
20
|
+
### Quick Setup
|
|
21
|
+
|
|
22
|
+
1. Start the WhatsApp API server (in a separate terminal):
|
|
23
|
+
```bash
|
|
24
|
+
# Clone and run the server
|
|
25
|
+
git clone [whatsmeow-api-repo]
|
|
26
|
+
cd whatsmeow-api
|
|
27
|
+
go run main.go
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Run the WhatsApp setup script:
|
|
31
|
+
```bash
|
|
32
|
+
ruby setup_whatsapp.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Choose authentication method:
|
|
36
|
+
- **QR Code** (default): Scan with WhatsApp mobile app
|
|
37
|
+
- **Pairing Code**: Enter code in WhatsApp settings
|
|
38
|
+
|
|
39
|
+
### Authentication Methods
|
|
40
|
+
|
|
41
|
+
#### QR Code Method
|
|
42
|
+
1. Open WhatsApp on your phone
|
|
43
|
+
2. Go to Settings → Linked Devices
|
|
44
|
+
3. Tap "Link a Device"
|
|
45
|
+
4. Scan the QR code displayed in terminal
|
|
46
|
+
|
|
47
|
+
#### Pairing Code Method
|
|
48
|
+
1. Provide your phone number during setup
|
|
49
|
+
2. Receive an 8-digit pairing code
|
|
50
|
+
3. In WhatsApp: Settings → Linked Devices → Link with phone number
|
|
51
|
+
4. Enter the pairing code
|
|
52
|
+
|
|
53
|
+
### Configuration
|
|
54
|
+
|
|
55
|
+
The WhatsApp source supports these settings:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
{
|
|
59
|
+
api_url: 'http://localhost:8080', # API server URL
|
|
60
|
+
device_id: 'unique_device_id', # Device identifier
|
|
61
|
+
fetch_limit: 50, # Messages per fetch
|
|
62
|
+
incremental_sync: true, # Only fetch new messages
|
|
63
|
+
use_pairing_code: false, # Use QR code by default
|
|
64
|
+
phone_number: '1234567890', # For pairing code only
|
|
65
|
+
polling_interval: 60 # Check every minute
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Message Types Supported
|
|
70
|
+
|
|
71
|
+
- Text messages
|
|
72
|
+
- Images, videos, audio
|
|
73
|
+
- Documents and files
|
|
74
|
+
- Location sharing
|
|
75
|
+
- Contact cards
|
|
76
|
+
- Stickers
|
|
77
|
+
- Group messages
|
|
78
|
+
|
|
79
|
+
## Telegram Setup
|
|
80
|
+
|
|
81
|
+
Telegram offers two integration methods: Bot API (simple) or User Account (full access).
|
|
82
|
+
|
|
83
|
+
### Method 1: Telegram Bot (Recommended for Start)
|
|
84
|
+
|
|
85
|
+
#### Creating a Bot
|
|
86
|
+
|
|
87
|
+
1. Open Telegram and search for **@BotFather**
|
|
88
|
+
2. Send `/newbot` command
|
|
89
|
+
3. Choose a name for your bot
|
|
90
|
+
4. Choose a username (must end in 'bot')
|
|
91
|
+
5. Copy the bot token provided
|
|
92
|
+
|
|
93
|
+
#### Setup
|
|
94
|
+
|
|
95
|
+
Run the Telegram setup script:
|
|
96
|
+
```bash
|
|
97
|
+
ruby setup_telegram.rb
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Choose option 1 (Bot) and enter your bot token.
|
|
101
|
+
|
|
102
|
+
#### Bot Limitations
|
|
103
|
+
|
|
104
|
+
- Only receives messages sent directly to the bot
|
|
105
|
+
- Cannot access your personal chats
|
|
106
|
+
- Users must start conversation with `/start`
|
|
107
|
+
- Cannot see messages in groups unless mentioned
|
|
108
|
+
|
|
109
|
+
### Method 2: User Account (Full Access)
|
|
110
|
+
|
|
111
|
+
#### Prerequisites
|
|
112
|
+
|
|
113
|
+
1. Get API credentials from https://my.telegram.org:
|
|
114
|
+
- Log in with your phone number
|
|
115
|
+
- Go to "API development tools"
|
|
116
|
+
- Create an app (if needed)
|
|
117
|
+
- Copy the **api_id** and **api_hash**
|
|
118
|
+
|
|
119
|
+
2. For full functionality, you need an MTProto proxy server (advanced)
|
|
120
|
+
|
|
121
|
+
#### Setup
|
|
122
|
+
|
|
123
|
+
Run the setup script and choose option 2:
|
|
124
|
+
```bash
|
|
125
|
+
ruby setup_telegram.rb
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Provide:
|
|
129
|
+
- API ID
|
|
130
|
+
- API Hash
|
|
131
|
+
- Phone number (with country code)
|
|
132
|
+
|
|
133
|
+
#### Authentication Process
|
|
134
|
+
|
|
135
|
+
1. You'll receive a code in your Telegram app
|
|
136
|
+
2. Enter the code when prompted
|
|
137
|
+
3. If you have 2FA enabled, enter your password
|
|
138
|
+
4. Session is saved for future use
|
|
139
|
+
|
|
140
|
+
### Configuration Options
|
|
141
|
+
|
|
142
|
+
#### Bot Configuration
|
|
143
|
+
```ruby
|
|
144
|
+
{
|
|
145
|
+
bot_token: 'YOUR_BOT_TOKEN',
|
|
146
|
+
fetch_limit: 100,
|
|
147
|
+
polling_interval: 60
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### User Account Configuration
|
|
152
|
+
```ruby
|
|
153
|
+
{
|
|
154
|
+
api_id: 'YOUR_API_ID',
|
|
155
|
+
api_hash: 'YOUR_API_HASH',
|
|
156
|
+
phone_number: '+1234567890',
|
|
157
|
+
session_string: 'saved_after_auth',
|
|
158
|
+
mtproto_api_url: 'http://localhost:8081'
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Message Types Supported
|
|
163
|
+
|
|
164
|
+
- Text messages
|
|
165
|
+
- Photos and videos
|
|
166
|
+
- Voice messages and audio
|
|
167
|
+
- Documents and files
|
|
168
|
+
- Stickers and GIFs
|
|
169
|
+
- Location sharing
|
|
170
|
+
- Contact cards
|
|
171
|
+
- Channel posts
|
|
172
|
+
- Group messages
|
|
173
|
+
|
|
174
|
+
## Testing the Sources
|
|
175
|
+
|
|
176
|
+
After setup, test your sources:
|
|
177
|
+
|
|
178
|
+
1. In Heathrow, press `s` to view sources
|
|
179
|
+
2. Navigate to WhatsApp or Telegram source
|
|
180
|
+
3. Press `t` to test connection
|
|
181
|
+
4. Check for success message
|
|
182
|
+
|
|
183
|
+
## Troubleshooting
|
|
184
|
+
|
|
185
|
+
### WhatsApp Issues
|
|
186
|
+
|
|
187
|
+
**"API server not running"**
|
|
188
|
+
- Ensure the WhatsApp API server is running on port 8080
|
|
189
|
+
- Check with: `curl http://localhost:8080/health`
|
|
190
|
+
|
|
191
|
+
**"Device not authenticated"**
|
|
192
|
+
- Re-run the setup script
|
|
193
|
+
- Try QR code method if pairing code fails
|
|
194
|
+
- Ensure your phone has internet connection
|
|
195
|
+
|
|
196
|
+
**"No messages fetched"**
|
|
197
|
+
- WhatsApp Web must stay connected
|
|
198
|
+
- Check phone's internet connection
|
|
199
|
+
- Verify in WhatsApp: Settings → Linked Devices
|
|
200
|
+
|
|
201
|
+
### Telegram Issues
|
|
202
|
+
|
|
203
|
+
**Bot not receiving messages**
|
|
204
|
+
- Users must start conversation with `/start`
|
|
205
|
+
- In groups, bot needs to be admin or mentioned
|
|
206
|
+
- Check bot token is correct
|
|
207
|
+
|
|
208
|
+
**User account authentication fails**
|
|
209
|
+
- Verify api_id and api_hash are correct
|
|
210
|
+
- Phone number must include country code
|
|
211
|
+
- Code expires after 5 minutes
|
|
212
|
+
- 2FA password is case-sensitive
|
|
213
|
+
|
|
214
|
+
**"Session expired"**
|
|
215
|
+
- Re-run authentication process
|
|
216
|
+
- Session strings expire after long inactivity
|
|
217
|
+
|
|
218
|
+
## Security Notes
|
|
219
|
+
|
|
220
|
+
### WhatsApp
|
|
221
|
+
- Messages are end-to-end encrypted
|
|
222
|
+
- Device sessions persist until manually disconnected
|
|
223
|
+
- Each device has unique encryption keys
|
|
224
|
+
- Disconnect unused devices from WhatsApp settings
|
|
225
|
+
|
|
226
|
+
### Telegram
|
|
227
|
+
- Bot tokens should be kept secret
|
|
228
|
+
- API credentials are sensitive - don't share
|
|
229
|
+
- Session strings grant full account access
|
|
230
|
+
- Use environment variables for credentials in production
|
|
231
|
+
|
|
232
|
+
## Advanced Configuration
|
|
233
|
+
|
|
234
|
+
### Running API Servers as Services
|
|
235
|
+
|
|
236
|
+
#### WhatsApp API Service
|
|
237
|
+
Create `/etc/systemd/system/whatsmeow-api.service`:
|
|
238
|
+
```ini
|
|
239
|
+
[Unit]
|
|
240
|
+
Description=WhatsApp API Server
|
|
241
|
+
After=network.target
|
|
242
|
+
|
|
243
|
+
[Service]
|
|
244
|
+
Type=simple
|
|
245
|
+
User=your-user
|
|
246
|
+
WorkingDirectory=/path/to/whatsmeow-api
|
|
247
|
+
ExecStart=/usr/bin/go run main.go
|
|
248
|
+
Restart=on-failure
|
|
249
|
+
|
|
250
|
+
[Install]
|
|
251
|
+
WantedBy=multi-user.target
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Start the service:
|
|
255
|
+
```bash
|
|
256
|
+
sudo systemctl enable whatsmeow-api
|
|
257
|
+
sudo systemctl start whatsmeow-api
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Multiple Accounts
|
|
261
|
+
|
|
262
|
+
You can add multiple WhatsApp or Telegram sources:
|
|
263
|
+
- Use different device_ids for WhatsApp
|
|
264
|
+
- Use different bot tokens or phone numbers for Telegram
|
|
265
|
+
- Each source syncs independently
|
|
266
|
+
|
|
267
|
+
### Filtering and Rules
|
|
268
|
+
|
|
269
|
+
Configure message filtering in Heathrow:
|
|
270
|
+
- Create views for specific contacts
|
|
271
|
+
- Filter by sender or group
|
|
272
|
+
- Set up notifications for important messages
|
|
273
|
+
|
|
274
|
+
## API Server Requirements
|
|
275
|
+
|
|
276
|
+
### WhatsApp (whatsmeow)
|
|
277
|
+
- Go 1.19 or higher
|
|
278
|
+
- SQLite for session storage
|
|
279
|
+
- ~50MB RAM per connected device
|
|
280
|
+
- Persistent storage for media cache
|
|
281
|
+
|
|
282
|
+
### Telegram (MTProto)
|
|
283
|
+
- Python 3.8+ or Go 1.19+
|
|
284
|
+
- Session storage (file or database)
|
|
285
|
+
- ~30MB RAM per session
|
|
286
|
+
- Optional media download storage
|
|
287
|
+
|
|
288
|
+
## Rate Limits
|
|
289
|
+
|
|
290
|
+
### WhatsApp
|
|
291
|
+
- No official rate limits for receiving
|
|
292
|
+
- Sending limited to prevent spam
|
|
293
|
+
- Reconnection backoff on disconnects
|
|
294
|
+
|
|
295
|
+
### Telegram
|
|
296
|
+
- Bot API: 30 messages/second
|
|
297
|
+
- MTProto: Adaptive rate limiting
|
|
298
|
+
- Respect flood wait errors
|
|
299
|
+
|
|
300
|
+
## Next Steps
|
|
301
|
+
|
|
302
|
+
1. Start Heathrow and verify sources appear
|
|
303
|
+
2. Monitor initial message sync
|
|
304
|
+
3. Configure polling intervals as needed
|
|
305
|
+
4. Set up views for different platforms
|
|
306
|
+
5. Consider running API servers as background services
|
data/heathrow.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require_relative 'lib/heathrow/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'heathrow'
|
|
5
|
+
spec.version = Heathrow::VERSION
|
|
6
|
+
spec.authors = ['Geir Isene', 'Claude Code']
|
|
7
|
+
spec.email = ['g@isene.com']
|
|
8
|
+
|
|
9
|
+
spec.summary = 'Communication Hub In The Terminal'
|
|
10
|
+
spec.description = 'A unified TUI application for managing all your communication sources in one place. Brings together emails, WhatsApp, Discord, Reddit, RSS feeds, and more into a single, efficient terminal interface.'
|
|
11
|
+
spec.homepage = 'https://github.com/isene/heathrow'
|
|
12
|
+
spec.license = 'Unlicense'
|
|
13
|
+
|
|
14
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/isene/heathrow'
|
|
18
|
+
spec.metadata['changelog_uri'] = 'https://github.com/isene/heathrow/blob/master/CHANGELOG.md'
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem
|
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
f.match(%r{\A(?:test|spec|features)/})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
spec.bindir = 'bin'
|
|
28
|
+
spec.executables = ['heathrow']
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
|
|
31
|
+
# Runtime dependencies - keep it simple!
|
|
32
|
+
spec.add_runtime_dependency 'rcurses', '>= 5.0'
|
|
33
|
+
spec.add_runtime_dependency 'sqlite3', '>= 1.4'
|
|
34
|
+
end
|
data/heathrowd.service
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Heathrow Daemon - Communication Hub Polling Service
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
User=%i
|
|
8
|
+
ExecStart=/usr/local/bin/heathrowd start --daemon --interval 30
|
|
9
|
+
ExecStop=/usr/local/bin/heathrowd stop
|
|
10
|
+
Restart=always
|
|
11
|
+
RestartSec=10
|
|
12
|
+
|
|
13
|
+
# Security settings
|
|
14
|
+
NoNewPrivileges=yes
|
|
15
|
+
PrivateTmp=yes
|
|
16
|
+
ProtectSystem=strict
|
|
17
|
+
ProtectHome=yes
|
|
18
|
+
ReadWritePaths=%h/.heathrow
|
|
19
|
+
|
|
20
|
+
[Install]
|
|
21
|
+
WantedBy=multi-user.target
|
data/img/heathrow.svg
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#0f0f1a"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="glow" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
8
|
+
<stop offset="0%" style="stop-color:#e2b714"/>
|
|
9
|
+
<stop offset="100%" style="stop-color:#c49000"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
@keyframes pulse { 0%,100% { opacity: 0.5; } 50% { opacity: 1; } }
|
|
15
|
+
@keyframes spin { from { transform-origin: 128px 128px; transform: rotate(0deg); } to { transform-origin: 128px 128px; transform: rotate(360deg); } }
|
|
16
|
+
@keyframes dot-travel {
|
|
17
|
+
0% { opacity: 0; }
|
|
18
|
+
10% { opacity: 1; }
|
|
19
|
+
90% { opacity: 1; }
|
|
20
|
+
100% { opacity: 0; }
|
|
21
|
+
}
|
|
22
|
+
.hub-ring { animation: pulse 3s ease-in-out infinite; }
|
|
23
|
+
.outer-ring { animation: spin 30s linear infinite; }
|
|
24
|
+
.dot1 { animation: dot-travel 2.5s ease-in-out infinite; }
|
|
25
|
+
.dot2 { animation: dot-travel 2.5s ease-in-out 0.4s infinite; }
|
|
26
|
+
.dot3 { animation: dot-travel 2.5s ease-in-out 0.8s infinite; }
|
|
27
|
+
.dot4 { animation: dot-travel 2.5s ease-in-out 1.2s infinite; }
|
|
28
|
+
.dot5 { animation: dot-travel 2.5s ease-in-out 1.6s infinite; }
|
|
29
|
+
.dot6 { animation: dot-travel 2.5s ease-in-out 2.0s infinite; }
|
|
30
|
+
</style>
|
|
31
|
+
|
|
32
|
+
<!-- Background circle -->
|
|
33
|
+
<circle cx="128" cy="128" r="120" fill="url(#bg)" stroke="#e2b714" stroke-width="3"/>
|
|
34
|
+
|
|
35
|
+
<!-- Outer decorative ring (slow spin) -->
|
|
36
|
+
<circle class="outer-ring" cx="128" cy="128" r="95" fill="none" stroke="#e2b714" stroke-width="0.5" stroke-dasharray="8 12" opacity="0.3"/>
|
|
37
|
+
|
|
38
|
+
<!-- Hub ring (pulsing) -->
|
|
39
|
+
<circle class="hub-ring" cx="128" cy="128" r="38" fill="none" stroke="url(#glow)" stroke-width="3"/>
|
|
40
|
+
<!-- Hub center dot -->
|
|
41
|
+
<circle cx="128" cy="128" r="6" fill="#e2b714" opacity="0.9"/>
|
|
42
|
+
|
|
43
|
+
<!-- Spokes with traveling dots -->
|
|
44
|
+
|
|
45
|
+
<!-- Top: Email (gold) -->
|
|
46
|
+
<line x1="128" y1="90" x2="128" y2="52" stroke="#e2b714" stroke-width="1.5" opacity="0.4"/>
|
|
47
|
+
<circle class="dot1" cx="128" cy="70" r="2" fill="#e2b714"/>
|
|
48
|
+
<rect x="116" y="34" width="24" height="16" rx="2" fill="none" stroke="#e2b714" stroke-width="2"/>
|
|
49
|
+
<polyline points="116,36 128,44 140,36" fill="none" stroke="#e2b714" stroke-width="1.5"/>
|
|
50
|
+
|
|
51
|
+
<!-- Top-right: Chat (blue) -->
|
|
52
|
+
<line x1="155" y1="100" x2="185" y2="66" stroke="#39c" stroke-width="1.5" opacity="0.4"/>
|
|
53
|
+
<circle class="dot2" cx="170" cy="83" r="2" fill="#39c"/>
|
|
54
|
+
<rect x="178" y="48" width="24" height="17" rx="4" fill="none" stroke="#39c" stroke-width="2"/>
|
|
55
|
+
<circle cx="186" cy="57" r="1.5" fill="#39c"/>
|
|
56
|
+
<circle cx="190" cy="57" r="1.5" fill="#39c"/>
|
|
57
|
+
<circle cx="194" cy="57" r="1.5" fill="#39c"/>
|
|
58
|
+
|
|
59
|
+
<!-- Right: RSS (orange) -->
|
|
60
|
+
<line x1="166" y1="128" x2="206" y2="128" stroke="#f80" stroke-width="1.5" opacity="0.4"/>
|
|
61
|
+
<circle class="dot3" cx="186" cy="128" r="2" fill="#f80"/>
|
|
62
|
+
<circle cx="220" cy="128" r="3" fill="#f80"/>
|
|
63
|
+
<path d="M212,119 A14,14 0 0,1 228,135" fill="none" stroke="#f80" stroke-width="2.5"/>
|
|
64
|
+
<path d="M208,113 A22,22 0 0,1 234,139" fill="none" stroke="#f80" stroke-width="1.5"/>
|
|
65
|
+
|
|
66
|
+
<!-- Bottom-right: Discord (purple) -->
|
|
67
|
+
<line x1="155" y1="156" x2="184" y2="188" stroke="#77d" stroke-width="1.5" opacity="0.4"/>
|
|
68
|
+
<circle class="dot4" cx="170" cy="172" r="2" fill="#77d"/>
|
|
69
|
+
<text x="186" y="200" font-family="sans-serif" font-size="22" font-weight="bold" fill="#77d">#</text>
|
|
70
|
+
|
|
71
|
+
<!-- Bottom: Terminal (green) -->
|
|
72
|
+
<line x1="128" y1="166" x2="128" y2="204" stroke="#4c4" stroke-width="1.5" opacity="0.4"/>
|
|
73
|
+
<circle class="dot5" cx="128" cy="186" r="2" fill="#4c4"/>
|
|
74
|
+
<rect x="110" y="206" width="36" height="24" rx="3" fill="none" stroke="#4c4" stroke-width="2"/>
|
|
75
|
+
<text x="116" y="224" font-family="monospace" font-size="12" fill="#4c4">>_</text>
|
|
76
|
+
|
|
77
|
+
<!-- Bottom-left: Phone (teal) -->
|
|
78
|
+
<line x1="101" y1="156" x2="72" y2="188" stroke="#5b5" stroke-width="1.5" opacity="0.4"/>
|
|
79
|
+
<circle class="dot6" cx="86" cy="172" r="2" fill="#5b5"/>
|
|
80
|
+
<rect x="58" y="188" width="18" height="28" rx="3" fill="none" stroke="#5b5" stroke-width="2"/>
|
|
81
|
+
<circle cx="67" cy="212" r="2" fill="#5b5"/>
|
|
82
|
+
|
|
83
|
+
<!-- Left: Forum (red) -->
|
|
84
|
+
<line x1="90" y1="128" x2="50" y2="128" stroke="#e55" stroke-width="1.5" opacity="0.4"/>
|
|
85
|
+
<circle cx="38" cy="128" r="12" fill="none" stroke="#e55" stroke-width="2"/>
|
|
86
|
+
<circle cx="34" cy="124" r="2" fill="#e55"/>
|
|
87
|
+
<circle cx="42" cy="124" r="2" fill="#e55"/>
|
|
88
|
+
<path d="M33,131 Q38,136 43,131" fill="none" stroke="#e55" stroke-width="1.5"/>
|
|
89
|
+
|
|
90
|
+
<!-- Top-left: Web (lavender) -->
|
|
91
|
+
<line x1="101" y1="100" x2="72" y2="68" stroke="#a8e" stroke-width="1.5" opacity="0.4"/>
|
|
92
|
+
<circle cx="60" cy="56" r="14" fill="none" stroke="#a8e" stroke-width="2"/>
|
|
93
|
+
<ellipse cx="60" cy="56" rx="6" ry="14" fill="none" stroke="#a8e" stroke-width="1"/>
|
|
94
|
+
<line x1="46" y1="56" x2="74" y2="56" stroke="#a8e" stroke-width="1"/>
|
|
95
|
+
</svg>
|
|
Binary file
|
data/img/sources.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Heathrow
|
|
2
|
+
class AddressBook
|
|
3
|
+
attr_reader :aliases
|
|
4
|
+
|
|
5
|
+
def initialize(path = File.expand_path('~/setup/addressbook'))
|
|
6
|
+
@aliases = {}
|
|
7
|
+
parse(path) if File.exist?(path)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse(path)
|
|
11
|
+
File.readlines(path).each do |line|
|
|
12
|
+
next unless line =~ /^alias\s+(\S+)\s+(.+)/
|
|
13
|
+
@aliases[$1] = $2.strip
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Search aliases and addresses by query string
|
|
18
|
+
def lookup(query)
|
|
19
|
+
query_down = query.downcase
|
|
20
|
+
@aliases.select { |k, v| k.include?(query_down) || v.downcase.include?(query_down) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Expand an alias name to full address, or return input unchanged
|
|
24
|
+
def expand(name)
|
|
25
|
+
@aliases[name] || name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get all alias names
|
|
29
|
+
def names
|
|
30
|
+
@aliases.keys.sort
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get completion candidates for a partial string
|
|
34
|
+
def complete(partial)
|
|
35
|
+
return [] if partial.nil? || partial.empty?
|
|
36
|
+
partial_down = partial.downcase
|
|
37
|
+
@aliases.select { |k, v|
|
|
38
|
+
k.start_with?(partial_down) || v.downcase.include?(partial_down)
|
|
39
|
+
}.map { |k, v| "#{k} → #{v}" }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|