diff --git a/pom.xml b/pom.xml index 5f69324..d2a9ba5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.n1netails n1netails-teams-webhook-client - 0.2.0 + 0.3.0 jar n1netails-teams-webhook-client @@ -55,6 +55,20 @@ gson 2.13.2 + + + org.junit.jupiter + junit-jupiter-api + 5.11.4 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.11.4 + test + diff --git a/readme.md b/readme.md index c8565cb..a0672ec 100644 --- a/readme.md +++ b/readme.md @@ -47,13 +47,13 @@ Install the teams webhook client by adding the following dependency: com.n1netails n1netails-teams-webhook-client - 0.2.0 + 0.3.0 ``` Gradle (Groovy) ```groovy -implementation 'com.n1netails:n1netails-teams-webhook-client:0.2.0' +implementation 'com.n1netails:n1netails-teams-webhook-client:0.3.0' ``` ## Usage @@ -94,12 +94,11 @@ The message card is a more flexible and customizable way to send messages. ```java import com.n1netails.n1netails.teams.api.TeamsWebhookClient; import com.n1netails.n1netails.teams.internal.TeamsWebhookClientImpl; -import com.n1netails.n1netails.teams.model.Fact; -import com.n1netails.n1netails.teams.model.MessageCard; -import com.n1netails.n1netails.teams.model.Section; +import com.n1netails.n1netails.teams.model.*; import com.n1netails.n1netails.teams.service.WebhookService; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class Example { @@ -120,10 +119,15 @@ public class Example { facts.add(new Fact("Fact 1", "Fact 1 Value")); facts.add(new Fact("Fact 2", "Fact 2 Value")); section.setFacts(facts); + section.setImageUrl("https://raw.githubusercontent.com/n1netails/n1netails/refs/heads/main/n1netails_icon_transparent.png"); sections.add(section); messageCard.setSections(sections); + List actions = new ArrayList<>(); + actions.add(new Action("View Website", "https://github.com/n1netails/n1netails-teams-webhook-client")); + messageCard.setActions(actions); + client.sendMessage("YOUR_WEBHOOK_URL", messageCard); } catch (Exception e) { e.printStackTrace(); @@ -137,6 +141,77 @@ public class Example { N1netails teams webhook message block +### Image Message + +```java +import com.n1netails.n1netails.teams.api.TeamsWebhookClient; +import com.n1netails.n1netails.teams.internal.TeamsWebhookClientImpl; +import com.n1netails.n1netails.teams.model.WebhookMessage; +import com.n1netails.n1netails.teams.service.WebhookService; + +public class Example { + public static void main(String[] args) { + try { + WebhookService webhookService = new WebhookService(); + TeamsWebhookClient client = new TeamsWebhookClientImpl(webhookService); + + WebhookMessage message = new WebhookMessage(); + message.setContent("Hello, from n1netails-teams-webhook-client!"); + message.setImageUrl("https://raw.githubusercontent.com/n1netails/n1netails/refs/heads/main/n1netails_icon_transparent.png"); + + List actions = new ArrayList<>(); + actions.add(new Action("Visit Website", "https://github.com/n1netails/n1netails-teams-webhook-client")); + message.setActions(actions); + + client.sendMessage("YOUR_WEBHOOK_URL", message); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +#### Example image message output +
+ N1netails teams webhook message image +
+ +### GIF Message + +```java +import com.n1netails.n1netails.teams.api.TeamsWebhookClient; +import com.n1netails.n1netails.teams.internal.TeamsWebhookClientImpl; +import com.n1netails.n1netails.teams.model.WebhookMessage; +import com.n1netails.n1netails.teams.service.WebhookService; + +public class Example { + public static void main(String[] args) { + try { + WebhookService webhookService = new WebhookService(); + TeamsWebhookClient client = new TeamsWebhookClientImpl(webhookService); + + WebhookMessage message = new WebhookMessage(); + message.setContent("Hello, from n1netails-teams-webhook-client!"); + // Gifs are also supported + message.setImageUrl("https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmEwajlsZjBqb2d2Zmk1bmJoYTEyN2Q0czRmOWtmYjd4YWVzMHoxaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xsE65jaPsUKUo/giphy.gif"); + + List actions = new ArrayList<>(); + actions.add(new Action("Visit Website", "https://github.com/n1netails/n1netails-teams-webhook-client")); + message.setActions(actions); + + client.sendMessage("YOUR_WEBHOOK_URL", message); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +#### Example gif message output +
+ N1netails teams webhook message gif +
+ # Develop ## Build diff --git a/src/main/java/com/n1netails/n1netails/teams/model/Action.java b/src/main/java/com/n1netails/n1netails/teams/model/Action.java new file mode 100644 index 0000000..3883d73 --- /dev/null +++ b/src/main/java/com/n1netails/n1netails/teams/model/Action.java @@ -0,0 +1,29 @@ +package com.n1netails.n1netails.teams.model; + +import lombok.Getter; +import lombok.Setter; + +/** + * Action for MessageCard + */ +@Getter +@Setter +public class Action { + private String name; + private String target; + + /** + * Action Constructor + */ + public Action() {} + + /** + * Action Constructor with parameters + * @param name Name of the action + * @param target Target URL of the action + */ + public Action(String name, String target) { + this.name = name; + this.target = target; + } +} diff --git a/src/main/java/com/n1netails/n1netails/teams/model/MessageCard.java b/src/main/java/com/n1netails/n1netails/teams/model/MessageCard.java index cbc1860..f183db8 100644 --- a/src/main/java/com/n1netails/n1netails/teams/model/MessageCard.java +++ b/src/main/java/com/n1netails/n1netails/teams/model/MessageCard.java @@ -18,6 +18,7 @@ public class MessageCard { private String title; private String summary; private List
sections; + private List actions; /** * Message Card Constructor diff --git a/src/main/java/com/n1netails/n1netails/teams/model/Section.java b/src/main/java/com/n1netails/n1netails/teams/model/Section.java index a6a1b86..fb01de9 100644 --- a/src/main/java/com/n1netails/n1netails/teams/model/Section.java +++ b/src/main/java/com/n1netails/n1netails/teams/model/Section.java @@ -14,6 +14,7 @@ public class Section { private String title; private List facts; + private String imageUrl; /** * Section Constructor diff --git a/src/main/java/com/n1netails/n1netails/teams/model/WebhookMessage.java b/src/main/java/com/n1netails/n1netails/teams/model/WebhookMessage.java index 29a88db..90d8020 100644 --- a/src/main/java/com/n1netails/n1netails/teams/model/WebhookMessage.java +++ b/src/main/java/com/n1netails/n1netails/teams/model/WebhookMessage.java @@ -3,6 +3,8 @@ import lombok.Getter; import lombok.Setter; +import java.util.List; + /** * Teams Webhook Message * @author shahid foy @@ -12,6 +14,8 @@ public class WebhookMessage { private String content; + private String imageUrl; + private List actions; /** * Webhook Message Constructor diff --git a/src/main/java/com/n1netails/n1netails/teams/model/WebhookPayload.java b/src/main/java/com/n1netails/n1netails/teams/model/WebhookPayload.java index 9cc715a..63b059e 100644 --- a/src/main/java/com/n1netails/n1netails/teams/model/WebhookPayload.java +++ b/src/main/java/com/n1netails/n1netails/teams/model/WebhookPayload.java @@ -45,6 +45,7 @@ public static class Content { private String type; private String version; private List body; + private List actions; /** * Content Constructor @@ -80,10 +81,43 @@ public static class BodyItem { private String size; private boolean wrap; private boolean separator; + private String url; + private List sources; /** * Body Item Constructor */ public BodyItem() {} } + + /** + * Media Source + */ + @Setter + @Getter + public static class MediaSource { + private String mimeType; + private String url; + + /** + * Media Source Constructor + */ + public MediaSource() {} + } + + /** + * Action Item + */ + @Setter + @Getter + public static class ActionItem { + private String type; + private String title; + private String url; + + /** + * Action Item Constructor + */ + public ActionItem() {} + } } diff --git a/src/main/java/com/n1netails/n1netails/teams/service/WebhookService.java b/src/main/java/com/n1netails/n1netails/teams/service/WebhookService.java index e4fc156..f829226 100644 --- a/src/main/java/com/n1netails/n1netails/teams/service/WebhookService.java +++ b/src/main/java/com/n1netails/n1netails/teams/service/WebhookService.java @@ -18,14 +18,56 @@ */ public class WebhookService { + /** + * Media type for Microsoft Adaptive Cards. + */ public static final String APPLICATION_VND_MICROSOFT_CARD_ADAPTIVE = "application/vnd.microsoft.card.adaptive"; + + /** + * HTTP Adaptive card IO Schemas Adaptive card JSON + */ public static final String HTTP_ADAPTIVECARDS_IO_SCHEMAS_ADAPTIVE_CARD_JSON = "http://adaptivecards.io/schemas/adaptive-card.json"; + + /** + * Adaptive Card schema URL. + */ public static final String ADAPTIVE_CARD = "AdaptiveCard"; + + /** + * Content version + */ public static final String VERSION = "1.4"; + + /** + * Text block + */ public static final String TEXT_BLOCK = "TextBlock"; + + /** + * Bolder + */ public static final String BOLDER = "Bolder"; + + /** + * Medium + */ public static final String MEDIUM = "Medium"; + /** + * Image + */ + public static final String IMAGE = "Image"; + + /** + * Media + */ + public static final String MEDIA = "Media"; + + /** + * Action Open URL. + */ + public static final String ACTION_OPEN_URL = "Action.OpenUrl"; + private final Gson gson = new Gson(); /** @@ -80,7 +122,12 @@ private void send(String webhookUrl, Object message) throws TeamsWebhookExceptio } } - private static WebhookPayload getWebhookPayload(WebhookMessage message) { + /** + * Generates a webhook payload from a webhook message. + * @param message webhook message + * @return the webhook payload + */ + public static WebhookPayload getWebhookPayload(WebhookMessage message) { WebhookPayload payload = new WebhookPayload(); WebhookPayload.Attachment attachment = new WebhookPayload.Attachment(); attachment.setContentType(APPLICATION_VND_MICROSOFT_CARD_ADAPTIVE); @@ -99,17 +146,46 @@ private static WebhookPayload.Content getContent(WebhookMessage message) { content.set$schema(HTTP_ADAPTIVECARDS_IO_SCHEMAS_ADAPTIVE_CARD_JSON); content.setType(ADAPTIVE_CARD); content.setVersion(VERSION); - WebhookPayload.BodyItem bodyItem = new WebhookPayload.BodyItem(); - bodyItem.setType(TEXT_BLOCK); - bodyItem.setText(message.getContent()); - bodyItem.setWeight(BOLDER); - bodyItem.setSize(MEDIUM); List bodyItems = new ArrayList<>(); - bodyItems.add(bodyItem); + + if (message.getContent() != null) { + WebhookPayload.BodyItem bodyItem = new WebhookPayload.BodyItem(); + bodyItem.setType(TEXT_BLOCK); + bodyItem.setText(message.getContent()); + bodyItem.setWeight(BOLDER); + bodyItem.setSize(MEDIUM); + bodyItems.add(bodyItem); + } + + if (message.getImageUrl() != null) { + WebhookPayload.BodyItem imageItem = new WebhookPayload.BodyItem(); + imageItem.setType(IMAGE); + imageItem.setUrl(message.getImageUrl()); + bodyItems.add(imageItem); + } + + if (message.getActions() != null) { + List actions = new ArrayList<>(); + for (Action action : message.getActions()) { + WebhookPayload.ActionItem actionItem = new WebhookPayload.ActionItem(); + actionItem.setType(ACTION_OPEN_URL); + actionItem.setTitle(action.getName()); + actionItem.setUrl(action.getTarget()); + actions.add(actionItem); + } + content.setActions(actions); + } + content.setBody(bodyItems); return content; } + /** + * Generates a webhook payload from a message card. + * + * @param card the message card + * @return the webhook payload + */ public static WebhookPayload getWebhookPayload(MessageCard card) { WebhookPayload payload = new WebhookPayload(); @@ -175,7 +251,26 @@ private static WebhookPayload.Content getContent(MessageCard card) { body.add(factBlock); } } + + if (section.getImageUrl() != null) { + WebhookPayload.BodyItem imageItem = new WebhookPayload.BodyItem(); + imageItem.setType(IMAGE); + imageItem.setUrl(section.getImageUrl()); + body.add(imageItem); + } + } + } + + if (card.getActions() != null) { + List actions = new ArrayList<>(); + for (Action action : card.getActions()) { + WebhookPayload.ActionItem actionItem = new WebhookPayload.ActionItem(); + actionItem.setType(ACTION_OPEN_URL); + actionItem.setTitle(action.getName()); + actionItem.setUrl(action.getTarget()); + actions.add(actionItem); } + content.setActions(actions); } content.setBody(body); diff --git a/src/test/java/com/n1netails/n1netails/teams/service/WebhookServiceTest.java b/src/test/java/com/n1netails/n1netails/teams/service/WebhookServiceTest.java new file mode 100644 index 0000000..5d19474 --- /dev/null +++ b/src/test/java/com/n1netails/n1netails/teams/service/WebhookServiceTest.java @@ -0,0 +1,80 @@ +package com.n1netails.n1netails.teams.service; + +import com.n1netails.n1netails.teams.model.*; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +public class WebhookServiceTest { + + @Test + public void testGetWebhookPayloadFromWebhookMessage() { + WebhookMessage message = new WebhookMessage(); + message.setContent("Test Content"); + message.setImageUrl("http://example.com/image.png"); + message.setActions(Collections.singletonList(new Action("Action", "http://example.com"))); + + WebhookPayload payload = WebhookService.getWebhookPayload(message); + + assertNotNull(payload); + assertEquals(1, payload.getAttachments().size()); + WebhookPayload.Attachment attachment = payload.getAttachments().get(0); + assertEquals(WebhookService.APPLICATION_VND_MICROSOFT_CARD_ADAPTIVE, attachment.getContentType()); + + WebhookPayload.Content content = attachment.getContent(); + assertEquals(WebhookService.HTTP_ADAPTIVECARDS_IO_SCHEMAS_ADAPTIVE_CARD_JSON, content.get$schema()); + assertEquals(WebhookService.ADAPTIVE_CARD, content.getType()); + assertEquals(WebhookService.VERSION, content.getVersion()); + + assertEquals(2, content.getBody().size()); + + WebhookPayload.BodyItem textItem = content.getBody().get(0); + assertEquals(WebhookService.TEXT_BLOCK, textItem.getType()); + assertEquals("Test Content", textItem.getText()); + + WebhookPayload.BodyItem imageItem = content.getBody().get(1); + assertEquals(WebhookService.IMAGE, imageItem.getType()); + assertEquals("http://example.com/image.png", imageItem.getUrl()); + + assertNotNull(content.getActions()); + assertEquals(1, content.getActions().size()); + assertEquals("Action", content.getActions().get(0).getTitle()); + } + + @Test + public void testGetWebhookPayloadFromMessageCard() { + MessageCard card = new MessageCard(); + card.setTitle("Test Title"); + card.setSummary("Test Summary"); + + Section section = new Section(); + section.setTitle("Section Title"); + section.setImageUrl("http://example.com/section-image.png"); +// section.setVideoUrl("http://example.com/section-video.mp4"); + card.setSections(Collections.singletonList(section)); + + Action action = new Action("View More", "http://example.com"); + card.setActions(Collections.singletonList(action)); + + WebhookPayload payload = WebhookService.getWebhookPayload(card); + + assertNotNull(payload); + WebhookPayload.Content content = payload.getAttachments().get(0).getContent(); + + assertEquals(4, content.getBody().size()); + assertEquals("Test Title", content.getBody().get(0).getText()); + assertEquals("Test Summary", content.getBody().get(1).getText()); + assertEquals("Section Title", content.getBody().get(2).getText()); + + assertEquals(WebhookService.IMAGE, content.getBody().get(3).getType()); + assertEquals("http://example.com/section-image.png", content.getBody().get(3).getUrl()); + + assertNotNull(content.getActions()); + assertEquals(1, content.getActions().size()); + assertEquals(WebhookService.ACTION_OPEN_URL, content.getActions().get(0).getType()); + assertEquals("View More", content.getActions().get(0).getTitle()); + assertEquals("http://example.com", content.getActions().get(0).getUrl()); + } +} diff --git a/teams-message-gif.png b/teams-message-gif.png new file mode 100644 index 0000000..677deff Binary files /dev/null and b/teams-message-gif.png differ diff --git a/teams-message-image.png b/teams-message-image.png new file mode 100644 index 0000000..517631a Binary files /dev/null and b/teams-message-image.png differ