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,193 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'io/console'
|
|
7
|
+
|
|
8
|
+
module Heathrow
|
|
9
|
+
module Wizards
|
|
10
|
+
class DiscordWizard
|
|
11
|
+
def self.run(source = nil)
|
|
12
|
+
new(source).run
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(source = nil)
|
|
16
|
+
@source = source
|
|
17
|
+
@config = source ? source.config : {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
puts "\n=== Discord Configuration Wizard ==="
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
# Get token
|
|
25
|
+
token = get_token
|
|
26
|
+
return nil unless token
|
|
27
|
+
|
|
28
|
+
# Test token
|
|
29
|
+
is_bot = test_token(token)
|
|
30
|
+
return nil unless is_bot
|
|
31
|
+
|
|
32
|
+
@config['token'] = token
|
|
33
|
+
@config['is_bot'] = is_bot
|
|
34
|
+
|
|
35
|
+
# Discover servers and channels
|
|
36
|
+
puts "\nDiscovering servers and channels..."
|
|
37
|
+
discover_and_select_channels(token, is_bot)
|
|
38
|
+
|
|
39
|
+
# Set other options
|
|
40
|
+
@config['fetch_limit'] ||= 50
|
|
41
|
+
@config['polling_interval'] ||= 300
|
|
42
|
+
|
|
43
|
+
puts "\n=== Configuration Complete ==="
|
|
44
|
+
puts "Token: #{token[0..20]}..."
|
|
45
|
+
puts "Bot account: #{is_bot ? 'Yes' : 'No'}"
|
|
46
|
+
puts "Channels: #{@config['channels'].split(',').length} selected" if @config['channels']
|
|
47
|
+
puts "Guilds: #{@config['guilds'].split(',').length} selected" if @config['guilds'] && !@config['guilds'].empty?
|
|
48
|
+
|
|
49
|
+
@config
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def get_token
|
|
55
|
+
puts "Discord Token Setup"
|
|
56
|
+
puts "-" * 40
|
|
57
|
+
puts "You can use either:"
|
|
58
|
+
puts " 1. Bot token (recommended) - Create at https://discord.com/developers/applications"
|
|
59
|
+
puts " 2. User token (against ToS) - Found in browser DevTools"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
existing = @config['token']
|
|
63
|
+
if existing
|
|
64
|
+
print "Current token: #{existing[0..20]}...\nKeep this token? (Y/n): "
|
|
65
|
+
keep = gets.chomp.downcase
|
|
66
|
+
return existing unless keep == 'n'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
print "Enter Discord token: "
|
|
70
|
+
token = gets.chomp.strip
|
|
71
|
+
|
|
72
|
+
if token.empty?
|
|
73
|
+
puts "Error: Token cannot be empty"
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
token
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_token(token)
|
|
81
|
+
# Try as bot token first
|
|
82
|
+
uri = URI("https://discord.com/api/v10/users/@me")
|
|
83
|
+
|
|
84
|
+
[true, false].each do |is_bot|
|
|
85
|
+
request = Net::HTTP::Get.new(uri)
|
|
86
|
+
request['Authorization'] = is_bot ? "Bot #{token}" : token
|
|
87
|
+
|
|
88
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
89
|
+
http.request(request)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
93
|
+
user_data = JSON.parse(response.body)
|
|
94
|
+
puts "✓ Connected as: #{user_data['username']}##{user_data['discriminator']}"
|
|
95
|
+
puts " Account type: #{is_bot ? 'Bot' : 'User'}"
|
|
96
|
+
return is_bot
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
puts "✗ Failed to authenticate with Discord"
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def discover_and_select_channels(token, is_bot)
|
|
105
|
+
# Get user's guilds
|
|
106
|
+
uri = URI("https://discord.com/api/v10/users/@me/guilds")
|
|
107
|
+
request = Net::HTTP::Get.new(uri)
|
|
108
|
+
request['Authorization'] = is_bot ? "Bot #{token}" : token
|
|
109
|
+
|
|
110
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
111
|
+
http.request(request)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
115
|
+
puts "Could not fetch guilds. You can manually add channel IDs later."
|
|
116
|
+
return
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
guilds = JSON.parse(response.body)
|
|
120
|
+
puts "\nFound #{guilds.length} servers:"
|
|
121
|
+
|
|
122
|
+
# Show guilds and their channels
|
|
123
|
+
selected_channels = []
|
|
124
|
+
selected_guilds = []
|
|
125
|
+
|
|
126
|
+
guilds.each_with_index do |guild, index|
|
|
127
|
+
puts "\n#{index + 1}. #{guild['name']}"
|
|
128
|
+
|
|
129
|
+
# Get channels for this guild
|
|
130
|
+
channels_uri = URI("https://discord.com/api/v10/guilds/#{guild['id']}/channels")
|
|
131
|
+
channels_request = Net::HTTP::Get.new(channels_uri)
|
|
132
|
+
channels_request['Authorization'] = is_bot ? "Bot #{token}" : token
|
|
133
|
+
|
|
134
|
+
channels_response = Net::HTTP.start(channels_uri.hostname, channels_uri.port, use_ssl: true) do |http|
|
|
135
|
+
http.request(channels_request)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if channels_response.is_a?(Net::HTTPSuccess)
|
|
139
|
+
channels = JSON.parse(channels_response.body)
|
|
140
|
+
text_channels = channels.select { |c| c['type'] == 0 } # Type 0 = text channel
|
|
141
|
+
|
|
142
|
+
puts " Channels:"
|
|
143
|
+
text_channels.first(10).each do |channel|
|
|
144
|
+
puts " - ##{channel['name']} (ID: #{channel['id']})"
|
|
145
|
+
end
|
|
146
|
+
puts " ... and #{text_channels.length - 10} more" if text_channels.length > 10
|
|
147
|
+
else
|
|
148
|
+
puts " Could not fetch channels for this server"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
puts "\n" + "=" * 40
|
|
153
|
+
puts "Channel Selection Options:"
|
|
154
|
+
puts " 1. Select all channels from specific servers"
|
|
155
|
+
puts " 2. Select individual channels (enter IDs)"
|
|
156
|
+
puts " 3. Use existing configuration"
|
|
157
|
+
puts " 4. Skip for now"
|
|
158
|
+
print "\nChoice (1-4): "
|
|
159
|
+
|
|
160
|
+
choice = gets.chomp
|
|
161
|
+
|
|
162
|
+
case choice
|
|
163
|
+
when '1'
|
|
164
|
+
print "Enter server numbers (comma-separated, e.g., 1,3,5): "
|
|
165
|
+
numbers = gets.chomp.split(',').map(&:strip)
|
|
166
|
+
|
|
167
|
+
numbers.each do |num|
|
|
168
|
+
idx = num.to_i - 1
|
|
169
|
+
if idx >= 0 && idx < guilds.length
|
|
170
|
+
selected_guilds << guilds[idx]['id']
|
|
171
|
+
puts " Added: #{guilds[idx]['name']}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
@config['guilds'] = selected_guilds.join(',') unless selected_guilds.empty?
|
|
176
|
+
|
|
177
|
+
when '2'
|
|
178
|
+
puts "Enter channel IDs (comma-separated):"
|
|
179
|
+
puts "Tip: Right-click channel in Discord > Copy ID"
|
|
180
|
+
print "> "
|
|
181
|
+
channels = gets.chomp
|
|
182
|
+
@config['channels'] = channels unless channels.empty?
|
|
183
|
+
|
|
184
|
+
when '3'
|
|
185
|
+
puts "Keeping existing configuration"
|
|
186
|
+
|
|
187
|
+
else
|
|
188
|
+
puts "Skipping channel configuration"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../sources/slack'
|
|
6
|
+
|
|
7
|
+
module Heathrow
|
|
8
|
+
module Wizards
|
|
9
|
+
# Setup wizard for Slack integration
|
|
10
|
+
class SlackWizard
|
|
11
|
+
def self.run
|
|
12
|
+
puts "\n=== Slack Setup Wizard ==="
|
|
13
|
+
puts "This will help you configure Slack integration for Heathrow.\n\n"
|
|
14
|
+
|
|
15
|
+
puts "You'll need a Slack API token. You can get one by:"
|
|
16
|
+
puts "1. Creating a Slack App at https://api.slack.com/apps"
|
|
17
|
+
puts "2. Installing it to your workspace"
|
|
18
|
+
puts "3. Getting a User OAuth Token (xoxp-...) or Bot Token (xoxb-...)"
|
|
19
|
+
puts "\nFor full access to all channels and DMs, use a User Token."
|
|
20
|
+
puts "For limited bot access, use a Bot Token.\n\n"
|
|
21
|
+
|
|
22
|
+
config = {}
|
|
23
|
+
|
|
24
|
+
# Get API token
|
|
25
|
+
print "Enter your Slack API token (xoxp-... or xoxb-...): "
|
|
26
|
+
config['api_token'] = gets.chomp
|
|
27
|
+
|
|
28
|
+
# Test the token
|
|
29
|
+
print "\nTesting connection..."
|
|
30
|
+
test_source = Heathrow::Sources::Slack.new(config)
|
|
31
|
+
|
|
32
|
+
unless test_source.test_connection
|
|
33
|
+
puts "\nFailed to connect. Please check your token and try again."
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts " Success!\n\n"
|
|
38
|
+
|
|
39
|
+
# Get workspace name (optional)
|
|
40
|
+
print "Enter a name for this workspace (optional, for display): "
|
|
41
|
+
workspace = gets.chomp
|
|
42
|
+
config['workspace'] = workspace unless workspace.empty?
|
|
43
|
+
|
|
44
|
+
# Ask about channel configuration
|
|
45
|
+
puts "\nChannel Configuration:"
|
|
46
|
+
puts "1. Monitor all channels I have access to (default)"
|
|
47
|
+
puts "2. Monitor specific channels only"
|
|
48
|
+
print "Choose option [1]: "
|
|
49
|
+
|
|
50
|
+
choice = gets.chomp
|
|
51
|
+
choice = '1' if choice.empty?
|
|
52
|
+
|
|
53
|
+
if choice == '2'
|
|
54
|
+
puts "\nEnter channel IDs to monitor (one per line, empty line to finish):"
|
|
55
|
+
puts "Example: C1234567890"
|
|
56
|
+
puts "You can find channel IDs in Slack by right-clicking a channel."
|
|
57
|
+
|
|
58
|
+
channels = []
|
|
59
|
+
loop do
|
|
60
|
+
print "> "
|
|
61
|
+
channel = gets.chomp
|
|
62
|
+
break if channel.empty?
|
|
63
|
+
channels << channel
|
|
64
|
+
end
|
|
65
|
+
config['channel_ids'] = channels unless channels.empty?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Ask about DM configuration
|
|
69
|
+
puts "\nDirect Message Configuration:"
|
|
70
|
+
puts "1. Monitor all DMs (default)"
|
|
71
|
+
puts "2. Monitor specific users only"
|
|
72
|
+
print "Choose option [1]: "
|
|
73
|
+
|
|
74
|
+
choice = gets.chomp
|
|
75
|
+
choice = '1' if choice.empty?
|
|
76
|
+
|
|
77
|
+
if choice == '2'
|
|
78
|
+
puts "\nEnter user IDs to monitor DMs with (one per line, empty line to finish):"
|
|
79
|
+
puts "Example: U1234567890"
|
|
80
|
+
|
|
81
|
+
users = []
|
|
82
|
+
loop do
|
|
83
|
+
print "> "
|
|
84
|
+
user = gets.chomp
|
|
85
|
+
break if user.empty?
|
|
86
|
+
users << user
|
|
87
|
+
end
|
|
88
|
+
config['dm_user_ids'] = users unless users.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Ask about fetch interval
|
|
92
|
+
print "\nHow often to fetch messages (in seconds) [300]: "
|
|
93
|
+
interval = gets.chomp
|
|
94
|
+
config['fetch_interval'] = interval.empty? ? 300 : interval.to_i
|
|
95
|
+
|
|
96
|
+
# Save configuration
|
|
97
|
+
source_name = workspace.empty? ? 'slack' : "slack_#{workspace.downcase.gsub(/\s+/, '_')}"
|
|
98
|
+
config_file = File.join(Dir.home, '.config', 'heathrow', 'sources', "#{source_name}.json")
|
|
99
|
+
|
|
100
|
+
# Create directory if it doesn't exist
|
|
101
|
+
FileUtils.mkdir_p(File.dirname(config_file))
|
|
102
|
+
|
|
103
|
+
# Write config
|
|
104
|
+
File.write(config_file, JSON.pretty_generate(config))
|
|
105
|
+
|
|
106
|
+
puts "\n✓ Slack configuration saved to: #{config_file}"
|
|
107
|
+
puts "\nYou can now use this source in Heathrow!"
|
|
108
|
+
puts "The source will appear as: #{source_name}"
|
|
109
|
+
|
|
110
|
+
config
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.run_oauth_flow
|
|
114
|
+
puts "\n=== Slack OAuth Setup (Alternative) ==="
|
|
115
|
+
puts "This method uses OAuth authentication flow.\n\n"
|
|
116
|
+
|
|
117
|
+
puts "1. Go to: https://api.slack.com/apps"
|
|
118
|
+
puts "2. Create a new app or select existing"
|
|
119
|
+
puts "3. Add these OAuth scopes:"
|
|
120
|
+
puts " - channels:history"
|
|
121
|
+
puts " - channels:read"
|
|
122
|
+
puts " - groups:history"
|
|
123
|
+
puts " - groups:read"
|
|
124
|
+
puts " - im:history"
|
|
125
|
+
puts " - im:read"
|
|
126
|
+
puts " - mpim:history"
|
|
127
|
+
puts " - mpim:read"
|
|
128
|
+
puts " - users:read"
|
|
129
|
+
puts " - chat:write"
|
|
130
|
+
puts "4. Install the app to your workspace"
|
|
131
|
+
puts "5. Copy the User OAuth Token\n\n"
|
|
132
|
+
|
|
133
|
+
print "Press Enter when ready to continue..."
|
|
134
|
+
gets
|
|
135
|
+
|
|
136
|
+
run
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/heathrow.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Main Heathrow module
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'sqlite3'
|
|
6
|
+
require 'time'
|
|
7
|
+
|
|
8
|
+
# Create Heathrow home directory structure
|
|
9
|
+
HEATHROW_HOME = File.expand_path('~/.heathrow')
|
|
10
|
+
HEATHROW_DB = File.join(HEATHROW_HOME, 'heathrow.db')
|
|
11
|
+
HEATHROW_CONFIG = File.join(HEATHROW_HOME, 'config.yml')
|
|
12
|
+
HEATHROW_SOURCES = File.join(HEATHROW_HOME, 'sources')
|
|
13
|
+
HEATHROW_VIEWS = File.join(HEATHROW_HOME, 'views')
|
|
14
|
+
HEATHROW_ATTACHMENTS = File.join(HEATHROW_HOME, 'attachments')
|
|
15
|
+
HEATHROW_PLUGINS = File.join(HEATHROW_HOME, 'plugins')
|
|
16
|
+
HEATHROW_LOGS = File.join(HEATHROW_HOME, 'logs')
|
|
17
|
+
|
|
18
|
+
# Create directory structure
|
|
19
|
+
[HEATHROW_HOME, HEATHROW_SOURCES, HEATHROW_VIEWS, HEATHROW_ATTACHMENTS, HEATHROW_PLUGINS, HEATHROW_LOGS].each do |dir|
|
|
20
|
+
FileUtils.mkdir_p(dir)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Load all components
|
|
24
|
+
require_relative 'heathrow/version'
|
|
25
|
+
|
|
26
|
+
# Core infrastructure (Phase 0)
|
|
27
|
+
require_relative 'heathrow/logger'
|
|
28
|
+
require_relative 'heathrow/event_bus'
|
|
29
|
+
require_relative 'heathrow/database'
|
|
30
|
+
require_relative 'heathrow/config'
|
|
31
|
+
|
|
32
|
+
# Plugin system
|
|
33
|
+
require_relative 'heathrow/plugin/base'
|
|
34
|
+
require_relative 'heathrow/plugin_manager'
|
|
35
|
+
|
|
36
|
+
# Models
|
|
37
|
+
require_relative 'heathrow/message'
|
|
38
|
+
require_relative 'heathrow/source'
|
|
39
|
+
|
|
40
|
+
# Sources
|
|
41
|
+
require_relative 'heathrow/sources/source_manager'
|
|
42
|
+
|
|
43
|
+
# Background polling
|
|
44
|
+
require_relative 'heathrow/poller'
|
|
45
|
+
|
|
46
|
+
# UI components
|
|
47
|
+
require_relative 'heathrow/ui/application'
|
|
48
|
+
require_relative 'heathrow/ui/source_wizard'
|
|
49
|
+
require_relative 'heathrow/ui/panes'
|
|
50
|
+
require_relative 'heathrow/ui/navigation'
|
|
51
|
+
require_relative 'heathrow/ui/views'
|
|
52
|
+
|
|
53
|
+
module Heathrow
|
|
54
|
+
class Error < StandardError; end
|
|
55
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: heathrow
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.7.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Geir Isene
|
|
8
|
+
- Claude Code
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: rcurses
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ">="
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '5.0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '5.0'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: sqlite3
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - ">="
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '1.4'
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '1.4'
|
|
42
|
+
description: A unified TUI application for managing all your communication sources
|
|
43
|
+
in one place. Brings together emails, WhatsApp, Discord, Reddit, RSS feeds, and
|
|
44
|
+
more into a single, efficient terminal interface.
|
|
45
|
+
email:
|
|
46
|
+
- g@isene.com
|
|
47
|
+
executables:
|
|
48
|
+
- heathrow
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- ".gitignore"
|
|
53
|
+
- README.md
|
|
54
|
+
- bin/heathrow
|
|
55
|
+
- bin/heathrowd
|
|
56
|
+
- docs/ARCHITECTURE.md
|
|
57
|
+
- docs/DATABASE_SCHEMA.md
|
|
58
|
+
- docs/DEVELOPMENT_WORKFLOW.md
|
|
59
|
+
- docs/DISCORD_SETUP.md
|
|
60
|
+
- docs/GMAIL_OAUTH_SETUP.md
|
|
61
|
+
- docs/PLUGIN_SYSTEM.md
|
|
62
|
+
- docs/PROJECT_PLAN.md
|
|
63
|
+
- docs/README.md
|
|
64
|
+
- docs/REDDIT_SETUP.md
|
|
65
|
+
- docs/REPLY_FORWARD.md
|
|
66
|
+
- docs/WHATSAPP_TELEGRAM_SETUP.md
|
|
67
|
+
- heathrow.gemspec
|
|
68
|
+
- heathrowd.service
|
|
69
|
+
- img/heathrow.svg
|
|
70
|
+
- img/rss_threaded.png
|
|
71
|
+
- img/sources.png
|
|
72
|
+
- lib/heathrow.rb
|
|
73
|
+
- lib/heathrow/address_book.rb
|
|
74
|
+
- lib/heathrow/config.rb
|
|
75
|
+
- lib/heathrow/database.rb
|
|
76
|
+
- lib/heathrow/database_new.rb
|
|
77
|
+
- lib/heathrow/event_bus.rb
|
|
78
|
+
- lib/heathrow/logger.rb
|
|
79
|
+
- lib/heathrow/message.rb
|
|
80
|
+
- lib/heathrow/message_composer.rb
|
|
81
|
+
- lib/heathrow/message_organizer.rb
|
|
82
|
+
- lib/heathrow/migrations/001_initial_schema.rb
|
|
83
|
+
- lib/heathrow/notmuch.rb
|
|
84
|
+
- lib/heathrow/oauth2_smtp.rb
|
|
85
|
+
- lib/heathrow/plugin/base.rb
|
|
86
|
+
- lib/heathrow/plugin_manager.rb
|
|
87
|
+
- lib/heathrow/poller.rb
|
|
88
|
+
- lib/heathrow/smtp_sender.rb
|
|
89
|
+
- lib/heathrow/source.rb
|
|
90
|
+
- lib/heathrow/sources/base.rb
|
|
91
|
+
- lib/heathrow/sources/discord.rb
|
|
92
|
+
- lib/heathrow/sources/gmail.rb
|
|
93
|
+
- lib/heathrow/sources/imap.rb
|
|
94
|
+
- lib/heathrow/sources/instagram.rb
|
|
95
|
+
- lib/heathrow/sources/instagram_fetch.py
|
|
96
|
+
- lib/heathrow/sources/instagram_send.py
|
|
97
|
+
- lib/heathrow/sources/instagram_send_marionette.py
|
|
98
|
+
- lib/heathrow/sources/maildir.rb
|
|
99
|
+
- lib/heathrow/sources/messenger.rb
|
|
100
|
+
- lib/heathrow/sources/messenger_fetch.js
|
|
101
|
+
- lib/heathrow/sources/messenger_fetch_marionette.py
|
|
102
|
+
- lib/heathrow/sources/messenger_send.js
|
|
103
|
+
- lib/heathrow/sources/messenger_send.py
|
|
104
|
+
- lib/heathrow/sources/reddit.rb
|
|
105
|
+
- lib/heathrow/sources/rss.rb
|
|
106
|
+
- lib/heathrow/sources/slack.rb
|
|
107
|
+
- lib/heathrow/sources/source_manager.rb
|
|
108
|
+
- lib/heathrow/sources/telegram.rb
|
|
109
|
+
- lib/heathrow/sources/webpage.rb
|
|
110
|
+
- lib/heathrow/sources/weechat.rb
|
|
111
|
+
- lib/heathrow/sources/whatsapp.rb
|
|
112
|
+
- lib/heathrow/ui/application.rb
|
|
113
|
+
- lib/heathrow/ui/navigation.rb
|
|
114
|
+
- lib/heathrow/ui/panes.rb
|
|
115
|
+
- lib/heathrow/ui/source_wizard.rb
|
|
116
|
+
- lib/heathrow/ui/threaded_view.rb
|
|
117
|
+
- lib/heathrow/ui/views.rb
|
|
118
|
+
- lib/heathrow/version.rb
|
|
119
|
+
- lib/heathrow/wizards/discord_wizard.rb
|
|
120
|
+
- lib/heathrow/wizards/slack_wizard.rb
|
|
121
|
+
homepage: https://github.com/isene/heathrow
|
|
122
|
+
licenses:
|
|
123
|
+
- Unlicense
|
|
124
|
+
metadata:
|
|
125
|
+
homepage_uri: https://github.com/isene/heathrow
|
|
126
|
+
source_code_uri: https://github.com/isene/heathrow
|
|
127
|
+
changelog_uri: https://github.com/isene/heathrow/blob/master/CHANGELOG.md
|
|
128
|
+
post_install_message:
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: 2.7.0
|
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
requirements: []
|
|
143
|
+
rubygems_version: 3.4.20
|
|
144
|
+
signing_key:
|
|
145
|
+
specification_version: 4
|
|
146
|
+
summary: Communication Hub In The Terminal
|
|
147
|
+
test_files: []
|