]> git.basschouten.com Git - openhab-addons.git/blob
a7cf802fca7398a233a20d142a86b6fefcbafffc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mail.internal;
14
15 import static org.openhab.binding.mail.internal.MailBindingConstants.CHANNEL_TYPE_UID_FOLDER_MAILCOUNT;
16 import static org.openhab.binding.mail.internal.MailBindingConstants.CHANNEL_TYPE_UID_MAIL_CONTENT;
17
18 import java.io.ByteArrayOutputStream;
19 import java.io.IOException;
20 import java.util.Properties;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import javax.mail.Address;
27 import javax.mail.Flags;
28 import javax.mail.Folder;
29 import javax.mail.Message;
30 import javax.mail.MessagingException;
31 import javax.mail.Session;
32 import javax.mail.Store;
33 import javax.mail.internet.MimeMessage;
34 import javax.mail.internet.MimeMultipart;
35 import javax.mail.search.FlagTerm;
36
37 import org.eclipse.jdt.annotation.NonNullByDefault;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.openhab.binding.mail.internal.config.POP3IMAPConfig;
40 import org.openhab.binding.mail.internal.config.POP3IMAPContentChannelConfig;
41 import org.openhab.binding.mail.internal.config.POP3IMAPMailCountChannelConfig;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.generic.ChannelTransformation;
51 import org.openhab.core.types.Command;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The {@link POP3IMAPHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Jan N. Klug - Initial contribution
60  */
61 @NonNullByDefault
62 public class POP3IMAPHandler extends BaseThingHandler {
63     private final Logger logger = LoggerFactory.getLogger(POP3IMAPHandler.class);
64
65     private @NonNullByDefault({}) POP3IMAPConfig config;
66     private @Nullable ScheduledFuture<?> refreshTask;
67     private final String baseProtocol;
68     private String protocol = "imap";
69
70     public POP3IMAPHandler(Thing thing) {
71         super(thing);
72         baseProtocol = thing.getThingTypeUID().getId(); // pop3 or imap
73     }
74
75     @Override
76     public void handleCommand(ChannelUID channelUID, Command command) {
77     }
78
79     @Override
80     public void initialize() {
81         config = getConfigAs(POP3IMAPConfig.class);
82
83         protocol = baseProtocol;
84
85         if (config.security == ServerSecurity.SSL) {
86             protocol = protocol.concat("s");
87         }
88
89         if (config.port == 0) {
90             switch (protocol) {
91                 case "imap" -> config.port = 143;
92                 case "imaps" -> config.port = 993;
93                 case "pop3" -> config.port = 110;
94                 case "pop3s" -> config.port = 995;
95                 default -> {
96                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
97                     return;
98                 }
99             }
100         }
101
102         refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.SECONDS);
103         updateStatus(ThingStatus.ONLINE);
104     }
105
106     @SuppressWarnings("null")
107     @Override
108     public void dispose() {
109         if (refreshTask != null) {
110             if (!refreshTask.isCancelled()) {
111                 refreshTask.cancel(true);
112             }
113         }
114     }
115
116     private void refresh() {
117         if (Thread.currentThread().isInterrupted()) {
118             return;
119         }
120         Properties props = new Properties();
121         props.setProperty("mail." + baseProtocol + ".starttls.enable", "true");
122         props.setProperty("mail.store.protocol", protocol);
123         Session session = Session.getInstance(props);
124
125         try (Store store = session.getStore()) {
126             store.connect(config.hostname, config.port, config.username, config.password);
127
128             for (Channel channel : thing.getChannels()) {
129                 if (CHANNEL_TYPE_UID_FOLDER_MAILCOUNT.equals(channel.getChannelTypeUID())) {
130                     final POP3IMAPMailCountChannelConfig channelConfig = channel.getConfiguration()
131                             .as(POP3IMAPMailCountChannelConfig.class);
132                     final String folderName = channelConfig.folder;
133                     if (folderName == null || folderName.isEmpty()) {
134                         logger.info("missing or empty folder name in channel {}", channel.getUID());
135                     } else {
136                         try (Folder mailbox = store.getFolder(folderName)) {
137                             mailbox.open(Folder.READ_ONLY);
138                             if (channelConfig.type == MailCountChannelType.TOTAL) {
139                                 updateState(channel.getUID(), new DecimalType(mailbox.getMessageCount()));
140                             } else {
141                                 updateState(channel.getUID(), new DecimalType(
142                                         mailbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false)).length));
143                             }
144                         }
145                     }
146                 } else if (CHANNEL_TYPE_UID_MAIL_CONTENT.equals(channel.getChannelTypeUID())) {
147                     final POP3IMAPContentChannelConfig channelConfig = channel.getConfiguration()
148                             .as(POP3IMAPContentChannelConfig.class);
149                     final String folderName = channelConfig.folder;
150                     if (folderName == null || folderName.isEmpty()) {
151                         logger.info("missing or empty folder name in channel '{}'", channel.getUID());
152                     } else {
153                         try (Folder mailbox = store.getFolder(folderName)) {
154                             mailbox.open(channelConfig.markAsRead ? Folder.READ_WRITE : Folder.READ_ONLY);
155                             Message[] messages = mailbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
156                             for (Message message : messages) {
157                                 String subject = message.getSubject();
158                                 Address[] senders = message.getFrom();
159                                 String sender = senders == null ? ""
160                                         : Stream.of(senders).map(Address::toString).collect(Collectors.joining(","));
161                                 logger.debug("Processing `{}` from `{}`", subject, sender);
162                                 if (!channelConfig.subject.isBlank() && !subject.matches(channelConfig.subject)) {
163                                     logger.trace("Subject '{}' did not pass subject filter", subject);
164                                     continue;
165                                 }
166                                 if (!channelConfig.sender.isBlank() && !sender.matches(channelConfig.sender)) {
167                                     logger.trace("Sender '{}' did not pass filter '{}'", subject, channelConfig.sender);
168                                     continue;
169                                 }
170                                 Object rawContent = message.getContent();
171                                 String contentAsString;
172                                 if (rawContent instanceof String str) {
173                                     logger.trace("Detected plain text message");
174                                     contentAsString = str;
175                                 } else if (rawContent instanceof MimeMessage mimeMessage) {
176                                     logger.trace("Detected MIME message");
177                                     try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
178                                         mimeMessage.writeTo(os);
179                                         contentAsString = os.toString();
180                                     }
181                                 } else if (rawContent instanceof MimeMultipart mimeMultipart) {
182                                     logger.trace("Detected MIME multipart message");
183                                     try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
184                                         mimeMultipart.writeTo(os);
185                                         contentAsString = os.toString();
186                                     }
187                                 } else {
188                                     logger.warn(
189                                             "Failed to convert mail content from '{}' with subject '{}', to String: {}",
190                                             sender, subject, rawContent.getClass());
191                                     continue;
192                                 }
193                                 logger.trace("Found content '{}'", contentAsString);
194                                 new ChannelTransformation(channelConfig.transformation).apply(contentAsString)
195                                         .ifPresent(result -> updateState(channel.getUID(), new StringType(result)));
196                             }
197                         }
198                     }
199                 }
200             }
201         } catch (MessagingException | IOException e) {
202             logger.info("Failed refreshing IMAP for thing '{}': {}", thing.getUID(), e.getMessage());
203         }
204     }
205 }