catfriend 0.0 → 0.1
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.
- data/bin/catfriend +107 -0
- data/lib/imap.rb +86 -0
- data/lib/server.rb +24 -0
- metadata +21 -7
data/bin/catfriend
CHANGED
@@ -1 +1,108 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Program for reading IMAP servers and creating desktop notifications when
|
4
|
+
# new e-mail arrives.
|
5
|
+
#
|
6
|
+
# Author:: James Pike (mailto:catfriend@chilon.net)
|
7
|
+
# Copyright:: Copyright (c) 2011 James Pike
|
8
|
+
# License:: MIT
|
9
|
+
module Catfriend
|
10
|
+
|
11
|
+
require 'imap'
|
12
|
+
require 'net/imap'
|
13
|
+
|
14
|
+
# xdg is optional. it outputs to stderr, so i redirect it for a while.
|
15
|
+
stderr_bak = $stderr.dup
|
16
|
+
$stderr.reopen '/dev/null', 'w'
|
17
|
+
begin
|
18
|
+
require 'xdg'
|
19
|
+
rescue LoadError ; end
|
20
|
+
$stderr = stderr_bak # restore stderr
|
21
|
+
|
22
|
+
# Reads a simple configuration format and returns an array of servers.
|
23
|
+
def self.parse_config
|
24
|
+
# xdg is optional
|
25
|
+
begin
|
26
|
+
config_file = XDG['CONFIG'].find 'catfriend'
|
27
|
+
rescue NameError ; end
|
28
|
+
config_file ||= "#{ENV['HOME']}/.config/catfriend"
|
29
|
+
|
30
|
+
# for location of certificate file
|
31
|
+
Dir.chdir File.dirname(config_file)
|
32
|
+
|
33
|
+
servers = []
|
34
|
+
current = {}
|
35
|
+
defaults = {}
|
36
|
+
|
37
|
+
File.foreach(config_file) do |line|
|
38
|
+
tokens = line.sub(/#.*/, '').scan(/\S+/)
|
39
|
+
|
40
|
+
# parse tokens so use ugly loop instead of functional loop
|
41
|
+
until tokens.empty?
|
42
|
+
field = tokens.shift
|
43
|
+
|
44
|
+
# obviously assigning it in a loop like this is slow but hey it's
|
45
|
+
# only run-once config and ruby people say DRY a lot.
|
46
|
+
shift_tokens = lambda do
|
47
|
+
if tokens.empty? then
|
48
|
+
raise ConfigError,
|
49
|
+
"field #{field} requires parameter"
|
50
|
+
end
|
51
|
+
return tokens.shift
|
52
|
+
end
|
53
|
+
|
54
|
+
case field
|
55
|
+
when "host","imap"
|
56
|
+
# host is deprecated
|
57
|
+
if not current.empty? then
|
58
|
+
servers << ImapServer.new(current)
|
59
|
+
current = {}
|
60
|
+
end
|
61
|
+
current[:host] = shift_tokens.call
|
62
|
+
when "notificationTimeout", "errorTimeout", "socketTimeout"
|
63
|
+
# convert from camelCase to camel_case
|
64
|
+
clean_field =
|
65
|
+
field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
|
66
|
+
defaults[clean_field] = shift_tokens.call
|
67
|
+
when "checkInterval"
|
68
|
+
shift_tokens.call # deprecated, ignore parameter
|
69
|
+
when "cert_file"
|
70
|
+
cert_file = shift_tokens.call
|
71
|
+
unless File.exists? cert_file
|
72
|
+
raise ConfigError,
|
73
|
+
"non-existant SSL certificate `#{cert_file}'" +
|
74
|
+
", search path: #{File.dirname(config_file)}/"
|
75
|
+
end
|
76
|
+
current[:cert_file] = cert_file
|
77
|
+
when "mailbox", "id", "user", "password"
|
78
|
+
current[field] = shift_tokens.call
|
79
|
+
when "nossl"
|
80
|
+
current[:no_ssl] = true
|
81
|
+
else
|
82
|
+
raise ConfigError,
|
83
|
+
"invalid config parameter '#{field}': #{line}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
servers << ImapServer.new(current) unless current.empty?
|
89
|
+
|
90
|
+
servers
|
91
|
+
end
|
92
|
+
|
93
|
+
# Main interface to the application. Reads all servers from config then runs
|
94
|
+
# each one in a thread. The program exits when all threads encounter an
|
95
|
+
# unrecoverable error. Perhaps I should make it exit if any thread exits.
|
96
|
+
def self.main
|
97
|
+
begin
|
98
|
+
servers = parse_config
|
99
|
+
servers.each { |s| s.start }
|
100
|
+
servers.each { |s| s.join }
|
101
|
+
rescue ConfigError => e
|
102
|
+
puts "misconfiguration: " + e.message
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end ########################### end module
|
107
|
+
|
108
|
+
Catfriend.main
|
data/lib/imap.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'server'
|
2
|
+
|
3
|
+
# This class represents a thread capable of checking and creating
|
4
|
+
# notifications for a single mailbox on a single IMAP server.
|
5
|
+
class ImapServer
|
6
|
+
include ThreadMixin
|
7
|
+
include AccessorsFromHash
|
8
|
+
|
9
|
+
# Create new IMAP server with optional full configuration hash.
|
10
|
+
# If the hash is not supplied at construction a further call must be
|
11
|
+
# made to #configure before #start is called to start the thread.
|
12
|
+
def initialize(args = nil)
|
13
|
+
configure args if args
|
14
|
+
end
|
15
|
+
|
16
|
+
# Configure all attributes based on hash then make sure this
|
17
|
+
# represents a total valid configuration.
|
18
|
+
def configure args
|
19
|
+
super args
|
20
|
+
|
21
|
+
if not @user
|
22
|
+
raise ConfigError, "imap user not set"
|
23
|
+
end
|
24
|
+
if not @host
|
25
|
+
raise ConfigError, "imap host not set"
|
26
|
+
end
|
27
|
+
if not @password
|
28
|
+
raise ConfigError, "imap password not set"
|
29
|
+
end
|
30
|
+
|
31
|
+
@id = @host unless @id
|
32
|
+
end
|
33
|
+
|
34
|
+
# The id is a token which represents this server when displaying
|
35
|
+
# notifications and is set to the host unless over-ridden by the
|
36
|
+
# configuration file
|
37
|
+
def id ; @id || @host ; end
|
38
|
+
|
39
|
+
# Raise an error related to this particular server.
|
40
|
+
def error message
|
41
|
+
# consider raising notification instead?
|
42
|
+
puts "#{id}: #{message}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# ThreadMixin interface. This connects to the mailserver and then
|
46
|
+
# runs #check_loop to do the e-mail checking if the connection
|
47
|
+
# succeeds.
|
48
|
+
def run
|
49
|
+
# connect and go
|
50
|
+
begin
|
51
|
+
connect
|
52
|
+
rescue OpenSSL::SSL::SSLError
|
53
|
+
error "try providing ssl certificate"
|
54
|
+
rescue Net::IMAP::NoResponseError
|
55
|
+
error "no response to connect, try ssl"
|
56
|
+
else
|
57
|
+
check_loop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Continually waits for new e-mail raising notifications when new
|
62
|
+
# e-mail arrives or when error conditions happen. This methods only exits
|
63
|
+
# on an unrecoverable error.
|
64
|
+
def check_loop
|
65
|
+
#
|
66
|
+
end
|
67
|
+
|
68
|
+
# Connect to the configured IMAP server.
|
69
|
+
def connect
|
70
|
+
args = nil
|
71
|
+
if not @no_ssl then
|
72
|
+
if @cert_file then
|
73
|
+
args = { :ssl => { :ca_file => @cert_file } }
|
74
|
+
else
|
75
|
+
args = { :ssl => true }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@imap = Net::IMAP.new(@host, args)
|
79
|
+
@imap.login(@user, @password)
|
80
|
+
puts @imap.select(@mailbox || "INBOX")
|
81
|
+
end
|
82
|
+
|
83
|
+
private :connect, :check_loop, :run, :error
|
84
|
+
attr_writer :host, :password, :id, :user, :no_ssl, :cert_file, :mailbox
|
85
|
+
end
|
86
|
+
|
data/lib/server.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Mixin this module and define "run" for a simple runnable/joinable thread
|
2
|
+
module ThreadMixin
|
3
|
+
# Call to start a thread running via the start method.
|
4
|
+
def start ; @thread = Thread.new { run } ; end
|
5
|
+
|
6
|
+
# Join thread started with start.
|
7
|
+
def join ; @thread.join ; end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Mixin to provide #configure which allows all instance variables with write
|
11
|
+
# accessors declared to be set from a hash.
|
12
|
+
module AccessorsFromHash
|
13
|
+
# Call this to tranfer the hash data to corresponding attributes. Any
|
14
|
+
# hash keys that do not have a corresponding write accessor in the
|
15
|
+
# class are silently ignored.
|
16
|
+
def configure args
|
17
|
+
args.each do |opt, val|
|
18
|
+
instance_variable_set("@#{opt}", val) if respond_to? "#{opt}="
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This class is used to signal the user made an error in their configuration.
|
24
|
+
class ConfigError < Exception ; end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: catfriend
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.1'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-libnotify
|
16
|
-
requirement: &
|
16
|
+
requirement: &9103360 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,8 +21,19 @@ dependencies:
|
|
21
21
|
version: '0.5'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
|
24
|
+
version_requirements: *9103360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: xdg
|
27
|
+
requirement: &9102900 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *9102900
|
36
|
+
description: E-mail checker with libnotify desktop notifications.
|
26
37
|
email:
|
27
38
|
- catfriend@chilon.net
|
28
39
|
executables:
|
@@ -31,9 +42,12 @@ extensions: []
|
|
31
42
|
extra_rdoc_files: []
|
32
43
|
files:
|
33
44
|
- LICENSE
|
45
|
+
- lib/imap.rb
|
46
|
+
- lib/server.rb
|
34
47
|
- bin/catfriend
|
35
|
-
homepage:
|
36
|
-
licenses:
|
48
|
+
homepage: https://github.com/nuisanceofcats/catfriend
|
49
|
+
licenses:
|
50
|
+
- Expat
|
37
51
|
post_install_message:
|
38
52
|
rdoc_options: []
|
39
53
|
require_paths:
|