Class: Imap::Providers::Generic
- Inherits:
-
Object
- Object
- Imap::Providers::Generic
- Defined in:
- lib/imap/providers/generic.rb
Direct Known Subclasses
Instance Method Summary collapse
- #account_digest ⇒ Object
- #archive(uid) ⇒ Object
- #can?(capability) ⇒ Boolean
- #connect! ⇒ Object
- #disconnect! ⇒ Object
- #disconnected? ⇒ Boolean
- #emails(uids, fields, opts = {}) ⇒ Object
- #filter_mailboxes(mailboxes) ⇒ Object
- #find_spam_by_message_ids(message_ids) ⇒ Object
- #find_trashed_by_message_ids(message_ids) ⇒ Object
- #find_uids_by_message_ids(message_ids) ⇒ Object
- #imap ⇒ Object
-
#initialize(server, options = {}) ⇒ Generic
constructor
A new instance of Generic.
- #labels ⇒ Object
- #list_mailboxes(attr_filter = nil) ⇒ Object
- #list_mailboxes_with_attributes(attr_filter = nil) ⇒ Object
- #open_mailbox(mailbox_name, write: false) ⇒ Object
-
#open_spam_mailbox(write: false) {|spam_uid_validity| ... } ⇒ Object
open the spam mailbox for inspection or writing.
-
#open_trash_mailbox(write: false) {|trash_uid_validity| ... } ⇒ Object
open the trash mailbox for inspection or writing.
-
#spam_mailbox ⇒ Object
Look for the special Junk XLIST attribute.
- #store(uid, attribute, old_set, new_set) ⇒ Object
- #tag_to_flag(tag) ⇒ Object
- #tag_to_label(tag) ⇒ Object
- #to_tag(label) ⇒ Object
- #trash(uid) ⇒ Object
-
#trash_mailbox ⇒ Object
Look for the special Trash XLIST attribute.
- #trash_move(uid) ⇒ Object
- #uids(opts = {}) ⇒ Object
- #unarchive(uid) ⇒ Object
Constructor Details
#initialize(server, options = {}) ⇒ Generic
Returns a new instance of Generic.
28 29 30 31 32 33 34 35 |
# File 'lib/imap/providers/generic.rb', line 28 def initialize(server, = {}) @server = server @port = [:port] || 993 @ssl = [:ssl] || true @username = [:username] @password = [:password] @timeout = [:timeout] || 10 end |
Instance Method Details
#account_digest ⇒ Object
37 38 39 |
# File 'lib/imap/providers/generic.rb', line 37 def account_digest @account_digest ||= Digest::MD5.hexdigest("#{@username}:#{@server}") end |
#archive(uid) ⇒ Object
182 183 184 |
# File 'lib/imap/providers/generic.rb', line 182 def archive(uid) # do nothing by default, just removing the Inbox label should be enough end |
#can?(capability) ⇒ Boolean
62 63 64 65 |
# File 'lib/imap/providers/generic.rb', line 62 def can?(capability) @capabilities ||= imap.responses["CAPABILITY"][-1] || imap.capability @capabilities.include?(capability) end |
#connect! ⇒ Object
49 50 51 |
# File 'lib/imap/providers/generic.rb', line 49 def connect! imap.login(@username, @password) end |
#disconnect! ⇒ Object
53 54 55 56 57 58 59 60 |
# File 'lib/imap/providers/generic.rb', line 53 def disconnect! begin imap.logout rescue StandardError nil end imap.disconnect end |
#disconnected? ⇒ Boolean
45 46 47 |
# File 'lib/imap/providers/generic.rb', line 45 def disconnected? @imap && @imap.disconnected? end |
#emails(uids, fields, opts = {}) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/imap/providers/generic.rb', line 110 def emails(uids, fields, opts = {}) fetched = imap.uid_fetch(uids, fields) # This will happen if the email does not exist in the provided mailbox. # It may have been deleted or otherwise moved, e.g. if deleted in Gmail # it will end up in "[Gmail]/Bin" return [] if fetched.nil? fetched.map do |email| attributes = {} fields.each { |field| attributes[field] = email.attr[field] } attributes end end |
#filter_mailboxes(mailboxes) ⇒ Object
176 177 178 179 180 |
# File 'lib/imap/providers/generic.rb', line 176 def filter_mailboxes(mailboxes) # we do not want to filter out any mailboxes for generic providers, # because we do not know what they are ahead of time mailboxes end |
#find_spam_by_message_ids(message_ids) ⇒ Object
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/imap/providers/generic.rb', line 262 def () spam_emails = [] spam_uid_validity = open_spam_mailbox do spam_email_uids = () if spam_email_uids.any? spam_emails = emails(spam_email_uids, %w[UID ENVELOPE]).map do |e| BasicMail.new( message_id: Email::MessageIdService.(e["ENVELOPE"].), uid: e["UID"], ) end end end SpamMailResponse.new.tap do |resp| resp.spam_emails = spam_emails resp.spam_uid_validity = spam_uid_validity end end |
#find_trashed_by_message_ids(message_ids) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/imap/providers/generic.rb', line 240 def () trashed_emails = [] trash_uid_validity = open_trash_mailbox do trashed_email_uids = () if trashed_email_uids.any? trashed_emails = emails(trashed_email_uids, %w[UID ENVELOPE]).map do |e| BasicMail.new( message_id: Email::MessageIdService.(e["ENVELOPE"].), uid: e["UID"], ) end end end TrashedMailResponse.new.tap do |resp| resp.trashed_emails = trashed_emails resp.trash_uid_validity = trash_uid_validity end end |
#find_uids_by_message_ids(message_ids) ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/imap/providers/generic.rb', line 284 def () = .map do |msgid| "HEADER Message-ID '#{Email::MessageIdService.(msgid)}'" end # OR clauses are written in Polish notation...so the query looks like this: # OR OR HEADER Message-ID XXXX HEADER Message-ID XXXX HEADER Message-ID XXXX or_clauses = "OR " * (.length - 1) query = "#{or_clauses}#{.join(" ")}" imap.uid_search(query) end |
#imap ⇒ Object
41 42 43 |
# File 'lib/imap/providers/generic.rb', line 41 def imap @imap ||= Net::IMAP.new(@server, port: @port, ssl: @ssl, open_timeout: @timeout) end |
#labels ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/imap/providers/generic.rb', line 79 def labels @labels ||= begin labels = {} list_mailboxes.each do |name| if tag = to_tag(name) labels[tag] = name end end labels end end |
#list_mailboxes(attr_filter = nil) ⇒ Object
147 148 149 150 |
# File 'lib/imap/providers/generic.rb', line 147 def list_mailboxes(attr_filter = nil) # Lists all the mailboxes but just returns the names. list_mailboxes_with_attributes(attr_filter).map(&:name) end |
#list_mailboxes_with_attributes(attr_filter = nil) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/imap/providers/generic.rb', line 152 def list_mailboxes_with_attributes(attr_filter = nil) # Basically, list all mailboxes in the root of the server. # ref: https://tools.ietf.org/html/rfc3501#section-6.3.8 imap .list("", "*") .reject do |m| # Noselect cannot be selected with the SELECT command. # technically we could use this for readonly mode when # SiteSetting.imap_write is disabled...maybe a later TODO # ref: https://tools.ietf.org/html/rfc3501#section-7.2.2 m.attr.include?(:Noselect) end .select do |m| # There are Special-Use mailboxes denoted by an attribute. For # example, some common ones are \Trash or \Sent. # ref: https://tools.ietf.org/html/rfc6154 if attr_filter m.attr.include? attr_filter else true end end end |
#open_mailbox(mailbox_name, write: false) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/imap/providers/generic.rb', line 94 def open_mailbox(mailbox_name, write: false) if write if !SiteSetting.enable_imap_write raise WriteDisabledError.new("Two-way IMAP sync is disabled! Cannot write to inbox.") end imap.select(mailbox_name) else imap.examine(mailbox_name) end @open_mailbox_name = mailbox_name @open_mailbox_write = write { uid_validity: imap.responses["UIDVALIDITY"][-1] } end |
#open_spam_mailbox(write: false) {|spam_uid_validity| ... } ⇒ Object
open the spam mailbox for inspection or writing. after the yield we close the spam and reopen the original mailbox to continue operations. the normal open_mailbox call can be made if more extensive spam ops need to be done.
228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/imap/providers/generic.rb', line 228 def open_spam_mailbox(write: false) open_mailbox_before_spam = @open_mailbox_name open_mailbox_before_spam_write = @open_mailbox_write spam_uid_validity = open_mailbox(spam_mailbox, write: write)[:uid_validity] yield(spam_uid_validity) if block_given? open_mailbox(open_mailbox_before_spam, write: open_mailbox_before_spam_write) spam_uid_validity end |
#open_trash_mailbox(write: false) {|trash_uid_validity| ... } ⇒ Object
open the trash mailbox for inspection or writing. after the yield we close the trash and reopen the original mailbox to continue operations. the normal open_mailbox call can be made if more extensive trash ops need to be done.
212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/imap/providers/generic.rb', line 212 def open_trash_mailbox(write: false) open_mailbox_before_trash = @open_mailbox_name open_mailbox_before_trash_write = @open_mailbox_write trash_uid_validity = open_mailbox(trash_mailbox, write: write)[:uid_validity] yield(trash_uid_validity) if block_given? open_mailbox(open_mailbox_before_trash, write: open_mailbox_before_trash_write) trash_uid_validity end |
#spam_mailbox ⇒ Object
Look for the special Junk XLIST attribute.
200 201 202 203 204 205 206 |
# File 'lib/imap/providers/generic.rb', line 200 def spam_mailbox Discourse .cache .fetch("imap_spam_mailbox_#{account_digest}", expires_in: 30.minutes) do list_mailboxes(:Junk).first end end |
#store(uid, attribute, old_set, new_set) ⇒ Object
127 128 129 130 131 132 |
# File 'lib/imap/providers/generic.rb', line 127 def store(uid, attribute, old_set, new_set) additions = new_set.reject { |val| old_set.include?(val) } imap.uid_store(uid, "+#{attribute}", additions) if additions.length > 0 removals = old_set.reject { |val| new_set.include?(val) } imap.uid_store(uid, "-#{attribute}", removals) if removals.length > 0 end |
#tag_to_flag(tag) ⇒ Object
139 140 141 |
# File 'lib/imap/providers/generic.rb', line 139 def tag_to_flag(tag) :Seen if tag == "seen" end |
#tag_to_label(tag) ⇒ Object
143 144 145 |
# File 'lib/imap/providers/generic.rb', line 143 def tag_to_label(tag) tag end |
#to_tag(label) ⇒ Object
134 135 136 137 |
# File 'lib/imap/providers/generic.rb', line 134 def to_tag(label) label = DiscourseTagging.clean_tag(label.to_s) label if label != "inbox" && label != "sent" end |
#trash(uid) ⇒ Object
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/imap/providers/generic.rb', line 298 def trash(uid) # MOVE is way easier than doing the COPY \Deleted EXPUNGE dance ourselves. # It is supported by Gmail and Outlook. if can?("MOVE") trash_move(uid) else # default behaviour for IMAP servers is to add the \Deleted flag # then EXPUNGE the mailbox which permanently deletes these messages # https://tools.ietf.org/html/rfc3501#section-6.4.3 # # TODO: We may want to add the option at some point to copy to some # other mailbox first before doing this (e.g. Trash) store(uid, "FLAGS", [], ["\\Deleted"]) imap.expunge end end |
#trash_mailbox ⇒ Object
Look for the special Trash XLIST attribute.
191 192 193 194 195 196 197 |
# File 'lib/imap/providers/generic.rb', line 191 def trash_mailbox Discourse .cache .fetch("imap_trash_mailbox_#{account_digest}", expires_in: 30.minutes) do list_mailboxes(:Trash).first end end |
#trash_move(uid) ⇒ Object
315 316 317 |
# File 'lib/imap/providers/generic.rb', line 315 def trash_move(uid) # up to the provider end |
#uids(opts = {}) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/imap/providers/generic.rb', line 67 def uids(opts = {}) if opts[:from] && opts[:to] imap.uid_search("UID #{opts[:from]}:#{opts[:to]}") elsif opts[:from] imap.uid_search("UID #{opts[:from]}:*") elsif opts[:to] imap.uid_search("UID 1:#{opts[:to]}") else imap.uid_search("ALL") end end |
#unarchive(uid) ⇒ Object
186 187 188 |
# File 'lib/imap/providers/generic.rb', line 186 def unarchive(uid) # same as above end |