Skip to content

Commit 3b225a3

Browse files
committed
✨ feat: add viber4j #8
1 parent cd4e53b commit 3b225a3

File tree

6 files changed

+470
-3
lines changed

6 files changed

+470
-3
lines changed

Diff for: libs/unify4j-v1.0.0.jar

1.02 KB
Binary file not shown.
+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.bot4j.viber.common;
2+
3+
import kong.unirest.HttpResponse;
4+
import kong.unirest.JsonNode;
5+
import kong.unirest.Unirest;
6+
import org.bot4j.viber.model.enums.ViberTextMode;
7+
import org.bot4j.viber.model.rd.AbstractViberClass;
8+
import org.unify4j.common.Collection4j;
9+
import org.unify4j.common.Json4j;
10+
import org.unify4j.common.Object4j;
11+
import org.unify4j.common.String4j;
12+
import org.unify4j.model.builder.HttpStatusBuilder;
13+
import org.unify4j.model.builder.HttpWrapBuilder;
14+
import org.unify4j.model.response.WrapResponse;
15+
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
import java.util.Optional;
19+
import java.util.concurrent.CompletableFuture;
20+
21+
public class Viber4j extends AbstractViberClass {
22+
protected Viber4j(Builder builder) {
23+
super(builder);
24+
}
25+
26+
@Override
27+
public AbstractViberClass requestId(String value) {
28+
if (this.options == null) {
29+
return this;
30+
}
31+
this.options.setRequestId(value);
32+
return this;
33+
}
34+
35+
@SuppressWarnings({"SimplifyOptionalCallChains"})
36+
@Override
37+
public WrapResponse<?> verify() {
38+
if (String4j.isEmpty(this.token)) {
39+
return new HttpWrapBuilder<>().badRequest("Token is required")
40+
.debug("X-Viber-Auth-Token", "Each API request must include an HTTP Header called X-Viber-Auth-Token containing the account’s authentication token.")
41+
.debug("X-Viber-Auth-Token", "The authentication token is generated upon bot creation and can be viewed by the account’s admins in the “edit info” screen of their bot or on the Viber Admin Panel.")
42+
.build();
43+
}
44+
Map<String, Object> request = this.messageBody();
45+
if (Collection4j.isEmptyMap(request)) {
46+
return new HttpWrapBuilder<>().badRequest("Request body is required").build();
47+
}
48+
if (!request.containsKey("receiver") || !Object4j.allNotNull(request.get("receiver"))) {
49+
return new HttpWrapBuilder<>().badRequest("Receiver is required")
50+
.debug("receiver", "Unique Viber user id")
51+
.build();
52+
}
53+
if (!request.containsKey("type") || !Object4j.allNotNull(request.get("type"))) {
54+
return new HttpWrapBuilder<>().badRequest("Message type is required").build();
55+
}
56+
Optional<ViberTextMode> type = ViberTextMode.tryValueOf(String.valueOf(request.get("type")));
57+
if (!type.isPresent()) {
58+
return new HttpWrapBuilder<>().badRequest("Invalid message type")
59+
.debug("type", "Available message types: text, picture, video, file, location, contact, sticker, carousel, content and url")
60+
.build();
61+
}
62+
if (!request.containsKey("sender")) {
63+
return new HttpWrapBuilder<>().badRequest("Sender is required").build();
64+
}
65+
String senderName = Json4j.readIf(Json4j.toJson(request), "$.sender.name");
66+
int _len = 28;
67+
if (String4j.isEmpty(senderName)) {
68+
return new HttpWrapBuilder<>().badRequest("Sender name is required")
69+
.debug("sender.name", "The sender’s name to display")
70+
.build();
71+
}
72+
if (senderName.length() > _len) {
73+
return new HttpWrapBuilder<>().badRequest("Invalid sender name")
74+
.debug("sender.name", String.format("Minimum length of sender name is %d characters.", _len))
75+
.build();
76+
}
77+
if (request.containsKey("text")) {
78+
String text = String.valueOf(request.get("text"));
79+
if (String4j.isEmpty(text)) {
80+
return new HttpWrapBuilder<>().badRequest("Text of message is required").build();
81+
}
82+
if (String4j.isBlank(text)) {
83+
return new HttpWrapBuilder<>().badRequest(String.format("Invalid text of message: '%s'", text)).build();
84+
}
85+
int len = 7000;
86+
if (text.length() > len) {
87+
return new HttpWrapBuilder<>().badRequest("Invalid text of message")
88+
.debug("text", String.format("Minimum length of text is %d characters.", len))
89+
.build();
90+
}
91+
}
92+
if (request.containsKey("tracking_data")) {
93+
String tracking = String.valueOf(request.get("tracking_data"));
94+
if (String4j.isEmpty(tracking)) {
95+
return new HttpWrapBuilder<>().badRequest("Tracking data is required")
96+
.debug("tracking_data", "Allow the account to track messages and user’s replies. Sent tracking_data value will be passed back with user’s reply")
97+
.build();
98+
}
99+
int len = 4096;
100+
if (tracking.length() > len) {
101+
return new HttpWrapBuilder<>().badRequest("Invalid tracking data")
102+
.debug("tracking_data", String.format("Minimum length of tracking_data is %d characters.", len))
103+
.build();
104+
}
105+
}
106+
return new HttpWrapBuilder<>().ok(null).build();
107+
}
108+
109+
@Override
110+
public Map<String, String> headers() {
111+
Map<String, String> headers = new HashMap<>();
112+
headers.put("X-Viber-Auth-Token", this.token);
113+
return headers;
114+
}
115+
116+
@Override
117+
public Map<String, Object> messageBody() {
118+
return this.message;
119+
}
120+
121+
@Override
122+
public WrapResponse<?> sendMessage() {
123+
if (this.connections.isSkip()) {
124+
return new HttpWrapBuilder<>().notImplemented("Oops! Viber4j unavailable").build();
125+
}
126+
WrapResponse<?> verified = this.verify();
127+
if (!verified.isSuccess()) {
128+
return verified;
129+
}
130+
String url = String.format("%s/pa/send_message", this.baseURL);
131+
Map<String, Object> request = this.messageBody();
132+
Map<String, String> headers = this.headers();
133+
if (this.connections.isDebugging()) {
134+
logger.debug("Viber4j, request_id: {} sending message on URL: {}", this.options.getRequestId(), url);
135+
logger.debug("Viber4j, request_id: {} sending request body: {}", this.options.getRequestId(), Json4j.toJson(request));
136+
logger.debug("Viber4j, request_id: {} sending request header: {}", this.options.getRequestId(), headers.toString());
137+
}
138+
try {
139+
HttpResponse<JsonNode> caller = Unirest.post(url).headers(headers).body(request).asJson();
140+
WrapResponse<Object> response = new HttpWrapBuilder<>()
141+
.statusCode(caller.getStatus())
142+
.requestId(this.options.getRequestId())
143+
.body(Json4j.translate(caller))
144+
.total(1)
145+
.build();
146+
if (Object4j.allNotNull(caller.getBody())) {
147+
String message = Json4j.readIf(caller.getBody().toString(), "$.status_message");
148+
response.setMessage(message);
149+
}
150+
return response;
151+
} catch (Exception e) {
152+
return new HttpWrapBuilder<>()
153+
.statusCode(HttpStatusBuilder.INTERNAL_SERVER_ERROR)
154+
.errors(e)
155+
.requestId(this.options.getRequestId())
156+
.build();
157+
}
158+
}
159+
160+
@Override
161+
public CompletableFuture<WrapResponse<?>> sendMessageAsync() {
162+
return CompletableFuture.supplyAsync(this::sendMessage);
163+
}
164+
165+
@Override
166+
public void sendMessageSilent() {
167+
CompletableFuture.runAsync(this::sendMessage);
168+
}
169+
170+
@Override
171+
public void sendMessageWait() {
172+
this.sendMessageAsync().join();
173+
}
174+
175+
public static class Builder extends AbstractViberClass.Builder<Builder> {
176+
@Override
177+
public Viber4j build() {
178+
return new Viber4j(this);
179+
}
180+
181+
@Override
182+
protected Builder self() {
183+
return this;
184+
}
185+
}
186+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.bot4j.viber.common;
2+
3+
import org.unify4j.model.builder.HttpWrapBuilder;
4+
import org.unify4j.model.response.WrapResponse;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.stream.Collectors;
10+
11+
public class ViberClusters4j {
12+
private final List<Viber4j> instances;
13+
14+
public ViberClusters4j(Builder builder) {
15+
this.instances = builder.instances;
16+
}
17+
18+
/**
19+
* Sends multiple messages to the Slack API.
20+
*
21+
* @return a List<WrapResponse<?>> containing the responses from the Viber API for each message.
22+
*/
23+
public List<WrapResponse<?>> sendMessage() {
24+
return instances.stream().map(Viber4j::sendMessage).collect(Collectors.toList());
25+
}
26+
27+
/**
28+
* Sends multiple messages to the Viber API asynchronously.
29+
*
30+
* @return a CompletableFuture<List<WrapResponse<?>>> representing the asynchronous operation.
31+
*/
32+
@SuppressWarnings({"SimplifyStreamApiCallChains"})
33+
public CompletableFuture<List<WrapResponse<?>>> sendMessageAsync() {
34+
List<CompletableFuture<WrapResponse<?>>> futures = instances.stream().map(Viber4j::sendMessageAsync).collect(Collectors.toList());
35+
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
36+
}
37+
38+
/**
39+
* Sends multiple messages to the Viber API silently (without waiting for the results).
40+
*/
41+
public void sendMessageSilent() {
42+
instances.forEach(Viber4j::sendMessageSilent);
43+
}
44+
45+
/**
46+
* Sends multiple messages to the Viber API and waits for the results.
47+
*/
48+
public void sendMessageWait() {
49+
this.sendMessageAsync().join();
50+
}
51+
52+
/**
53+
* Verifies all Viber instances in the cluster.
54+
*
55+
* @return a WrapResponse<?> indicating the verification result.
56+
*/
57+
public WrapResponse<?> verify() {
58+
for (Viber4j instance : instances) {
59+
WrapResponse<?> verified = instance.verify();
60+
if (!verified.isSuccess()) {
61+
return verified;
62+
}
63+
}
64+
return new HttpWrapBuilder<>().ok(null).build();
65+
}
66+
67+
public static class Builder {
68+
protected List<Viber4j> instances = new ArrayList<>();
69+
70+
public ViberClusters4j build() {
71+
return new ViberClusters4j(this);
72+
}
73+
74+
public Builder append(Viber4j instance) {
75+
this.instances.add(instance);
76+
return this;
77+
}
78+
}
79+
}

Diff for: plugin/src/main/groovy/org/bot4j/viber/model/enums/ViberTextMode.java

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.bot4j.viber.model.enums;
22

3+
import java.util.Arrays;
4+
import java.util.Optional;
5+
36
public enum ViberTextMode {
47
TEXT("text"),
58
PICTURE("picture"),
@@ -21,4 +24,17 @@ public enum ViberTextMode {
2124
public String getValue() {
2225
return value;
2326
}
27+
28+
/**
29+
* Attempts to find a ViberTextMode enum constant that matches the provided name.
30+
*
31+
* @param name the value to match against the enum constants' values.
32+
* @return an Optional containing the matching ViberTextMode constant if found, otherwise an empty Optional.
33+
* <p>
34+
* This method uses a stream to iterate over the available ViberTextMode constants and returns the first match found
35+
* where the enum's value equals the provided name. If no match is found, an empty Optional is returned.
36+
*/
37+
public static Optional<ViberTextMode> tryValueOf(String name) {
38+
return Arrays.stream(ViberTextMode.values()).filter(e -> e.getValue().equals(name)).findFirst();
39+
}
2440
}

0 commit comments

Comments
 (0)