mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-02-04 19:34:05 +01:00
[openhabcloud] Enhanced Notification Actions (#16938)
* [openhabcloud] Enhanced Notification Actions Co-authored-by: Florian Hotze <florianh_dev@icloud.com> Signed-off-by: Dan Cunningham <dan@digitaldan.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
45bbaa3d79
commit
19ffa56e31
@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Victor Belov - Initial contribution
|
* @author Victor Belov - Initial contribution
|
||||||
* @author Kai Kreuzer - migrated code to ESH APIs
|
* @author Kai Kreuzer - migrated code to ESH APIs
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NotificationAction {
|
public class NotificationAction {
|
||||||
@ -57,9 +58,32 @@ public class NotificationAction {
|
|||||||
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
||||||
public static void sendNotification(String userId, String message, @Nullable String icon,
|
public static void sendNotification(String userId, String message, @Nullable String icon,
|
||||||
@Nullable String severity) {
|
@Nullable String severity) {
|
||||||
|
sendNotification(userId, message, icon, severity, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an advanced push notification to mobile devices of user
|
||||||
|
*
|
||||||
|
* @param userId the cloud user id of the recipient
|
||||||
|
* @param message the body of the notification
|
||||||
|
* @param icon name for the notification
|
||||||
|
* @param severity category for the notification
|
||||||
|
* @param title for the notification
|
||||||
|
* @param onClickAction the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the media to attach to a notification
|
||||||
|
* @param actionButton1 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 an action button in the format "Title=Action"
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
||||||
|
public static void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
logger.debug("sending notification '{}' to user {}", message, userId);
|
logger.debug("sending notification '{}' to user {}", message, userId);
|
||||||
if (cloudService != null) {
|
if (cloudService != null) {
|
||||||
cloudService.sendNotification(userId, message, icon, severity);
|
cloudService.sendNotification(userId, message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,9 +139,32 @@ public class NotificationAction {
|
|||||||
*/
|
*/
|
||||||
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
||||||
public static void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
|
public static void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
|
||||||
|
sendBroadcastNotification(message, icon, severity, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an advanced broadcast notification. Broadcast notifications are pushed to all
|
||||||
|
* mobile devices of all users of the account
|
||||||
|
*
|
||||||
|
* @param message the body of the notification
|
||||||
|
* @param icon name for the notification
|
||||||
|
* @param severity category for the notification
|
||||||
|
* @param title for the notification
|
||||||
|
* @param onClickAction the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the media to attach to a notification
|
||||||
|
* @param actionButton1 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 an action button in the format "Title=Action"
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
|
||||||
|
public static void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
logger.debug("sending broadcast notification '{}' to all users", message);
|
logger.debug("sending broadcast notification '{}' to all users", message);
|
||||||
if (cloudService != null) {
|
if (cloudService != null) {
|
||||||
cloudService.sendBroadcastNotification(message, icon, severity);
|
cloudService.sendBroadcastNotification(message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.openhab.core.OpenHAB;
|
import org.openhab.core.OpenHAB;
|
||||||
@ -70,6 +71,7 @@ import okhttp3.logging.HttpLoggingInterceptor.Level;
|
|||||||
*
|
*
|
||||||
* @author Victor Belov - Initial contribution
|
* @author Victor Belov - Initial contribution
|
||||||
* @author Kai Kreuzer - migrated code to new Jetty client and ESH APIs
|
* @author Kai Kreuzer - migrated code to new Jetty client and ESH APIs
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
public class CloudClient {
|
public class CloudClient {
|
||||||
|
|
||||||
@ -587,16 +589,67 @@ public class CloudClient {
|
|||||||
* @param message notification message text
|
* @param message notification message text
|
||||||
* @param icon name of the icon for this notification
|
* @param icon name of the icon for this notification
|
||||||
* @param severity severity name for this notification
|
* @param severity severity name for this notification
|
||||||
|
* @param title for the notification
|
||||||
|
* @param onClickAction the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the media to attach to a notification
|
||||||
|
* @param actionButton1 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 an action button in the format "Title=Action"
|
||||||
*/
|
*/
|
||||||
public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity) {
|
public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
|
sendNotificationInternal(userId, message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sends broadcast notification to the openHAB Cloud
|
||||||
|
*
|
||||||
|
* @param message notification message text
|
||||||
|
* @param icon name of the icon for this notification
|
||||||
|
* @param severity severity name for this notification
|
||||||
|
* @param title for this notification
|
||||||
|
* @param onClickAction the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the media to attach to a notification
|
||||||
|
* @param actionButton1 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 an action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 an action button in the format "Title=Action"
|
||||||
|
*/
|
||||||
|
public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
|
sendNotificationInternal(null, message, icon, severity, title, onClickAction, mediaAttachmentUrl, actionButton1,
|
||||||
|
actionButton2, actionButton3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNotificationInternal(@Nullable String userId, String message, @Nullable String icon,
|
||||||
|
@Nullable String severity, @Nullable String title, @Nullable String onClickAction,
|
||||||
|
@Nullable String mediaAttachmentUrl, @Nullable String actionButton1, @Nullable String actionButton2,
|
||||||
|
@Nullable String actionButton3) {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
JSONObject notificationMessage = new JSONObject();
|
JSONObject notificationMessage = new JSONObject();
|
||||||
try {
|
try {
|
||||||
notificationMessage.put("userId", userId);
|
if (userId != null) {
|
||||||
|
notificationMessage.put("userId", userId);
|
||||||
|
}
|
||||||
notificationMessage.put("message", message);
|
notificationMessage.put("message", message);
|
||||||
notificationMessage.put("icon", icon);
|
notificationMessage.put("icon", icon);
|
||||||
notificationMessage.put("severity", severity);
|
notificationMessage.put("severity", severity);
|
||||||
socket.emit("notification", notificationMessage);
|
if (title != null) {
|
||||||
|
notificationMessage.put("title", title);
|
||||||
|
}
|
||||||
|
if (onClickAction != null) {
|
||||||
|
notificationMessage.put("on-click", onClickAction);
|
||||||
|
}
|
||||||
|
if (mediaAttachmentUrl != null) {
|
||||||
|
notificationMessage.put("media-attachment-url", mediaAttachmentUrl);
|
||||||
|
}
|
||||||
|
JSONArray actionArray = createActionArray(actionButton1, actionButton2, actionButton3);
|
||||||
|
if (!actionArray.isEmpty()) {
|
||||||
|
notificationMessage.put("actions", actionArray);
|
||||||
|
}
|
||||||
|
socket.emit(userId == null ? "broadcastnotification" : "notification", notificationMessage);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
logger.debug("{}", e.getMessage());
|
logger.debug("{}", e.getMessage());
|
||||||
}
|
}
|
||||||
@ -628,29 +681,6 @@ public class CloudClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method sends broadcast notification to the openHAB Cloud
|
|
||||||
*
|
|
||||||
* @param message notification message text
|
|
||||||
* @param icon name of the icon for this notification
|
|
||||||
* @param severity severity name for this notification
|
|
||||||
*/
|
|
||||||
public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
|
|
||||||
if (isConnected()) {
|
|
||||||
JSONObject notificationMessage = new JSONObject();
|
|
||||||
try {
|
|
||||||
notificationMessage.put("message", message);
|
|
||||||
notificationMessage.put("icon", icon);
|
|
||||||
notificationMessage.put("severity", severity);
|
|
||||||
socket.emit("broadcastnotification", notificationMessage);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
logger.debug("{}", e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("No connection, notification is not sent");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send item update to openHAB Cloud
|
* Send item update to openHAB Cloud
|
||||||
*
|
*
|
||||||
@ -715,6 +745,23 @@ public class CloudClient {
|
|||||||
return headersJSON;
|
return headersJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JSONArray createActionArray(@Nullable String... actionStrings) {
|
||||||
|
JSONArray actionArray = new JSONArray();
|
||||||
|
for (String actionString : actionStrings) {
|
||||||
|
if (actionString == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] parts = actionString.split("=", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
JSONObject action = new JSONObject();
|
||||||
|
action.put("title", parts[0]);
|
||||||
|
action.put("action", parts[1]);
|
||||||
|
actionArray.put(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionArray;
|
||||||
|
}
|
||||||
|
|
||||||
private static String censored(String secret) {
|
private static String censored(String secret) {
|
||||||
if (secret.length() < 4) {
|
if (secret.length() < 4) {
|
||||||
return "*******";
|
return "*******";
|
||||||
|
@ -63,6 +63,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Victor Belov - Initial contribution
|
* @author Victor Belov - Initial contribution
|
||||||
* @author Kai Kreuzer - migrated code to new Jetty client and ESH APIs
|
* @author Kai Kreuzer - migrated code to new Jetty client and ESH APIs
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@Component(service = { CloudService.class, EventSubscriber.class,
|
@Component(service = { CloudService.class, EventSubscriber.class,
|
||||||
ActionService.class }, configurationPid = "org.openhab.openhabcloud", property = Constants.SERVICE_PID
|
ActionService.class }, configurationPid = "org.openhab.openhabcloud", property = Constants.SERVICE_PID
|
||||||
@ -114,10 +115,19 @@ public class CloudService implements ActionService, CloudClientListener, EventSu
|
|||||||
* @param message the {@link String} containing a message to send to specified user id
|
* @param message the {@link String} containing a message to send to specified user id
|
||||||
* @param icon the {@link String} containing a name of the icon to be used with this notification
|
* @param icon the {@link String} containing a name of the icon to be used with this notification
|
||||||
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
|
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
|
||||||
|
* @param title the {@link String} containing the title to be used with this notification
|
||||||
|
* @param onClickAction the {@link String} containing the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the {@link String} containing the media to attach to a notification
|
||||||
|
* @param actionButton1 the {@link String} containing the action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 the {@link String} containing the action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 the {@link String} containing the action button in the format "Title=Action"
|
||||||
*/
|
*/
|
||||||
public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity) {
|
public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
logger.debug("Sending message '{}' to user id {}", message, userId);
|
logger.debug("Sending message '{}' to user id {}", message, userId);
|
||||||
cloudClient.sendNotification(userId, message, icon, severity);
|
cloudClient.sendNotification(userId, message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,10 +150,19 @@ public class CloudService implements ActionService, CloudClientListener, EventSu
|
|||||||
* @param message the {@link String} containing a message to send to specified user id
|
* @param message the {@link String} containing a message to send to specified user id
|
||||||
* @param icon the {@link String} containing a name of the icon to be used with this notification
|
* @param icon the {@link String} containing a name of the icon to be used with this notification
|
||||||
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
|
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
|
||||||
|
* @param title the {@link String} containing the title to be used with this notification
|
||||||
|
* @param onClickAction the {@link String} containing the action to perform when clicked
|
||||||
|
* @param mediaAttachmentUrl the {@link String} containing the media to attach to a notification
|
||||||
|
* @param actionButton1 the {@link String} containing the action button in the format "Title=Action"
|
||||||
|
* @param actionButton2 the {@link String} containing the action button in the format "Title=Action"
|
||||||
|
* @param actionButton3 the {@link String} containing the action button in the format "Title=Action"
|
||||||
*/
|
*/
|
||||||
public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
|
public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity,
|
||||||
|
@Nullable String title, @Nullable String onClickAction, @Nullable String mediaAttachmentUrl,
|
||||||
|
@Nullable String actionButton1, @Nullable String actionButton2, @Nullable String actionButton3) {
|
||||||
logger.debug("Sending broadcast message '{}' to all users", message);
|
logger.debug("Sending broadcast message '{}' to all users", message);
|
||||||
cloudClient.sendBroadcastNotification(message, icon, severity);
|
cloudClient.sendBroadcastNotification(message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String substringBefore(String str, String separator) {
|
private String substringBefore(String str, String separator) {
|
||||||
|
@ -17,12 +17,14 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
import org.openhab.core.automation.Action;
|
import org.openhab.core.automation.Action;
|
||||||
import org.openhab.core.automation.handler.BaseActionModuleHandler;
|
import org.openhab.core.automation.handler.BaseActionModuleHandler;
|
||||||
import org.openhab.core.automation.handler.ModuleHandler;
|
import org.openhab.core.automation.handler.ModuleHandler;
|
||||||
|
import org.openhab.core.config.core.ConfigParser;
|
||||||
import org.openhab.io.openhabcloud.internal.CloudService;
|
import org.openhab.io.openhabcloud.internal.CloudService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a base {@link ModuleHandler} implementation for {@link Action}s to send a notifications via openHAB Cloud.
|
* This is a base {@link ModuleHandler} implementation for {@link Action}s to send a notifications via openHAB Cloud.
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class BaseNotificationActionHandler extends BaseActionModuleHandler {
|
public abstract class BaseNotificationActionHandler extends BaseActionModuleHandler {
|
||||||
@ -30,12 +32,24 @@ public abstract class BaseNotificationActionHandler extends BaseActionModuleHand
|
|||||||
public static final String PARAM_MESSAGE = "message";
|
public static final String PARAM_MESSAGE = "message";
|
||||||
public static final String PARAM_ICON = "icon";
|
public static final String PARAM_ICON = "icon";
|
||||||
public static final String PARAM_SEVERITY = "severity";
|
public static final String PARAM_SEVERITY = "severity";
|
||||||
|
public static final String PARAM_TITLE = "title";
|
||||||
|
public static final String PARAM_ON_CLICK_ACTION = "onClickAction";
|
||||||
|
public static final String PARAM_MEDIA_ATTACHMENT_URL = "mediaAttachmentUrl";
|
||||||
|
public static final String PARAM_ACTION_BUTTON_1 = "actionButton1";
|
||||||
|
public static final String PARAM_ACTION_BUTTON_2 = "actionButton2";
|
||||||
|
public static final String PARAM_ACTION_BUTTON_3 = "actionButton3";
|
||||||
|
|
||||||
protected final CloudService cloudService;
|
protected final CloudService cloudService;
|
||||||
|
|
||||||
protected final String message;
|
protected final String message;
|
||||||
protected final @Nullable String icon;
|
protected final @Nullable String icon;
|
||||||
protected final @Nullable String severity;
|
protected final @Nullable String severity;
|
||||||
|
protected final @Nullable String title;
|
||||||
|
protected final @Nullable String onClickAction;
|
||||||
|
protected final @Nullable String mediaAttachmentUrl;
|
||||||
|
protected final @Nullable String actionButton1;
|
||||||
|
protected final @Nullable String actionButton2;
|
||||||
|
protected final @Nullable String actionButton3;
|
||||||
|
|
||||||
public BaseNotificationActionHandler(Action module, CloudService cloudService) {
|
public BaseNotificationActionHandler(Action module, CloudService cloudService) {
|
||||||
super(module);
|
super(module);
|
||||||
@ -48,10 +62,17 @@ public abstract class BaseNotificationActionHandler extends BaseActionModuleHand
|
|||||||
throw new IllegalArgumentException(String.format("Param '%s' should be of type String.", PARAM_MESSAGE));
|
throw new IllegalArgumentException(String.format("Param '%s' should be of type String.", PARAM_MESSAGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
Object iconParam = module.getConfiguration().get(PARAM_ICON);
|
this.icon = stringConfig(PARAM_ICON);
|
||||||
this.icon = iconParam instanceof String ? iconParam.toString() : null;
|
this.severity = stringConfig(PARAM_SEVERITY);
|
||||||
|
this.title = stringConfig(PARAM_TITLE);
|
||||||
|
this.onClickAction = stringConfig(PARAM_ON_CLICK_ACTION);
|
||||||
|
this.mediaAttachmentUrl = stringConfig(PARAM_MEDIA_ATTACHMENT_URL);
|
||||||
|
this.actionButton1 = stringConfig(PARAM_ACTION_BUTTON_1);
|
||||||
|
this.actionButton2 = stringConfig(PARAM_ACTION_BUTTON_2);
|
||||||
|
this.actionButton3 = stringConfig(PARAM_ACTION_BUTTON_3);
|
||||||
|
}
|
||||||
|
|
||||||
Object severityParam = module.getConfiguration().get(PARAM_SEVERITY);
|
private @Nullable String stringConfig(String key) {
|
||||||
this.severity = severityParam instanceof String ? severityParam.toString() : null;
|
return ConfigParser.valueAs(module.getConfiguration().get(key), String.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,41 +34,62 @@ import org.osgi.service.component.annotations.Component;
|
|||||||
* openHAB Cloud.
|
* openHAB Cloud.
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = ModuleTypeProvider.class)
|
@Component(service = ModuleTypeProvider.class)
|
||||||
public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
||||||
|
|
||||||
private static final ModuleType SEND_NOTIFICATION_ACTION = new ActionType(SendNotificationActionHandler.TYPE_ID,
|
private static final ModuleType SEND_NOTIFICATION_ACTION = new ActionType(SendNotificationActionHandler.TYPE_ID,
|
||||||
getSendNotificationConfig(false, null), "send a notification",
|
getNotificationConfig(ConfigType.NOT_EXTENDED, true, null), "send a notification",
|
||||||
"Sends a notification to a specific cloud user.", null, Visibility.VISIBLE, null, null);
|
"Sends a notification to a specific cloud user.", null, Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
private static final ModuleType SEND_EXTENDED_NOTIFICATION_ACTION = new ActionType(
|
private static final ModuleType SEND_EXTENDED_NOTIFICATION_ACTION = new ActionType(
|
||||||
SendNotificationActionHandler.EXTENDED_TYPE_ID, getSendNotificationConfig(true, null),
|
SendNotificationActionHandler.EXTENDED_TYPE_ID, getNotificationConfig(ConfigType.EXTENDED, true, null),
|
||||||
"send a notification with icon and severity",
|
"send a notification with icon and severity",
|
||||||
"Sends a notification to a specific cloud user. Optionally add an icon or the severity.", null,
|
"Sends a notification to a specific cloud user. Optionally add an icon or the severity.", null,
|
||||||
Visibility.VISIBLE, null, null);
|
Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
|
private static final ModuleType SEND_EXTENDED2_NOTIFICATION_ACTION = new ActionType(
|
||||||
|
SendNotificationActionHandler.EXTENDED2_TYPE_ID, getNotificationConfig(ConfigType.EXTENDED2, true, null),
|
||||||
|
"send a notification with icon, severity, title, click action, media attachment and action buttons",
|
||||||
|
"Sends a notification to a specific cloud user. Optionally add an icon, severity, title, on click action, media to attach, and up to 3 action buttons with a format of \"Title=Action\".",
|
||||||
|
null, Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
private static final ModuleType SEND_BROADCAST_NOTIFICATION_ACTION = new ActionType(
|
private static final ModuleType SEND_BROADCAST_NOTIFICATION_ACTION = new ActionType(
|
||||||
SendBroadcastNotificationActionHandler.TYPE_ID, getNotificationConfig(false, null),
|
SendBroadcastNotificationActionHandler.TYPE_ID, getNotificationConfig(ConfigType.NOT_EXTENDED, true, null),
|
||||||
"broadcast a notification", "Sends a notification to all devices of all users.", null, Visibility.VISIBLE,
|
"broadcast a notification", "Sends a notification to all devices of all users.", null, Visibility.VISIBLE,
|
||||||
null, null);
|
null, null);
|
||||||
private static final ModuleType SEND_EXRENDED_BROADCAST_NOTIFICATION_ACTION = new ActionType(
|
|
||||||
SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID, getNotificationConfig(true, null),
|
private static final ModuleType SEND_EXTENDED_BROADCAST_NOTIFICATION_ACTION = new ActionType(
|
||||||
"broadcast a notification with icon and severity",
|
SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID,
|
||||||
|
getNotificationConfig(ConfigType.EXTENDED, false, null), "broadcast a notification with icon and severity",
|
||||||
"Sends a notification to all devices of all users. Optionally add an icon or the severity.", null,
|
"Sends a notification to all devices of all users. Optionally add an icon or the severity.", null,
|
||||||
Visibility.VISIBLE, null, null);
|
Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
|
private static final ModuleType SEND_EXTENDED2_BROADCAST_NOTIFICATION_ACTION = new ActionType(
|
||||||
|
SendBroadcastNotificationActionHandler.EXTENDED2_TYPE_ID,
|
||||||
|
getNotificationConfig(ConfigType.EXTENDED2, false, null),
|
||||||
|
"broadcast a notification with with icon, severity, title, on click action, media attachment and action buttons",
|
||||||
|
"Sends a notification to all devices of all users. Optionally add an icon, severity, title, click action, media to attach, and up to 3 action buttons with a format of \"Title=Action\".",
|
||||||
|
null, Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
private static final ModuleType SEND_LOG_NOTIFICATION_ACTION = new ActionType(
|
private static final ModuleType SEND_LOG_NOTIFICATION_ACTION = new ActionType(
|
||||||
SendLogNotificationActionHandler.TYPE_ID, getNotificationConfig(false, null), "send a log message",
|
SendLogNotificationActionHandler.TYPE_ID, getNotificationConfig(ConfigType.NOT_EXTENDED, false, null),
|
||||||
|
"send a log message",
|
||||||
"Sends a log notification to the openHAB Cloud instance. Notifications are NOT sent to any registered devices.",
|
"Sends a log notification to the openHAB Cloud instance. Notifications are NOT sent to any registered devices.",
|
||||||
null, Visibility.VISIBLE, null, null);
|
null, Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
private static final ModuleType SEND_EXTENDED_LOG_NOTIFICATION_ACTION = new ActionType(
|
private static final ModuleType SEND_EXTENDED_LOG_NOTIFICATION_ACTION = new ActionType(
|
||||||
SendLogNotificationActionHandler.EXTENDED_TYPE_ID, getNotificationConfig(true, null),
|
SendLogNotificationActionHandler.EXTENDED_TYPE_ID, getNotificationConfig(ConfigType.EXTENDED, false, null),
|
||||||
"send a log message with icon and severity",
|
"send a log message with icon and severity",
|
||||||
"Sends a log notification to the openHAB Cloud instance. Optionally add an icon or the severity. Notifications are NOT sent to any registered devices.",
|
"Sends a log notification to the openHAB Cloud instance. Optionally add an icon or the severity. Notifications are NOT sent to any registered devices.",
|
||||||
null, Visibility.VISIBLE, null, null);
|
null, Visibility.VISIBLE, null, null);
|
||||||
|
|
||||||
private static final List<ModuleType> MODULE_TYPES = List.of(SEND_NOTIFICATION_ACTION,
|
private static final List<ModuleType> MODULE_TYPES = List.of(SEND_NOTIFICATION_ACTION,
|
||||||
SEND_EXTENDED_NOTIFICATION_ACTION, SEND_BROADCAST_NOTIFICATION_ACTION,
|
SEND_EXTENDED_NOTIFICATION_ACTION, SEND_EXTENDED2_NOTIFICATION_ACTION, SEND_BROADCAST_NOTIFICATION_ACTION,
|
||||||
SEND_EXRENDED_BROADCAST_NOTIFICATION_ACTION, SEND_LOG_NOTIFICATION_ACTION,
|
SEND_EXTENDED_BROADCAST_NOTIFICATION_ACTION, SEND_EXTENDED2_BROADCAST_NOTIFICATION_ACTION,
|
||||||
SEND_EXTENDED_LOG_NOTIFICATION_ACTION);
|
SEND_LOG_NOTIFICATION_ACTION, SEND_EXTENDED_LOG_NOTIFICATION_ACTION);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
@ -78,10 +99,14 @@ public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
|||||||
return SEND_NOTIFICATION_ACTION;
|
return SEND_NOTIFICATION_ACTION;
|
||||||
case SendNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
return SEND_EXTENDED_NOTIFICATION_ACTION;
|
return SEND_EXTENDED_NOTIFICATION_ACTION;
|
||||||
|
case SendNotificationActionHandler.EXTENDED2_TYPE_ID:
|
||||||
|
return SEND_EXTENDED2_NOTIFICATION_ACTION;
|
||||||
case SendBroadcastNotificationActionHandler.TYPE_ID:
|
case SendBroadcastNotificationActionHandler.TYPE_ID:
|
||||||
return SEND_BROADCAST_NOTIFICATION_ACTION;
|
return SEND_BROADCAST_NOTIFICATION_ACTION;
|
||||||
case SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
return SEND_EXRENDED_BROADCAST_NOTIFICATION_ACTION;
|
return SEND_EXTENDED_BROADCAST_NOTIFICATION_ACTION;
|
||||||
|
case SendBroadcastNotificationActionHandler.EXTENDED2_TYPE_ID:
|
||||||
|
return SEND_EXTENDED2_BROADCAST_NOTIFICATION_ACTION;
|
||||||
case SendLogNotificationActionHandler.TYPE_ID:
|
case SendLogNotificationActionHandler.TYPE_ID:
|
||||||
return SEND_LOG_NOTIFICATION_ACTION;
|
return SEND_LOG_NOTIFICATION_ACTION;
|
||||||
case SendLogNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendLogNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
@ -101,23 +126,30 @@ public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
|||||||
return MODULE_TYPES;
|
return MODULE_TYPES;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ConfigDescriptionParameter> getSendNotificationConfig(boolean isExtended,
|
private static List<ConfigDescriptionParameter> getNotificationConfig(ConfigType type, boolean userRequired,
|
||||||
@Nullable Locale locale) {
|
@Nullable Locale locale) {
|
||||||
List<ConfigDescriptionParameter> params = new ArrayList<>();
|
List<ConfigDescriptionParameter> params = new ArrayList<>();
|
||||||
params.add(ConfigDescriptionParameterBuilder.create(SendNotificationActionHandler.PARAM_USER, Type.TEXT)
|
|
||||||
.withRequired(true).withLabel("User Id").withDescription("The cloud user id of the recipient.")
|
|
||||||
.build());
|
|
||||||
params.addAll(getNotificationConfig(isExtended, locale));
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ConfigDescriptionParameter> getNotificationConfig(boolean isExtended, @Nullable Locale locale) {
|
if (userRequired) {
|
||||||
List<ConfigDescriptionParameter> params = new ArrayList<>();
|
params.add(ConfigDescriptionParameterBuilder.create(SendNotificationActionHandler.PARAM_USER, Type.TEXT)
|
||||||
params.add(getMessageConfigParameter(locale));
|
.withRequired(true).withLabel("User Id").withDescription("The cloud user id of the recipient.")
|
||||||
if (isExtended) {
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == ConfigType.EXTENDED || type == ConfigType.EXTENDED2) {
|
||||||
|
params.add(getMessageConfigParameter(locale));
|
||||||
params.add(getIconConfigParameter(locale));
|
params.add(getIconConfigParameter(locale));
|
||||||
params.add(getSeverityConfigParameter(locale));
|
params.add(getSeverityConfigParameter(locale));
|
||||||
}
|
}
|
||||||
|
if (type == ConfigType.EXTENDED2) {
|
||||||
|
params.add(getTitleConfigParameter(locale));
|
||||||
|
params.add(getonClickActionConfigParameter(locale));
|
||||||
|
params.add(getMediaAttachmentUrlConfigParameter(locale));
|
||||||
|
params.add(getActionButton1ConfigParameter(locale));
|
||||||
|
params.add(getActionButton2ConfigParameter(locale));
|
||||||
|
params.add(getActionButton3ConfigParameter(locale));
|
||||||
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +168,40 @@ public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
|||||||
.withLabel("Severity").withDescription("The severity of the notification.").build();
|
.withLabel("Severity").withDescription("The severity of the notification.").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getTitleConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder.create(BaseNotificationActionHandler.PARAM_TITLE, Type.TEXT)
|
||||||
|
.withLabel("Title").withDescription("The title of the notification.").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getonClickActionConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder.create(BaseNotificationActionHandler.PARAM_ON_CLICK_ACTION, Type.TEXT)
|
||||||
|
.withLabel("On Click Action").withDescription("The action to perform when clicked.").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getMediaAttachmentUrlConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder
|
||||||
|
.create(BaseNotificationActionHandler.PARAM_MEDIA_ATTACHMENT_URL, Type.TEXT)
|
||||||
|
.withLabel("Media Attachment URL").withDescription("The media to attach to a notification.").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getActionButton1ConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder.create(BaseNotificationActionHandler.PARAM_ACTION_BUTTON_1, Type.TEXT)
|
||||||
|
.withLabel("Action Button 1").withDescription("An action button in the format \"Title=Action\".")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getActionButton2ConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder.create(BaseNotificationActionHandler.PARAM_ACTION_BUTTON_2, Type.TEXT)
|
||||||
|
.withLabel("Action Button 2").withDescription("An action button in the format \"Title=Action\".")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigDescriptionParameter getActionButton3ConfigParameter(@Nullable Locale locale) {
|
||||||
|
return ConfigDescriptionParameterBuilder.create(BaseNotificationActionHandler.PARAM_ACTION_BUTTON_3, Type.TEXT)
|
||||||
|
.withLabel("Action Button 3").withDescription("An action button in the format \"Title=Action\".")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||||
// does nothing because this provider does not change
|
// does nothing because this provider does not change
|
||||||
@ -145,4 +211,10 @@ public class NotificationActionTypeProvider implements ModuleTypeProvider {
|
|||||||
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||||
// does nothing because this provider does not change
|
// does nothing because this provider does not change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum ConfigType {
|
||||||
|
NOT_EXTENDED,
|
||||||
|
EXTENDED,
|
||||||
|
EXTENDED2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,16 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
* openHAB Cloud.
|
* openHAB Cloud.
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = ModuleHandlerFactory.class)
|
@Component(service = ModuleHandlerFactory.class)
|
||||||
public class NotificationModuleHandlerFactory extends BaseModuleHandlerFactory {
|
public class NotificationModuleHandlerFactory extends BaseModuleHandlerFactory {
|
||||||
|
|
||||||
private static final Collection<String> TYPES = List.of(SendNotificationActionHandler.TYPE_ID,
|
private static final Collection<String> TYPES = List.of(SendNotificationActionHandler.TYPE_ID,
|
||||||
SendNotificationActionHandler.EXTENDED_TYPE_ID, SendBroadcastNotificationActionHandler.TYPE_ID,
|
SendNotificationActionHandler.EXTENDED_TYPE_ID, SendNotificationActionHandler.EXTENDED2_TYPE_ID,
|
||||||
SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID, SendLogNotificationActionHandler.TYPE_ID,
|
SendBroadcastNotificationActionHandler.TYPE_ID, SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID,
|
||||||
|
SendBroadcastNotificationActionHandler.EXTENDED2_TYPE_ID, SendLogNotificationActionHandler.TYPE_ID,
|
||||||
SendLogNotificationActionHandler.EXTENDED_TYPE_ID);
|
SendLogNotificationActionHandler.EXTENDED_TYPE_ID);
|
||||||
private final CloudService cloudService;
|
private final CloudService cloudService;
|
||||||
|
|
||||||
@ -66,9 +68,11 @@ public class NotificationModuleHandlerFactory extends BaseModuleHandlerFactory {
|
|||||||
switch (module.getTypeUID()) {
|
switch (module.getTypeUID()) {
|
||||||
case SendNotificationActionHandler.TYPE_ID:
|
case SendNotificationActionHandler.TYPE_ID:
|
||||||
case SendNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
|
case SendNotificationActionHandler.EXTENDED2_TYPE_ID:
|
||||||
return new SendNotificationActionHandler(action, cloudService);
|
return new SendNotificationActionHandler(action, cloudService);
|
||||||
case SendBroadcastNotificationActionHandler.TYPE_ID:
|
case SendBroadcastNotificationActionHandler.TYPE_ID:
|
||||||
case SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendBroadcastNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
|
case SendBroadcastNotificationActionHandler.EXTENDED2_TYPE_ID:
|
||||||
return new SendBroadcastNotificationActionHandler(action, cloudService);
|
return new SendBroadcastNotificationActionHandler(action, cloudService);
|
||||||
case SendLogNotificationActionHandler.TYPE_ID:
|
case SendLogNotificationActionHandler.TYPE_ID:
|
||||||
case SendLogNotificationActionHandler.EXTENDED_TYPE_ID:
|
case SendLogNotificationActionHandler.EXTENDED_TYPE_ID:
|
||||||
|
@ -25,12 +25,14 @@ import org.openhab.io.openhabcloud.internal.CloudService;
|
|||||||
* cloud users.
|
* cloud users.
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SendBroadcastNotificationActionHandler extends BaseNotificationActionHandler {
|
public class SendBroadcastNotificationActionHandler extends BaseNotificationActionHandler {
|
||||||
|
|
||||||
public static final String TYPE_ID = "notification.SendBroadcastNotification";
|
public static final String TYPE_ID = "notification.SendBroadcastNotification";
|
||||||
public static final String EXTENDED_TYPE_ID = "notification.SendExtendedBroadcastNotification";
|
public static final String EXTENDED_TYPE_ID = "notification.SendExtendedBroadcastNotification";
|
||||||
|
public static final String EXTENDED2_TYPE_ID = "notification.SendExtended2BroadcastNotification";
|
||||||
|
|
||||||
public SendBroadcastNotificationActionHandler(Action module, CloudService cloudService) {
|
public SendBroadcastNotificationActionHandler(Action module, CloudService cloudService) {
|
||||||
super(module, cloudService);
|
super(module, cloudService);
|
||||||
@ -38,7 +40,8 @@ public class SendBroadcastNotificationActionHandler extends BaseNotificationActi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
|
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
|
||||||
cloudService.sendBroadcastNotification(message, icon, severity);
|
cloudService.sendBroadcastNotification(message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,15 @@ import org.openhab.io.openhabcloud.internal.CloudService;
|
|||||||
* This is a {@link ModuleHandler} implementation for {@link Action}s to send a notification to a specific cloud user.
|
* This is a {@link ModuleHandler} implementation for {@link Action}s to send a notification to a specific cloud user.
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
* @author Dan Cunningham - Extended notification enhancements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SendNotificationActionHandler extends BaseNotificationActionHandler {
|
public class SendNotificationActionHandler extends BaseNotificationActionHandler {
|
||||||
|
|
||||||
public static final String TYPE_ID = "notification.SendNotification";
|
public static final String TYPE_ID = "notification.SendNotification";
|
||||||
public static final String EXTENDED_TYPE_ID = "notification.SendExtendedNotification";
|
public static final String EXTENDED_TYPE_ID = "notification.SendExtendedNotification";
|
||||||
|
public static final String EXTENDED2_TYPE_ID = "notification.SendExtended2Notification";
|
||||||
|
|
||||||
public static final String PARAM_USER = "userId";
|
public static final String PARAM_USER = "userId";
|
||||||
|
|
||||||
private final String userId;
|
private final String userId;
|
||||||
@ -47,7 +50,8 @@ public class SendNotificationActionHandler extends BaseNotificationActionHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
|
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
|
||||||
cloudService.sendNotification(userId, message, icon, severity);
|
cloudService.sendNotification(userId, message, icon, severity, title, onClickAction, mediaAttachmentUrl,
|
||||||
|
actionButton1, actionButton2, actionButton3);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user