├── .gitignore ├── system.properties ├── Procfile ├── .settings ├── org.eclipse.m2e.core.prefs └── org.eclipse.jdt.core.prefs ├── src ├── com │ └── petersamokhin │ │ └── bots │ │ └── sdk │ │ ├── callbacks │ │ ├── AbstractCallback.java │ │ ├── Callback.java │ │ ├── CallbackDouble.java │ │ ├── CallbackTriple.java │ │ └── CallbackFourth.java │ │ ├── utils │ │ ├── vkapi │ │ │ ├── docs │ │ │ │ └── DocTypes.java │ │ │ ├── calls │ │ │ │ ├── Call.java │ │ │ │ ├── CallSync.java │ │ │ │ └── CallAsync.java │ │ │ ├── CallbackApiSettings.java │ │ │ ├── Executor.java │ │ │ ├── API.java │ │ │ └── CallbackApiHandler.java │ │ ├── web │ │ │ ├── Connection.java │ │ │ └── MultipartUtility.java │ │ └── Utils.java │ │ ├── clients │ │ ├── User.java │ │ ├── Client.java │ │ └── Group.java │ │ ├── longpoll │ │ ├── Queue.java │ │ ├── responses │ │ │ └── GetLongPollServerResponse.java │ │ ├── LongPoll.java │ │ └── UpdatesHandler.java │ │ └── objects │ │ ├── Chat.java │ │ └── Message.java └── net │ └── x666c │ └── simplereddit │ ├── bot │ ├── commands │ │ ├── Command.java │ │ ├── CommandProcessor.java │ │ └── reddit │ │ │ ├── MessageResolver.java │ │ │ └── RedditSender.java │ └── BotMain.java │ ├── Post.java │ └── Reddit.java ├── .project ├── .classpath └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java -cp target/subrbot-1.0-jar-with-dependencies.jar net.x666c.simplereddit.bot.BotMain -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/callbacks/AbstractCallback.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.callbacks; 2 | 3 | /** 4 | * Created by PeterSamokhin on 28/09/2017 21:59 5 | */ 6 | public interface AbstractCallback { 7 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/callbacks/Callback.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.callbacks; 2 | 3 | /** 4 | * For all callbacks compatibility. 5 | */ 6 | public interface Callback { 7 | 8 | void onResult(T object); 9 | } 10 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/callbacks/CallbackDouble.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.callbacks; 2 | 3 | /** 4 | * Created by PeterSamokhin on 29/09/2017 00:54 5 | */ 6 | public interface CallbackDouble extends AbstractCallback { 7 | 8 | void onEvent(T t, R r); 9 | } 10 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/callbacks/CallbackTriple.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.callbacks; 2 | 3 | /** 4 | * Created by PeterSamokhin on 29/09/2017 00:27 5 | */ 6 | public interface CallbackTriple extends AbstractCallback { 7 | 8 | void onEvent(T t, M m, R r); 9 | } 10 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/callbacks/CallbackFourth.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.callbacks; 2 | 3 | /** 4 | * Created by PeterSamokhin on 29/09/2017 00:57 5 | */ 6 | public interface CallbackFourth extends AbstractCallback { 7 | 8 | void onEvent(P i, D a, R as, S ti); 9 | } 10 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/docs/DocTypes.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi.docs; 2 | 3 | /** 4 | * Types of docs 5 | */ 6 | public enum DocTypes { 7 | 8 | DOC("doc"), 9 | AUDIO_MESSAGE("audio_message"), 10 | GRAFFITI("graffiti"); 11 | 12 | String type; 13 | 14 | DocTypes(String type) { 15 | this.type = type; 16 | } 17 | 18 | public String getType() { 19 | return type; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/bot/commands/Command.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit.bot.commands; 2 | 3 | import java.util.function.BiConsumer; 4 | 5 | import org.json.JSONObject; 6 | 7 | public class Command { 8 | 9 | public final int argsMax; 10 | private BiConsumer exec; 11 | 12 | public Command(int maxArgs, BiConsumer cmd) { 13 | argsMax = maxArgs; 14 | exec = cmd; 15 | } 16 | 17 | public void execute(JSONObject o, String[] args) { 18 | exec.accept(o, args); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | VkSubredditBotMaven 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/clients/User.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.clients; 2 | 3 | /** 4 | * User client, that contains important methods to work with users 5 | * 6 | * Not need now to put methods there: use API.call 7 | */ 8 | public class User extends Client { 9 | 10 | /** 11 | * Default constructor 12 | * @param id User or group id 13 | * @param access_token Access token key 14 | */ 15 | public User(Integer id, String access_token) { 16 | super(id, access_token); 17 | } 18 | 19 | /** 20 | * Default constructor 21 | * @param access_token Access token key 22 | */ 23 | public User(String access_token) { 24 | super(access_token); 25 | } 26 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/calls/Call.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi.calls; 2 | 3 | import org.json.JSONObject; 4 | 5 | /** 6 | * Abstract class for backward compatibility 7 | */ 8 | public abstract class Call { 9 | 10 | protected String methodName; 11 | protected JSONObject params; 12 | 13 | public String getMethodName() { 14 | return methodName; 15 | } 16 | 17 | public JSONObject getParams() { 18 | return params; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Call{" + 24 | "methodName='" + methodName + '\'' + 25 | ", params=" + params.toString() + 26 | '}'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.release=disabled 13 | org.eclipse.jdt.core.compiler.source=1.8 14 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/bot/commands/CommandProcessor.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit.bot.commands; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.json.JSONObject; 6 | 7 | import com.petersamokhin.bots.sdk.clients.Group; 8 | 9 | public class CommandProcessor { 10 | 11 | private final HashMap commands = new HashMap<>(); 12 | private final Group group; 13 | 14 | public CommandProcessor(Group g) { 15 | group = g; 16 | } 17 | 18 | public void addCommand(String name, Command c) { 19 | commands.put(name, c); 20 | } 21 | 22 | public Command getCommandObject(String name) { 23 | return commands.get(name); 24 | } 25 | 26 | public boolean excuteCommand(String name, JSONObject o, String... arguments) { 27 | if(commands.containsKey(name)) { 28 | getCommandObject(name).execute(o, arguments); 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/CallbackApiSettings.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi; 2 | 3 | /** 4 | * Settings for interacting with Callback API 5 | */ 6 | public class CallbackApiSettings { 7 | 8 | private String host = null, path; 9 | private int port = 80; 10 | private boolean autoAnswer = false; 11 | public final String confirmationCode; 12 | 13 | public CallbackApiSettings(String confCode, String host, int port, String path, boolean autoAnswer, boolean autoSet) { 14 | this.confirmationCode = confCode; 15 | this.host = host; 16 | this.path = path; 17 | this.port = port; 18 | this.autoAnswer = autoAnswer; 19 | CallbackApiHandler.autoSetEvents = autoSet; 20 | } 21 | 22 | /* Getters */ 23 | String getHost() { 24 | return host; 25 | } 26 | 27 | String getPath() { 28 | return path; 29 | } 30 | 31 | int getPort() { 32 | return port; 33 | } 34 | 35 | boolean isAutoAnswer() { 36 | return autoAnswer; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/calls/CallSync.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi.calls; 2 | 3 | import org.json.JSONObject; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Deserialized class of call to vk api using execute method 9 | */ 10 | public class CallSync extends Call { 11 | 12 | public CallSync(String methodName, JSONObject params) { 13 | this.methodName = methodName; 14 | this.params = params; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "Call{" + 20 | "methodName='" + methodName + '\'' + 21 | ", params=" + params.toString() + 22 | '}'; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (!(o instanceof CallSync)) return false; 29 | CallSync call = (CallSync) o; 30 | return Objects.equals(getMethodName(), call.getMethodName()) && 31 | Objects.equals(getParams().toMap(), call.getParams().toMap()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/longpoll/Queue.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.longpoll; 2 | 3 | import org.json.JSONArray; 4 | 5 | import java.util.concurrent.CopyOnWriteArrayList; 6 | 7 | /** 8 | * Queue of updates 9 | */ 10 | class Queue { 11 | 12 | /** 13 | * List of updates that we need to handle 14 | */ 15 | volatile CopyOnWriteArrayList updates = new CopyOnWriteArrayList<>(); 16 | 17 | /** 18 | * We add all of updates from longpoll server 19 | * to queue 20 | * 21 | * @param elements Array of updates 22 | */ 23 | void putAll(JSONArray elements) { 24 | elements.forEach(item -> updates.add((JSONArray) item)); 25 | } 26 | 27 | /** 28 | * Analog method of 'shift()' method from javascript 29 | * 30 | * @return First element of list, and then remove it 31 | */ 32 | JSONArray shift() { 33 | if (this.updates.size() > 0) { 34 | JSONArray answer = this.updates.get(0); 35 | this.updates.remove(0); 36 | return answer; 37 | } 38 | return new JSONArray(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/calls/CallAsync.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi.calls; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import org.json.JSONObject; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Deserialized class of call to vk api using execute method 10 | */ 11 | public class CallAsync extends Call { 12 | 13 | private Callback callback; 14 | 15 | public CallAsync(String methodName, JSONObject params, Callback callback) { 16 | this.methodName = methodName; 17 | this.params = params; 18 | this.callback = callback; 19 | } 20 | 21 | public Callback getCallback() { 22 | return callback; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (!(o instanceof CallAsync)) return false; 29 | CallAsync call = (CallAsync) o; 30 | return Objects.equals(getMethodName(), call.getMethodName()) && 31 | Objects.equals(getParams().toMap(), call.getParams().toMap()) && 32 | Objects.equals(getCallback(), call.getCallback()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/bot/commands/reddit/MessageResolver.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit.bot.commands.reddit; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | 6 | import org.json.JSONObject; 7 | 8 | import com.petersamokhin.bots.sdk.clients.Group; 9 | 10 | import net.x666c.simplereddit.Post; 11 | import net.x666c.simplereddit.Reddit; 12 | 13 | public class MessageResolver { 14 | 15 | private HashMap redditMap = new HashMap<>(); 16 | private final Group group; 17 | 18 | public MessageResolver(Group group) { 19 | this.group = group; 20 | } 21 | 22 | public String[] tokenize(JSONObject msg) { 23 | String text = msg.getString("text"); 24 | 25 | String[] ret = text.split(" "); 26 | 27 | if(isMention(ret)) 28 | return Arrays.copyOfRange(ret, 1, ret.length); 29 | return ret; 30 | } 31 | 32 | 33 | 34 | public void resolveReddit(String[] tokens, JSONObject json) { 35 | System.out.println(isMention(tokens)); 36 | if(isMention(tokens)) { 37 | tokens = Arrays.copyOfRange(tokens, 1, tokens.length); 38 | } 39 | sendPostFromReddit(tokens, json); 40 | } 41 | 42 | private void sendPostFromReddit(String[] tokens, JSONObject json) { 43 | RedditSender s = new RedditSender(group, json); 44 | 45 | Reddit subreddit = null; 46 | if(tokens[1].equals("random")) { 47 | subreddit = s.randomSubreddit(redditMap); 48 | } else { 49 | subreddit = s.knownSubreddit(redditMap, tokens[1]); 50 | } 51 | 52 | Post post = subreddit.randomPost(); 53 | 54 | if(tokens[2].equals("photo") || tokens[2].equals("image")) { 55 | s.sendPhoto(post); 56 | } else if(tokens[2].equals("text")) { 57 | s.sendText(post); 58 | } else if(tokens[2].equals("any")) { 59 | s.sendAny(post); 60 | } 61 | } 62 | 63 | 64 | 65 | private boolean isMention(String[] tokens) { 66 | return tokens[0].matches("^\\[club[0-9]{9}\\|@[a-zA-Z]+\\]$"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/longpoll/responses/GetLongPollServerResponse.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.longpoll.responses; 2 | 3 | /** 4 | * Deserialized object of VK response 5 | */ 6 | public class GetLongPollServerResponse { 7 | 8 | private String key; 9 | private String server; 10 | private Integer ts; 11 | private Integer pts; 12 | 13 | /** 14 | * VK response without pts (more: link 15 | * 16 | * @param key link 17 | * @param server link 18 | * @param ts link 19 | */ 20 | public GetLongPollServerResponse(String key, String server, Integer ts) { 21 | this.key = key; 22 | this.server = server; 23 | this.ts = ts; 24 | } 25 | 26 | /** 27 | * VK response with pts (more: link 28 | * 29 | * @param key link 30 | * @param server link 31 | * @param ts link 32 | * @param pts link 33 | */ 34 | public GetLongPollServerResponse(String key, String server, Integer ts, Integer pts) { 35 | this.key = key; 36 | this.server = server; 37 | this.ts = ts; 38 | this.pts = pts; 39 | } 40 | 41 | // Getters and Setters 42 | 43 | public String getKey() { 44 | return key; 45 | } 46 | 47 | public void setKey(String key) { 48 | this.key = key; 49 | } 50 | 51 | public String getServer() { 52 | return server; 53 | } 54 | 55 | public void setServer(String server) { 56 | this.server = server; 57 | } 58 | 59 | public Integer getTs() { 60 | return ts; 61 | } 62 | 63 | public void setTs(Integer ts) { 64 | this.ts = ts; 65 | } 66 | 67 | public Integer getPts() { 68 | return pts; 69 | } 70 | 71 | public void setPts(Integer pts) { 72 | this.pts = pts; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | subrbot 6 | subrbot 7 | 1.0 8 | 9 | 10 | 11 | org.json 12 | json 13 | 20160810 14 | 15 | 16 | 17 | org.eclipse.jetty 18 | jetty-server 19 | 9.4.41.v20210516 20 | 21 | 22 | 23 | 24 | org.eclipse.jetty 25 | jetty-servlet 26 | 9.4.12.v20180830 27 | 28 | 29 | 30 | javax.servlet 31 | javax.servlet-api 32 | 4.0.1 33 | provided 34 | 35 | 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | 1.7.5 41 | 42 | 43 | 44 | org.slf4j 45 | slf4j-log4j12 46 | 1.7.5 47 | 48 | 49 | 50 | 51 | com.sparkjava 52 | spark-core 53 | 2.8.0 54 | 55 | 56 | 57 | 58 | 59 | 60 | src 61 | 62 | 63 | maven-compiler-plugin 64 | 3.7.0 65 | 66 | 1.8 67 | 1.8 68 | 69 | 70 | 71 | maven-assembly-plugin 72 | 73 | 74 | package 75 | 76 | single 77 | 78 | 79 | 80 | 81 | 82 | jar-with-dependencies 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/bot/BotMain.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit.bot; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.apache.log4j.BasicConfigurator; 7 | import org.apache.log4j.Level; 8 | import org.apache.log4j.Logger; 9 | import org.json.JSONObject; 10 | 11 | import com.petersamokhin.bots.sdk.callbacks.Callback; 12 | import com.petersamokhin.bots.sdk.clients.Group; 13 | import com.petersamokhin.bots.sdk.objects.Message; 14 | import com.petersamokhin.bots.sdk.utils.vkapi.CallbackApiSettings; 15 | 16 | import net.x666c.simplereddit.bot.commands.Command; 17 | import net.x666c.simplereddit.bot.commands.CommandProcessor; 18 | import net.x666c.simplereddit.bot.commands.reddit.MessageResolver; 19 | 20 | public class BotMain { 21 | 22 | public static final List randomSubs = Arrays.asList(SubredditsFiltered.SUBREDDITS.split("\r\n")); 23 | 24 | private static CommandProcessor processor; 25 | 26 | public static void main(String[] a) throws Exception { 27 | Logger.getRootLogger().setLevel(Level.INFO); 28 | BasicConfigurator.configure(); 29 | 30 | // Vk ------------------------------------------------------------------ 31 | 32 | Group group = new Group("6b49194f54d1b9c69717059c7ed7910bda1d548f7da9ce2ef406274a17f3cc7f4abac21fd951cf32a8599"); 33 | 34 | //group.callbackApi(new CallbackApiSettings("1515c4a2", "localhost", Integer.parseInt(System.getenv("PORT")), "/", true, false)); 35 | 36 | // FOR LOCAL TESTING: 37 | group.callbackApi(new CallbackApiSettings("1515c4a2", "localhost", 80, "/", true, false)); 38 | 39 | MessageResolver mr = new MessageResolver(group); 40 | processor = new CommandProcessor(group); 41 | 42 | { 43 | processor.addCommand("list", new Command(0, (o, args) -> { 44 | sendMessage(group, o, "да йобана мне лень все команды записывать чичас, но тут по идее должен их список быть"); 45 | })); 46 | } 47 | 48 | group.onMessageNew(json -> { 49 | System.out.println(json.toString()); 50 | try { 51 | System.out.println("Got a chat message: '" + json.getString("text") + "'"); 52 | 53 | String message = json.getString("text"); 54 | String[] tokens = mr.tokenize(json); 55 | 56 | System.out.println(Arrays.toString(tokens)); 57 | 58 | // Special cases (do not follow the standard) 59 | if (tokens[0].equals("reddit") && tokens.length == 3) { 60 | mr.resolveReddit(tokens, json); 61 | } else if(message.toLowerCase().contains("блять колдун")) { 62 | for (int i = 0; i < 10; i++) { 63 | new Message() 64 | .from(group) 65 | .to(json.getInt("peer_id")) 66 | .text("блять+колдун") 67 | .send((Callback[]) null); 68 | } 69 | // The rest 70 | } else { 71 | if(!processor.excuteCommand(tokens[0], json, Arrays.copyOfRange(tokens, 1, tokens.length))) 72 | sendMessage(group, json, "Invalid command: " + message); 73 | } 74 | } catch (Exception e) { 75 | sendMessage(group, json, "Exception: " + e.toString()); 76 | } 77 | }); 78 | } 79 | 80 | public static void sendMessage(Group g, JSONObject json, String text) { 81 | new Message() 82 | .from(g) 83 | .to(json.getInt("peer_id")) 84 | .text(text.replaceAll(" ", "+")) 85 | .send((Callback[]) null); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/bot/commands/reddit/RedditSender.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit.bot.commands.reddit; 2 | 3 | import static net.x666c.simplereddit.bot.BotMain.randomSubs; 4 | 5 | import java.util.HashMap; 6 | import java.util.Random; 7 | 8 | import org.json.JSONObject; 9 | 10 | import com.petersamokhin.bots.sdk.callbacks.Callback; 11 | import com.petersamokhin.bots.sdk.clients.Group; 12 | import com.petersamokhin.bots.sdk.objects.Message; 13 | 14 | import net.x666c.simplereddit.Post; 15 | import net.x666c.simplereddit.Reddit; 16 | import net.x666c.simplereddit.Reddit.Time; 17 | import net.x666c.simplereddit.Reddit.Type; 18 | 19 | public class RedditSender { 20 | 21 | private final Group group; 22 | private final JSONObject json; 23 | 24 | public RedditSender(Group g, JSONObject j) { 25 | group = g; 26 | json = j; 27 | } 28 | 29 | public Reddit randomSubreddit(HashMap redditMap) { 30 | String chosen = null; 31 | do { 32 | chosen = randomSubs.get(new Random().nextInt(randomSubs.size())); 33 | } while(redditMap.containsKey(chosen)); 34 | 35 | Reddit ret = null; 36 | String respText = "Connected+to+'"+chosen+"'+(random)"; 37 | try { 38 | ret = Reddit.newConnection(chosen, Type.Top, Time.PastMonth, 1000); 39 | redditMap.put(chosen, ret); 40 | } catch (Exception e) { 41 | respText = "Invite+required!"; 42 | } 43 | System.out.println("Chose '" + chosen + "' randomly"); 44 | 45 | new Message() 46 | .from(group) 47 | .to(json.getInt("peer_id")) 48 | .text(respText) 49 | .send((Callback[]) null); 50 | 51 | return ret; 52 | } 53 | 54 | public Reddit knownSubreddit(HashMap redditMap, String name) { 55 | if(redditMap.containsKey(name)) 56 | return redditMap.get(name); 57 | else { 58 | Reddit ret = null; 59 | String respText = "Connected+to+'"+name+"'"; 60 | try { 61 | ret = Reddit.newConnection(name, Type.Rising, Time.PastWeek, 100); 62 | } catch (Exception e) { 63 | respText = "Invite+required!"; 64 | } 65 | redditMap.put(name, ret); 66 | 67 | new Message() 68 | .from(group) 69 | .to(json.getInt("peer_id")) 70 | .text(respText) 71 | .send((Callback[]) null); 72 | 73 | return ret; 74 | } 75 | } 76 | 77 | 78 | 79 | public void sendPhoto(Post post) { 80 | String title = post.title().replaceAll(" ", "+"); 81 | 82 | new Message() 83 | .from(group) 84 | .to(json.getInt("peer_id")) 85 | .photo(post.image() == null ? "No+images+found" : post.image()) 86 | .text("Title:"+title) 87 | .send((Callback[]) null); 88 | } 89 | 90 | public void sendText(Post post) { 91 | String title = post.title().replaceAll(" ", "+"); 92 | String text = post.text().replaceAll(" ", "+"); 93 | String link = (post.link() == null) ? "" : post.link().replaceAll(" ", "+"); 94 | 95 | new Message() 96 | .from(group) 97 | .to(json.getInt("peer_id")) 98 | .text((post.text().isEmpty() ? "Title:+"+title + "+No+text+found" : text) + "\n+Link:+" + link) 99 | .send((Callback[]) null); 100 | } 101 | 102 | public void sendAny(Post post) { 103 | String title = post.title().replaceAll(" ", "+"); 104 | String text = post.text().replaceAll(" ", "+"); 105 | String link = (post.link() == null) ? "" : post.link().replaceAll(" ", "+"); 106 | 107 | Message m = new Message() 108 | .from(group) 109 | .to(json.getInt("peer_id")) 110 | .text((post.text().isEmpty() ? "Title:+"+title : "Title:+"+title+"+\n"+text) + "\n+Link:+" + link); 111 | 112 | if(post.image() != null) { 113 | if(post.image().endsWith("gif")) 114 | m.doc(post.image()); 115 | else 116 | m.photo(post.image()); 117 | } 118 | m.send((Callback[]) null); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/web/Connection.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.web; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStream; 10 | import java.net.HttpURLConnection; 11 | import java.net.URL; 12 | 13 | /** 14 | * Web work: get and post requests 15 | */ 16 | public final class Connection { 17 | 18 | private static final Logger LOG = LoggerFactory.getLogger(Connection.class); 19 | 20 | /** 21 | * Make GET-request 22 | * 23 | * @param urlString URL 24 | * @return String response body 25 | */ 26 | public static String getRequestResponse(String urlString) { 27 | try { 28 | urlString = urlString.replaceAll("\r", ""); 29 | urlString = urlString.replaceAll("\n", ""); 30 | 31 | URL url = new URL(urlString); 32 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 33 | 34 | conn.setConnectTimeout(30000); 35 | conn.setReadTimeout(30000); 36 | 37 | conn.setRequestMethod("GET"); 38 | conn.setRequestProperty("Accept-Charset", "utf-8"); 39 | 40 | int responseCode = conn.getResponseCode(); 41 | 42 | BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); 43 | String inputLine; 44 | StringBuilder responseSb = new StringBuilder(); 45 | 46 | while ((inputLine = in.readLine()) != null) { 47 | responseSb.append(inputLine); 48 | } 49 | in.close(); 50 | 51 | String response = responseSb.toString(); 52 | 53 | if (!(responseCode == HttpURLConnection.HTTP_OK)) { 54 | LOG.error("Response of 'get' request to url {} is not succesful, code is {} and response is {}", url, responseCode, response); 55 | } 56 | 57 | return response; 58 | } catch (IOException ignored) { 59 | LOG.error("IOException occured when processing request to {}, error is {}", urlString, ignored.toString()); 60 | ignored.printStackTrace(); 61 | return "{}"; 62 | } 63 | } 64 | 65 | /** 66 | * Make POST-request 67 | * 68 | * @param urlString URL 69 | * @param body Request body 70 | * @return String response body 71 | */ 72 | public static String postRequestResponse(String urlString, String body) { 73 | 74 | try { 75 | URL url = new URL(urlString); 76 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 77 | 78 | conn.setRequestMethod("POST"); 79 | conn.setRequestProperty("User-Agent", "VKAndroidApp/4.9-1118 (Android 5.1; SDK 22; armeabi-v7a; UMI IRON; ru)"); 80 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 81 | 82 | conn.setDoOutput(true); 83 | 84 | OutputStream os = conn.getOutputStream(); 85 | 86 | os.write(body.getBytes("UTF-8")); 87 | os.flush(); 88 | os.close(); 89 | 90 | int responseCode = conn.getResponseCode(); 91 | 92 | BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); 93 | String inputLine; 94 | StringBuilder responseSb = new StringBuilder(); 95 | 96 | while ((inputLine = in.readLine()) != null) { 97 | responseSb.append(inputLine); 98 | } 99 | in.close(); 100 | 101 | String response = responseSb.toString(); 102 | 103 | if (!(responseCode == HttpURLConnection.HTTP_OK)) { 104 | LOG.error("Response of 'post' request to url {} with body {} is not succesful, code is {} and response is {}", url, body, responseCode); 105 | } 106 | 107 | return response; 108 | } catch (IOException ignored) { 109 | LOG.error("IOException occured when processing request to {} with body {}, error is {}", urlString, body, ignored.toString()); 110 | return "{}"; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/Post.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.time.Instant; 5 | import java.time.ZoneId; 6 | import java.time.format.DateTimeFormatter; 7 | import java.time.format.FormatStyle; 8 | import java.util.Date; 9 | import java.util.Locale; 10 | 11 | import javax.net.ssl.HttpsURLConnection; 12 | 13 | import org.json.JSONArray; 14 | import org.json.JSONObject; 15 | 16 | public class Post { 17 | 18 | private final JSONObject post; 19 | 20 | Post(Object data) { 21 | post = (JSONObject) data; 22 | } 23 | 24 | // --------------------------------------- Content --------------------------------------- // 25 | 26 | public String title() { 27 | return data().getString("title"); 28 | } 29 | 30 | public String text() { 31 | return data().getString("selftext"); 32 | } 33 | 34 | public String image() { 35 | String url = data().getString("url"); 36 | 37 | if(checkIfURLIsImage(url)) 38 | return url; 39 | else 40 | return null; 41 | } 42 | 43 | public String video() { 44 | return extractVideo(); 45 | } 46 | 47 | // If some news article is linked idk 48 | public String link() { 49 | String possibleLink = data().getString("url"); 50 | 51 | if(possibleLink.contains(id())) // No links (be pointing to self) 52 | return null; 53 | else 54 | return possibleLink; 55 | } 56 | 57 | // --------------------------------------- Meta --------------------------------------- // 58 | 59 | public int score() { 60 | return data().getInt("ups") + data().getInt("downs"); 61 | } 62 | 63 | public Instant creationTime() { 64 | return Instant.ofEpochSecond(data().getLong("created_utc")); 65 | } 66 | 67 | public Instant timeSinceCreation() { 68 | return Instant.now().minusSeconds(creationTime().getEpochSecond()); 69 | } 70 | 71 | public String postTime() { 72 | Instant time = creationTime(); 73 | return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.UK).withZone(ZoneId.systemDefault()).format(time); 74 | } 75 | 76 | public String timeSincePost() { 77 | Instant time = creationTime(); 78 | return Date.from(time).toString(); 79 | } 80 | 81 | public String id() { 82 | return data().getString("id"); 83 | } 84 | 85 | public String kind() { 86 | return post.getString("kind"); 87 | } 88 | 89 | // --------------------------------------- The rest --------------------------------------- // 90 | 91 | // No need to implement all json fields as separate methods. Let users do it themselves duh 92 | /** 93 | * @return json with following structure: 94 | * { 95 | * kind: 96 | * data: {...} 97 | * } 98 | */ 99 | public JSONObject getJSON() { 100 | return post; 101 | } 102 | 103 | /** 104 | * Same as calling getJSON(), but without kind: field 105 | * @return json with following structure: 106 | * { 107 | * // lots of metadata crap here 108 | * } 109 | */ 110 | public JSONObject data() { 111 | return post.getJSONObject("data"); 112 | } 113 | 114 | 115 | 116 | private boolean checkIfURLIsImage(String url) { 117 | try { 118 | HttpsURLConnection check = Reddit.initConnection(url); 119 | return check.getContentType().startsWith("image"); 120 | } catch (Exception e) { 121 | return false; 122 | } 123 | } 124 | 125 | private String extractVideo() { 126 | try { 127 | boolean isCrosspostSource = data().isNull("crosspost_parent_list");// && !data().isNull("media"); // idk if the second is necessary 128 | 129 | if (!isCrosspostSource) { 130 | JSONArray parents = data().getJSONArray("crosspost_parent_list"); 131 | JSONObject crosspostSource = parents.getJSONObject(0); 132 | if (!crosspostSource.isNull("media")) { 133 | return parseMediaObject(crosspostSource.getJSONObject("media")); 134 | } else { 135 | return null; // Imply that crosspost depth is 1 136 | } 137 | } else { 138 | if(data().isNull("media")) 139 | return null; 140 | return parseMediaObject(data().getJSONObject("media")); 141 | } 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | return null; 145 | } 146 | } 147 | 148 | private String parseMediaObject(JSONObject mediaNotNull) { 149 | if (mediaNotNull.isNull("type")) { // null == hosted on reddit(?) 150 | return mediaNotNull.getJSONObject("reddit_video").getString("fallback_url"); 151 | } else { // hosted elsewhere 152 | String mediaType = mediaNotNull.getString("type"); 153 | 154 | if (mediaType.equals("gfycat.com")) { // if hosted on gfycat.com 155 | return mediaNotNull.getJSONObject("oembed").getString("thumbnail_url"); // Only returns a thumbnail gif. Later will write a decoder to get a full video link 156 | } 157 | // else if(other provider) { // if hosted somewhere else 158 | // } 159 | } 160 | 161 | return null; // if none match. should only be executed if encountered an uncpecified hosting 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/web/MultipartUtility.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.web; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.*; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | 11 | /** 12 | * OkHttp sending byte[] array not works with vk 13 | * Using this. 14 | * 15 | * Stealed from there. 16 | */ 17 | public class MultipartUtility { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(MultipartUtility.class); 20 | 21 | private HttpURLConnection httpConn; 22 | private DataOutputStream request; 23 | private final String boundary = "*****"; 24 | private final String crlf = "\r\n"; 25 | private final String twoHyphens = "--"; 26 | 27 | /** 28 | * This constructor initializes a new HTTP POST request with content type 29 | * is set to multipart/form-data 30 | */ 31 | public MultipartUtility(String requestURL) { 32 | 33 | try { 34 | URL url = new URL(requestURL); 35 | httpConn = (HttpURLConnection) url.openConnection(); 36 | httpConn.setUseCaches(false); 37 | httpConn.setDoOutput(true); 38 | httpConn.setDoInput(true); 39 | 40 | httpConn.setRequestMethod("POST"); 41 | httpConn.setRequestProperty("Connection", "Keep-Alive"); 42 | httpConn.setRequestProperty("Cache-Control", "no-cache"); 43 | httpConn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + this.boundary); 44 | 45 | request = new DataOutputStream(httpConn.getOutputStream()); 46 | } catch (IOException ignored) { 47 | LOG.error("Error when trying to connect to the url for uploading file in multipart/form-data, url: {}", requestURL); 48 | } 49 | } 50 | 51 | /** 52 | * Adds a upload file section to the request 53 | * 54 | * @param fieldName name of field in body of POST-request 55 | * @param uploadFile a File to be uploaded 56 | */ 57 | public void addFilePart(String fieldName, File uploadFile) { 58 | try { 59 | String fileName = uploadFile.getName(); 60 | request.writeBytes(this.twoHyphens + this.boundary + this.crlf); 61 | request.writeBytes("Content-Disposition: form-data; name=\"" + 62 | fieldName + "\";filename=\"" + 63 | fileName + "\"" + this.crlf); 64 | request.writeBytes(this.crlf); 65 | 66 | byte[] bytes = Files.readAllBytes(uploadFile.toPath()); 67 | request.write(bytes); 68 | } catch (IOException ignored) { 69 | LOG.error("Error when adding file as multipart/form-data field. Field name is {} and file path is {}.", fieldName, uploadFile.getAbsolutePath()); 70 | } 71 | } 72 | 73 | /** 74 | * Adds a upload file section to the request 75 | * 76 | * @param fieldName name of field in body of POST-requestx 77 | * @param bytes an array of bytes to be uploaded 78 | */ 79 | public void addBytesPart(String fieldName, String fileName, byte[] bytes) { 80 | try { 81 | request.writeBytes(this.twoHyphens + this.boundary + this.crlf); 82 | request.writeBytes("Content-Disposition: form-data; name=\"" + fieldName + "\";filename=\"" + fileName + "\"" + this.crlf); 83 | request.writeBytes(this.crlf); 84 | 85 | request.write(bytes); 86 | } catch (IOException ignored) { 87 | LOG.error("Error when adding bytes as multipart/form-data field. Field name is {} and file name is {}.", fieldName, fileName); 88 | } 89 | } 90 | 91 | /** 92 | * Completes the request and receives response from the server. 93 | * 94 | * @return a list of Strings as response in case the server returned 95 | * status OK, otherwise an exception is thrown. 96 | */ 97 | public String finish() { 98 | 99 | String response = "error"; 100 | 101 | try { 102 | request.writeBytes(this.crlf); 103 | request.writeBytes(this.twoHyphens + this.boundary + this.twoHyphens + this.crlf); 104 | 105 | request.flush(); 106 | request.close(); 107 | 108 | int status = httpConn.getResponseCode(); 109 | 110 | if (status == HttpURLConnection.HTTP_OK) { 111 | InputStream responseStream = new BufferedInputStream(httpConn.getInputStream()); 112 | 113 | BufferedReader responseStreamReader = new BufferedReader(new InputStreamReader(responseStream)); 114 | 115 | String line; 116 | StringBuilder stringBuilder = new StringBuilder(); 117 | 118 | while ((line = responseStreamReader.readLine()) != null) { 119 | stringBuilder.append(line).append("\n"); 120 | } 121 | responseStreamReader.close(); 122 | 123 | response = stringBuilder.toString(); 124 | httpConn.disconnect(); 125 | } else { 126 | LOG.error("Some error occured when receiving answer of sending file or bytes in multipart/form-date format: http status is {} and url is {}.", status, httpConn.getURL()); 127 | } 128 | } catch (IOException ignored) { 129 | LOG.error("Some error occured when receiving answer of sending file or bytes in multipart/form-date format: {}", ignored.toString()); 130 | } 131 | 132 | return response; 133 | } 134 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/Executor.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi; 2 | 3 | import com.petersamokhin.bots.sdk.utils.vkapi.calls.Call; 4 | import com.petersamokhin.bots.sdk.utils.vkapi.calls.CallAsync; 5 | import com.petersamokhin.bots.sdk.utils.vkapi.calls.CallSync; 6 | import com.petersamokhin.bots.sdk.utils.web.Connection; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.UnsupportedEncodingException; 14 | import java.net.URLDecoder; 15 | import java.util.ArrayList; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.stream.IntStream; 20 | 21 | import static com.petersamokhin.bots.sdk.clients.Client.scheduler; 22 | 23 | /** 24 | * Best way to use VK API: you can call up to 25 vk api methods by call execute once 25 | * Because without execute you only can call up to 3 methods per second 26 | *

27 | * See more: link 28 | */ 29 | public class Executor { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(Executor.class); 32 | 33 | public static boolean LOG_REQUESTS = false; 34 | 35 | /** 36 | * We can call 'execute' method no more than three times per second. 37 | * 1000/3 ~ 333 milliseconds 38 | */ 39 | private static final int delay = 335; 40 | 41 | /** 42 | * Queue of requests 43 | */ 44 | private volatile List queue = new ArrayList<>(); 45 | 46 | private final String URL = "https://api.vk.com/method/execute"; 47 | private final String accessToken; 48 | private final String V = "&v=" + 5.69; 49 | 50 | 51 | /** 52 | * Init executor 53 | *

54 | * All requests called directly by using client.api().call(...) 55 | * or indirectly (by calling methods that will use VK API) will be queued. 56 | * Every 350 milliseconds first 25 requests from queue will be executed. 57 | * VK response will be returned to the callback. 58 | *

59 | * Important: 60 | * 61 | * @param accessToken Method 'execute' will be called using this access_token. 62 | * @see API#callSync(String, Object) requests made by this method wont be queued, be careful. 63 | * And responses of callSync seems like {"response":{...}} and all are instances of JSONObject. 64 | * but from method 'execute' will be returned "response" object directly (can be integer, boolean etc). 65 | */ 66 | public Executor(String accessToken) { 67 | this.accessToken = "&access_token=" + accessToken; 68 | 69 | scheduler.scheduleWithFixedDelay(this::executing, 0, delay, TimeUnit.MILLISECONDS); 70 | } 71 | 72 | /** 73 | * Method that makes 'execute' requests 74 | * with first 25 calls from queue. 75 | */ 76 | private void executing() { 77 | 78 | List tmpQueue = new ArrayList<>(); 79 | int count = 0; 80 | 81 | for (Iterator iterator = queue.iterator(); iterator.hasNext() && count < 25; count++) { 82 | tmpQueue.add(iterator.next()); 83 | } 84 | 85 | queue.removeAll(tmpQueue); 86 | 87 | StringBuilder calls = new StringBuilder(); 88 | calls.append('['); 89 | 90 | for (int i = 0; i < count; i++) { 91 | String codeTmp = codeForExecute(tmpQueue.get(i)); 92 | calls.append(codeTmp); 93 | if (i < count - 1) { 94 | calls.append(','); 95 | } 96 | } 97 | calls.append(']'); 98 | 99 | String code = calls.toString(); 100 | 101 | // Execute 102 | if (count > 0) { 103 | String vkCallParams = "code=return " + code + ";" + accessToken + V; 104 | 105 | String responseString = Connection.postRequestResponse(URL, vkCallParams); 106 | 107 | if (LOG_REQUESTS) { 108 | LOG.error("New executing request response: {}", responseString); 109 | } 110 | 111 | JSONObject response; 112 | 113 | try { 114 | response = new JSONObject(responseString); 115 | } catch (JSONException e) { 116 | tmpQueue.forEach(call -> call.getCallback().onResult("false")); 117 | LOG.error("Bad response from executing: {}, params: {}", responseString, vkCallParams); 118 | return; 119 | } 120 | 121 | if (response.has("execute_errors")) { 122 | try { 123 | LOG.error("Errors when executing: {}, code: {}", response.get("execute_errors").toString(), URLDecoder.decode(code, "UTF-8")); 124 | } catch (UnsupportedEncodingException ignored) { 125 | } 126 | } 127 | 128 | if (!response.has("response")) { 129 | LOG.error("No 'response' object when executing code, VK response: {}", response); 130 | tmpQueue.forEach(call -> call.getCallback().onResult("false")); 131 | return; 132 | } 133 | 134 | JSONArray responses = response.getJSONArray("response"); 135 | 136 | IntStream.range(0, count).forEachOrdered(i -> tmpQueue.get(i).getCallback().onResult(responses.get(i))); 137 | } 138 | } 139 | 140 | /** 141 | * Method that makes string in json format from call object. 142 | * 143 | * @param call Call object 144 | * @return String 'API.method.name({param:value})' 145 | * @see Call 146 | * @see CallAsync 147 | * @see CallSync 148 | */ 149 | public String codeForExecute(Call call) { 150 | 151 | return "API." + call.getMethodName() + '(' + call.getParams().toString() + ')'; 152 | } 153 | 154 | /** 155 | * Method that puts all requests in a queue. 156 | * 157 | * @param call Call to be executed. 158 | */ 159 | public void execute(CallAsync call) { 160 | queue.add(call); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/net/x666c/simplereddit/Reddit.java: -------------------------------------------------------------------------------- 1 | package net.x666c.simplereddit; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.net.URL; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Random; 11 | import java.util.stream.Stream; 12 | 13 | import javax.net.ssl.HttpsURLConnection; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONObject; 17 | 18 | public class Reddit { 19 | 20 | private static final String REDDIT = "https://www.reddit.com/r/"; 21 | private static final String REQUEST = ".json?limit="; 22 | 23 | public static Reddit newConnection(String subreddit, Type postType, Time sort, int postLimitRelative) { 24 | return new Reddit(subreddit, postType, sort, postLimitRelative); 25 | } 26 | public static Reddit newConnection(String subreddit, Type postType, int postLimitRelative) { 27 | return newConnection(subreddit, postType, Time.PastMonth, postLimitRelative); 28 | } 29 | 30 | public static Post newConnection(String postLink) { 31 | try { 32 | HttpsURLConnection check = initConnection(postLink + "/.json"); 33 | 34 | String content = new BufferedReader(new InputStreamReader(check.getInputStream())).lines().reduce("", String::concat); 35 | 36 | JSONObject data = new JSONArray(content).getJSONObject(0).getJSONObject("data").getJSONArray("children").getJSONObject(0); 37 | return new Post(data); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | return null; 41 | } 42 | } 43 | 44 | private final String name; 45 | 46 | private HttpsURLConnection connection; 47 | 48 | private int requestedLength, actualLength; 49 | 50 | private String jsonLink; 51 | private JSONObject json; 52 | 53 | private List posts; 54 | private Post startingPost = null; 55 | 56 | private Reddit(String subreddit, Type type, Time sort, int limit) { 57 | this.name = subreddit; 58 | 59 | jsonLink = REDDIT + subreddit + "/" + type + "/" + REQUEST + limit + sort; 60 | System.out.println(jsonLink); 61 | 62 | requestedLength = limit; 63 | 64 | refresh(); 65 | } 66 | 67 | public void refresh() { 68 | try { 69 | 70 | if(startingPost != null) 71 | jsonLink += "&after=" + startingPost.kind() + "_" + startingPost.id(); 72 | 73 | connection = initConnection(jsonLink); // 1.56 seconds 74 | 75 | json = reparseJSON(); // 0.36 seconds 76 | 77 | actualLength = data().getInt("dist"); 78 | 79 | posts = new ArrayList<>(actualLength); 80 | 81 | JSONArray children = data().getJSONArray("children"); 82 | for (Object post : children) { 83 | posts.add(new Post(post)); // Can probably yield and unsorted list, oh well 84 | } 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | 90 | static HttpsURLConnection initConnection(String to) throws Exception { 91 | HttpsURLConnection connection = (HttpsURLConnection) new URL(to).openConnection(); 92 | connection.setRequestProperty("Accept-Language", "en-US,en"); 93 | //connection.setRequestProperty("Connection", "keep-alive"); // Hmmm 94 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 7.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0"); 95 | 96 | if(connection.getResponseCode() == 429) { // Too many requests 97 | throw new RuntimeException("Too Many Requests: 429"); 98 | } 99 | 100 | return connection; 101 | } 102 | 103 | private Stream getUpdatedStream() throws Exception { 104 | InputStream in = connection.getInputStream(); 105 | return new BufferedReader(new InputStreamReader(in), Short.MAX_VALUE).lines(); 106 | } 107 | 108 | private JSONObject reparseJSON() throws Exception { 109 | return new JSONObject(getUpdatedStream().reduce("", String::concat)); 110 | } 111 | 112 | 113 | // -------------------------------------- Public ------------------------------------------ // 114 | 115 | public JSONObject data() { 116 | return json.getJSONObject("data"); 117 | } 118 | 119 | public String modhash() { 120 | return data().getString("modhash"); 121 | } 122 | 123 | public String before() { 124 | return data().getString("before"); 125 | } 126 | 127 | public String after() { 128 | return data().getString("after"); 129 | } 130 | 131 | public int actualLength() { 132 | return actualLength; 133 | } 134 | 135 | public int requestedLength() { 136 | return requestedLength; 137 | } 138 | 139 | /** 140 | * Warning: implicitly calls refresh() 141 | * @param post Post to start with (exclusive) 142 | */ 143 | public void setStartingPost(Post post) { 144 | startingPost = post; 145 | refresh(); 146 | } 147 | 148 | public Post post(int index) { 149 | if(index < 0 || index > actualLength) { 150 | throw new IndexOutOfBoundsException("index Out of bounds: index="+index + " length="+actualLength); 151 | } 152 | 153 | return posts.get(index); 154 | } 155 | 156 | public Post randomPost() { 157 | return posts.get(new Random().nextInt(actualLength)); 158 | } 159 | 160 | public List posts() { 161 | ArrayList ret = new ArrayList<>(posts.size()); 162 | Collections.copy(ret, posts); 163 | return ret; 164 | } 165 | 166 | public List postRange(int from, int to) { 167 | return new ArrayList<>(posts.subList(from, to)); 168 | } 169 | 170 | public boolean validSubreddit() { 171 | return actualLength > 0; 172 | } 173 | 174 | public String name() { 175 | return name; 176 | } 177 | 178 | public static enum Type { 179 | Hot, 180 | New, 181 | Controversial, 182 | Top, 183 | Rising; 184 | 185 | public String toString() { 186 | return name().toLowerCase(); 187 | } 188 | } 189 | 190 | public static enum Time { 191 | PastHour("&t=hour"), 192 | PastDay("&t=day"), 193 | PastWeek("&t=week"), 194 | PastMonth("&t=month"), 195 | PastYear("&t=year"), 196 | OfAllTime("&t=all"); 197 | 198 | private final String val; 199 | private Time(String val) { 200 | this.val = val; 201 | } 202 | 203 | public String toString() { 204 | return val; 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/objects/Chat.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.objects; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.clients.Client; 5 | import com.petersamokhin.bots.sdk.clients.Group; 6 | import com.petersamokhin.bots.sdk.utils.Utils; 7 | import com.petersamokhin.bots.sdk.utils.web.MultipartUtility; 8 | import org.json.JSONArray; 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.net.MalformedURLException; 17 | import java.net.URL; 18 | import java.nio.file.Files; 19 | import java.nio.file.Paths; 20 | 21 | /** 22 | * Created by PeterSamokhin on 29/09/2017 02:49 23 | */ 24 | public class Chat { 25 | 26 | public static final Integer CHAT_PREFIX = 2000000000; 27 | 28 | private static final Logger LOG = LoggerFactory.getLogger(Chat.class); 29 | 30 | private Client client; 31 | private Integer chatId; 32 | 33 | public Chat(Client client, Integer chatId) { 34 | this.client = client; 35 | this.chatId = chatId; 36 | 37 | if (client instanceof Group) { 38 | LOG.error("Now groups can't work with chats, sorry."); 39 | } 40 | } 41 | 42 | public void addUser(Integer userId, Callback... callbacks) { 43 | this.client.api().call("messages.addChatUser", "{chat_id:" + (chatId - CHAT_PREFIX) + ",user_id:" + userId + "}", response -> { 44 | if (callbacks.length > 0) { 45 | callbacks[0].onResult(response); 46 | } 47 | }); 48 | } 49 | 50 | public void kickUser(Integer userId, Callback... callbacks) { 51 | this.client.api().call("messages.removeChatUser", "{chat_id:" + (chatId - CHAT_PREFIX) + ",user_id:" + userId + "}", response -> { 52 | if (callbacks.length > 0) { 53 | callbacks[0].onResult(response); 54 | } 55 | }); 56 | } 57 | 58 | public void deletePhoto(Callback... callbacks) { 59 | this.client.api().call("messages.deleteChatPhoto", "{chat_id:" + (chatId - CHAT_PREFIX) + "}", response -> { 60 | if (callbacks.length > 0) { 61 | callbacks[0].onResult(response); 62 | } 63 | }); 64 | } 65 | 66 | public void editTitle(String newTitle, Callback... callbacks) { 67 | this.client.api().call("messages.editChat", "{chat_id:" + (chatId - CHAT_PREFIX) + ",title:" + newTitle + "}", response -> { 68 | if (callbacks.length > 0) { 69 | callbacks[0].onResult(response); 70 | } 71 | }); 72 | } 73 | 74 | public void getUsers(String fields, Callback callback) { 75 | this.client.api().call("messages.getChatUsers", "{chat_id:" + (chatId - CHAT_PREFIX) + ",fields:" + fields + "}", response -> { 76 | callback.onResult(new JSONArray(response.toString())); 77 | }); 78 | } 79 | 80 | public void setPhoto(String photo, Callback callback) { 81 | 82 | String type = null; 83 | File photoFile = new File(photo); 84 | if (photoFile.exists()) { 85 | type = "fromFile"; 86 | } 87 | 88 | URL photoUrl = null; 89 | if (type == null) { 90 | try { 91 | photoUrl = new URL(photo); 92 | type = "fromUrl"; 93 | } catch (MalformedURLException ignored) { 94 | LOG.error("Error when trying add photo to message: file not found, or url is bad. Your param: {}", photo); 95 | callback.onResult("false"); 96 | return; 97 | } 98 | } 99 | 100 | byte[] photoBytes; 101 | switch (type) { 102 | 103 | case "fromFile": { 104 | try { 105 | photoBytes = Files.readAllBytes(Paths.get(photoFile.toURI())); 106 | } catch (IOException ignored) { 107 | LOG.error("Error when reading file {}", photoFile.getAbsolutePath()); 108 | callback.onResult("false"); 109 | return; 110 | } 111 | break; 112 | } 113 | 114 | case "fromUrl": { 115 | try { 116 | photoBytes = Utils.toByteArray(photoUrl); 117 | } catch (IOException ignored) { 118 | LOG.error("Error {} occured when reading URL {}", ignored.toString(), photo); 119 | callback.onResult("false"); 120 | return; 121 | } 122 | break; 123 | } 124 | 125 | default: { 126 | LOG.error("Bad 'photo' string: path to file, URL or already uploaded 'photo()_()' was expected."); 127 | callback.onResult("false"); 128 | return; 129 | } 130 | } 131 | 132 | if (photoBytes != null) { 133 | 134 | JSONObject params_getMessagesUploadServer = new JSONObject().put("chat_id", chatId); 135 | client.api().call("photos.getChatUploadServer", params_getMessagesUploadServer, response -> { 136 | 137 | if (response.toString().equalsIgnoreCase("false")) { 138 | LOG.error("Can't get messages upload server, aborting. Photo wont be attached to message."); 139 | callback.onResult(false); 140 | return; 141 | } 142 | 143 | String uploadUrl = new JSONObject(response.toString()).getString("upload_url"); 144 | 145 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 146 | multipartUtility.addBytesPart("file", "photo.png", photoBytes); 147 | 148 | String response_uploadFileString = multipartUtility.finish(); 149 | 150 | if (response_uploadFileString.length() < 2 || response_uploadFileString.contains("error") || !response_uploadFileString.contains("response")) { 151 | LOG.error("Photo wan't uploaded: {}", response_uploadFileString); 152 | callback.onResult("false"); 153 | return; 154 | } 155 | 156 | JSONObject getPhotoStringResponse; 157 | 158 | try { 159 | getPhotoStringResponse = new JSONObject(response_uploadFileString); 160 | } catch (JSONException ignored) { 161 | LOG.error("Bad response of uploading photo: {}", response_uploadFileString); 162 | callback.onResult("false"); 163 | return; 164 | } 165 | 166 | if (!getPhotoStringResponse.has("response")) { 167 | LOG.error("Bad response of uploading chat photo, no 'response' param: {}", getPhotoStringResponse.toString()); 168 | callback.onResult("false"); 169 | return; 170 | } 171 | 172 | String responseParam = getPhotoStringResponse.getString("response"); 173 | 174 | JSONObject params_photosSaveMessagesPhoto = new JSONObject().put("file", responseParam); 175 | 176 | client.api().call("messages.setChatPhoto", params_photosSaveMessagesPhoto, response1 -> { 177 | 178 | 179 | if (response1.toString().equalsIgnoreCase("false")) { 180 | LOG.error("Error when saving uploaded photo: response is 'false', see execution errors."); 181 | callback.onResult("false"); 182 | return; 183 | } 184 | 185 | callback.onResult(response1); 186 | }); 187 | }); 188 | } 189 | } 190 | 191 | public void getChatInfo(Callback callback) { 192 | 193 | client.api().call("messages.getChat", "{chat_id:" + chatId + "}", response -> 194 | callback.onResult((JSONObject) response) 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/API.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.clients.Client; 5 | import com.petersamokhin.bots.sdk.utils.Utils; 6 | import com.petersamokhin.bots.sdk.utils.vkapi.calls.CallAsync; 7 | import com.petersamokhin.bots.sdk.utils.vkapi.calls.CallSync; 8 | import com.petersamokhin.bots.sdk.utils.web.Connection; 9 | import org.json.JSONArray; 10 | import org.json.JSONObject; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.UnsupportedEncodingException; 15 | import java.net.URLEncoder; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | /** 20 | * Simple interacting with VK API 21 | */ 22 | public class API { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(API.class); 25 | 26 | private static Executor executor; 27 | 28 | private String URL = "https://api.vk.com/method/", V = "&v=" + 5.67; 29 | private String accessToken; 30 | 31 | private static boolean executionStarted = false; 32 | 33 | /** 34 | * Get the token from client 35 | * todo Not all methods available with group tokens, and few methods available without token 36 | * todo Need to make client with both tokens, or any another conclusion 37 | * 38 | * @param client Client with token 39 | */ 40 | public API(Client client) { 41 | this.accessToken = "&access_token=" + client.getAccessToken(); 42 | if (!executionStarted) { 43 | executor = new Executor(client.getAccessToken()); 44 | executionStarted = true; 45 | } 46 | } 47 | 48 | /** 49 | * todo Not all methods available with group tokens, and few methods available without token 50 | * todo Need to make client with both tokens, or any another conclusion 51 | * 52 | * @param token access_token 53 | */ 54 | public API(String token) { 55 | this.accessToken = "&access_token=" + token; 56 | if (!executionStarted) { 57 | executor = new Executor(token); 58 | executionStarted = true; 59 | } 60 | } 61 | 62 | /** 63 | * Call to VK API 64 | * 65 | * @param method Method name 66 | * @param params Params as string, JSONObject or Map 67 | * @param callback Callback to return the response 68 | */ 69 | public void call(String method, Object params, Callback callback) { 70 | 71 | try { 72 | JSONObject parameters = new JSONObject(); 73 | 74 | if (params != null) { 75 | boolean good = false; 76 | 77 | // Work with map 78 | if (params instanceof Map) { 79 | 80 | parameters = new JSONObject((Map) params); 81 | good = true; 82 | } 83 | 84 | // with JO 85 | if (params instanceof JSONObject) { 86 | parameters = (JSONObject) params; 87 | good = true; 88 | } 89 | 90 | // or string 91 | if (params instanceof String) { 92 | String s = params.toString(); 93 | if (s.startsWith("{")) { 94 | parameters = new JSONObject(s); 95 | good = true; 96 | } else { 97 | if (s.contains("&") && s.contains("=")) { 98 | parameters = Utils.explodeQuery(s); 99 | good = true; 100 | } 101 | } 102 | } 103 | 104 | if (good) { 105 | CallAsync call = new CallAsync(method, parameters, callback); 106 | executor.execute(call); 107 | } 108 | } 109 | } catch (Exception e) { 110 | LOG.error("Some error occured when calling VK API method {} with params {}, error is {}", method, params.toString(), e); 111 | } 112 | } 113 | 114 | /** 115 | * Call to VK API 116 | * 117 | * @param callback Callback to return the response 118 | * @param method Method name 119 | * @param params Floating count of params 120 | */ 121 | public void call(Callback callback, String method, Object... params) { 122 | 123 | try { 124 | if (params != null) { 125 | 126 | if (params.length == 1) { 127 | this.call(method, params[0], callback); 128 | } 129 | 130 | if (params.length > 1) { 131 | 132 | if (params.length % 2 == 0) { 133 | Map map = new HashMap<>(); 134 | 135 | for (int i = 0; i < params.length - 1; i += 2) { 136 | map.put(params[i].toString(), params[i + 1]); 137 | } 138 | 139 | this.call(method, map, callback); 140 | } 141 | } 142 | } 143 | } catch (Exception e) { 144 | LOG.error("Some error occured when calling VK API: {}", e); 145 | } 146 | } 147 | 148 | /** 149 | * Call to 'execute' method, because can not call API.execute inside execute. 150 | * More: link; 151 | */ 152 | public JSONObject execute(String code) { 153 | 154 | return new JSONObject(callSync("execute", new JSONObject().put("code", code))); 155 | } 156 | 157 | /** 158 | * Execute float count of calls, up to 25 159 | * 160 | * @param calls single call to VK API or calls separated by comma. 161 | * @see CallAsync 162 | */ 163 | public void execute(CallAsync... calls) { 164 | if (calls.length < 26) { 165 | for (CallAsync call : calls) { 166 | executor.execute(call); 167 | } 168 | } else { 169 | CallAsync[] newCalls = new CallAsync[25]; 170 | System.arraycopy(calls, 0, newCalls, 0, 25); 171 | for (CallAsync call : newCalls) { 172 | executor.execute(call); 173 | } 174 | } 175 | } 176 | 177 | /** 178 | * Execute float count of calls, up to 25 179 | * 180 | * @param calls single call to VK API or calls separated by comma. 181 | * @return JSONArray with responses of calls 182 | * @see CallSync 183 | */ 184 | public JSONArray execute(CallSync... calls) { 185 | 186 | StringBuilder code = new StringBuilder("return ["); 187 | 188 | for (int i = 0; i < calls.length; i++) { 189 | String codeTmp = executor.codeForExecute(calls[i]); 190 | code.append(codeTmp); 191 | if (i < calls.length - 1) { 192 | code.append(','); 193 | } 194 | } 195 | code.append("];"); 196 | 197 | JSONObject response = null; 198 | try { 199 | response = new JSONObject(callSync("execute", new JSONObject().put("code", URLEncoder.encode(code.toString(), "UTF-8")))); 200 | } catch (UnsupportedEncodingException ignored) { 201 | } 202 | 203 | return response.getJSONArray("response"); 204 | } 205 | 206 | /** 207 | * Call to VK API 208 | * 209 | * @param method Method name 210 | * @param params Params as string, JSONObject or Map 211 | * @return JSONObject response of VK answer 212 | * @deprecated not safe to use this method, because all async methods are in queue 213 | * and will be called in execute method, that can call 25 methods by one call 214 | */ 215 | @Deprecated 216 | public String callSync(String method, Object params) { 217 | 218 | try { 219 | if (params != null) { 220 | 221 | String paramsString; 222 | 223 | // Work with map 224 | if (params instanceof Map) { 225 | 226 | paramsString = Utils.MapToURLParamsQuery(new JSONObject((Map) params)); 227 | 228 | } else { 229 | 230 | paramsString = String.valueOf(params); 231 | 232 | if (paramsString.startsWith("{")) { 233 | 234 | paramsString = Utils.MapToURLParamsQuery(new JSONObject(paramsString)); 235 | 236 | } 237 | } 238 | 239 | String query = URL + method + "?" + paramsString + accessToken + V; 240 | 241 | return Connection.getRequestResponse(query); 242 | } 243 | } catch (Exception e) { 244 | LOG.error("Some error occured when calling VK API: {}", e); 245 | } 246 | return "error"; 247 | } 248 | 249 | /** 250 | * Call to VK API 251 | * 252 | * @param method Method name 253 | * @param params Floating count of params 254 | * @return JSONObject response of VK answer 255 | */ 256 | @Deprecated 257 | public String callSync(String method, Object... params) { 258 | 259 | try { 260 | if (params != null && params.length > 0) { 261 | 262 | String query = URL + method + "?" + Utils.paramsToString(params) + accessToken + V; 263 | 264 | return Connection.getRequestResponse(query); 265 | } 266 | } catch (Exception e) { 267 | LOG.error("Some error occured when calling VK API: {}", e); 268 | } 269 | 270 | return ""; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/clients/Client.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.clients; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.callbacks.CallbackDouble; 5 | import com.petersamokhin.bots.sdk.callbacks.CallbackFourth; 6 | import com.petersamokhin.bots.sdk.callbacks.CallbackTriple; 7 | import com.petersamokhin.bots.sdk.longpoll.LongPoll; 8 | import com.petersamokhin.bots.sdk.objects.Chat; 9 | import com.petersamokhin.bots.sdk.objects.Message; 10 | import com.petersamokhin.bots.sdk.utils.vkapi.API; 11 | import org.json.JSONArray; 12 | import org.json.JSONObject; 13 | 14 | import java.util.List; 15 | import java.util.concurrent.*; 16 | 17 | /** 18 | * Main client class, that contains all necessary methods and fields 19 | * for base work with VK and longpoll server 20 | */ 21 | @SuppressWarnings("unused") 22 | public abstract class Client { 23 | 24 | /* 25 | * Executor services for threadsafing and fast work 26 | */ 27 | public static final ExecutorService service = Executors.newCachedThreadPool(); 28 | public static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); 29 | 30 | /* 31 | * Main params 32 | */ 33 | private String accessToken; 34 | private Integer id; 35 | private static API api; 36 | private LongPoll longPoll = null; 37 | 38 | public CopyOnWriteArrayList commands = new CopyOnWriteArrayList<>(); 39 | private ConcurrentHashMap chats = new ConcurrentHashMap<>(); 40 | 41 | /** 42 | * Default constructor 43 | * 44 | * @param access_token Access token key 45 | */ 46 | Client(String access_token) { 47 | 48 | this.accessToken = access_token; 49 | this.longPoll = new LongPoll(this); 50 | 51 | api = new API(this); 52 | } 53 | 54 | /** 55 | * Default constructor 56 | * 57 | * @param id User or group id 58 | * @param access_token Access token key 59 | */ 60 | Client(Integer id, String access_token) { 61 | 62 | this.id = id; 63 | this.accessToken = access_token; 64 | this.longPoll = new LongPoll(this); 65 | 66 | api = new API(this); 67 | } 68 | 69 | /** 70 | * If need to set not default longpoll 71 | */ 72 | public void setLongPoll(LongPoll LP) { 73 | 74 | this.longPoll.off(); 75 | this.longPoll = LP; 76 | } 77 | 78 | public Chat chat(Integer chatId) { 79 | 80 | if (!this.chats.containsKey(chatId)) { 81 | this.chats.put(chatId, new Chat(this, chatId)); 82 | } 83 | 84 | return this.chats.get(chatId); 85 | } 86 | 87 | /** 88 | * Get longpoll of current client 89 | */ 90 | public LongPoll longPoll() { 91 | return longPoll; 92 | } 93 | 94 | /** 95 | * Get API for making requests 96 | */ 97 | public API api() { 98 | return api; 99 | } 100 | 101 | /** 102 | * If the client need to start typing 103 | * after receiving message 104 | * and until client's message is sent 105 | */ 106 | public void enableTyping(boolean enable) { 107 | this.longPoll().enableTyping(enable); 108 | } 109 | 110 | /* On every event */ 111 | 112 | public void onLongPollEvent(Callback callback) { 113 | this.longPoll().registerCallback("OnEveryLongPollEventCallback", callback); 114 | } 115 | 116 | /* Chats */ 117 | public void onChatJoin(CallbackTriple callback) { 118 | this.longPoll().registerChatCallback("OnChatJoinCallback", callback); 119 | } 120 | 121 | public void onChatLeave(CallbackTriple callback) { 122 | this.longPoll().registerChatCallback("OnChatLeaveCallback", callback); 123 | } 124 | 125 | public void onChatTitleChanged(CallbackFourth callback) { 126 | this.longPoll().registerChatCallback("OnChatTitleChangedCallback", callback); 127 | } 128 | 129 | public void onChatPhotoChanged(CallbackTriple callback) { 130 | this.longPoll().registerChatCallback("onChatPhotoChangedCallback", callback); 131 | } 132 | 133 | public void onChatPhotoRemoved(CallbackDouble callback) { 134 | this.longPoll().registerChatCallback("onChatPhotoRemovedCallback", callback); 135 | } 136 | 137 | public void onChatCreated(CallbackTriple callback) { 138 | this.longPoll().registerChatCallback("onChatCreatedCallback", callback); 139 | } 140 | 141 | /* Messages */ 142 | public void onChatMessage(Callback callback) { 143 | this.longPoll().registerCallback("OnChatMessageCallback", callback); 144 | } 145 | 146 | public void onEveryMessage(Callback callback) { 147 | this.longPoll().registerCallback("OnEveryMessageCallback", callback); 148 | } 149 | 150 | public void onMessageWithFwds(Callback callback) { 151 | this.longPoll().registerCallback("OnMessageWithFwdsCallback", callback); 152 | } 153 | 154 | public void onAudioMessage(Callback callback) { 155 | this.longPoll().registerCallback("OnAudioMessageCallback", callback); 156 | } 157 | 158 | public void onDocMessage(Callback callback) { 159 | this.longPoll().registerCallback("OnDocMessageCallback", callback); 160 | } 161 | 162 | public void onGifMessage(Callback callback) { 163 | this.longPoll().registerCallback("OnGifMessageCallback", callback); 164 | } 165 | 166 | public void onLinkMessage(Callback callback) { 167 | this.longPoll().registerCallback("OnLinkMessageCallback", callback); 168 | } 169 | 170 | public void onMessage(Callback callback) { 171 | this.longPoll().registerCallback("OnMessageCallback", callback); 172 | } 173 | 174 | public void onPhotoMessage(Callback callback) { 175 | this.longPoll().registerCallback("OnPhotoMessageCallback", callback); 176 | } 177 | 178 | public void onSimpleTextMessage(Callback callback) { 179 | this.longPoll().registerCallback("OnSimpleTextMessageCallback", callback); 180 | } 181 | 182 | public void onStickerMessage(Callback callback) { 183 | this.longPoll().registerCallback("OnStickerMessageCallback", callback); 184 | } 185 | 186 | public void onTyping(Callback callback) { 187 | this.longPoll().registerCallback("OnTypingCallback", callback); 188 | } 189 | 190 | public void onVideoMessage(Callback callback) { 191 | this.longPoll().registerCallback("OnVideoMessageCallback", callback); 192 | } 193 | 194 | public void onVoiceMessage(Callback callback) { 195 | this.longPoll().registerCallback("OnVoiceMessageCallback", callback); 196 | } 197 | 198 | public void onWallMessage(Callback callback) { 199 | this.longPoll().registerCallback("OnWallMessageCallback", callback); 200 | } 201 | 202 | /* Commands */ 203 | 204 | public void onCommand(Object command, Callback callback) { 205 | this.commands.add(new Command(command, callback)); 206 | } 207 | 208 | public void onCommand(Callback callback, Object... commands) { 209 | this.commands.add(new Command(commands, callback)); 210 | } 211 | 212 | public void onCommand(Object[] commands, Callback callback) { 213 | this.commands.add(new Command(commands, callback)); 214 | } 215 | 216 | public void onCommand(List list, Callback callback) { 217 | this.commands.add(new Command(list, callback)); 218 | } 219 | 220 | 221 | /** 222 | * If true, all updates from longpoll server 223 | * will be logged to level 'INFO' 224 | */ 225 | public void enableLoggingUpdates(boolean enable) { 226 | this.longPoll().enableLoggingUpdates(enable); 227 | } 228 | 229 | /** 230 | * Command object 231 | */ 232 | public class Command { 233 | private Object[] commands; 234 | private Callback callback; 235 | 236 | public Command(Object[] commands, Callback callback) { 237 | this.commands = commands; 238 | this.callback = callback; 239 | } 240 | 241 | public Command(Object command, Callback callback) { 242 | this.commands = new Object[]{command}; 243 | this.callback = callback; 244 | } 245 | 246 | public Command(List command, Callback callback) { 247 | this.commands = command.toArray(); 248 | this.callback = callback; 249 | } 250 | 251 | public Object[] getCommands() { 252 | return commands; 253 | } 254 | 255 | public Callback getCallback() { 256 | return callback; 257 | } 258 | } 259 | 260 | // Getters and setters 261 | 262 | public String getAccessToken() { 263 | return accessToken; 264 | } 265 | 266 | public void setAccessToken(String accessToken) { 267 | this.accessToken = accessToken; 268 | } 269 | 270 | public Integer getId() { 271 | return id; 272 | } 273 | 274 | public void setId(Integer id) { 275 | this.id = id; 276 | } 277 | 278 | @Override 279 | public String toString() { 280 | return "Client{" + 281 | "accessToken='" + accessToken + '\'' + 282 | ", id=" + id + 283 | '}'; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.*; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | import java.net.URLConnection; 12 | import java.net.URLEncoder; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * Utils class with useful methods 20 | */ 21 | public class Utils { 22 | 23 | private static final Logger LOG = LoggerFactory.getLogger(Utils.class); 24 | 25 | /** 26 | * Analog of JS setTimeout 27 | */ 28 | public static void setTimeout(Runnable runnable, int delay) { 29 | new Thread(() -> { 30 | try { 31 | Thread.sleep(delay); 32 | runnable.run(); 33 | } catch (InterruptedException e) { 34 | System.err.println(e.toString()); 35 | } 36 | }).start(); 37 | } 38 | 39 | /** 40 | * Convert to URL params query 41 | * 42 | * @param arr Map or JSONObject 43 | * @return Query 44 | */ 45 | public static String MapToURLParamsQuery(Object arr) { 46 | 47 | String answer = null; 48 | 49 | if (arr instanceof JSONObject) { 50 | JSONObject j = (JSONObject) arr; 51 | answer = j.toMap().entrySet().stream() 52 | .map(p -> p.getKey() + "=" + toQueryString(p.getValue())) 53 | .reduce((p1, p2) -> p1 + "&" + p2) 54 | .orElse(""); 55 | } else if (arr instanceof Map) { 56 | Map map = (Map) arr; 57 | answer = map.entrySet().stream() 58 | .map(p -> p.getKey() + "=" + toQueryString(p.getValue())) 59 | .reduce((p1, p2) -> p1 + "&" + p2) 60 | .orElse(""); 61 | } 62 | 63 | return answer; 64 | } 65 | 66 | /** 67 | * Arrays and lists to comma separated string 68 | */ 69 | public static String toQueryString(Object o) { 70 | 71 | if (o instanceof List) { 72 | return toQueryString(((List) o).toArray()); 73 | } 74 | 75 | if (o instanceof String[]) { 76 | return String.join(",", (String[]) o); 77 | } 78 | 79 | if (o instanceof Object[]) { 80 | Object[] a = (Object[]) o; 81 | String[] s = new String[a.length]; 82 | for (int i = 0; i < a.length; i++) 83 | s[i] = String.valueOf(a[i]); 84 | return toQueryString(s); 85 | } 86 | 87 | if (o instanceof JSONArray) { 88 | return ((JSONArray) o).join(","); 89 | } 90 | 91 | return o.toString(); 92 | } 93 | 94 | /** 95 | * Params from varargs: ["user_id", 62802565] to "&user_id=62802565" 96 | * 97 | * @param params Params 98 | * @return String 99 | */ 100 | public static String paramsToString(Object... params) { 101 | if (params.length % 2 != 0) { 102 | return null; 103 | } else { 104 | StringBuilder sb = new StringBuilder(); 105 | 106 | for (int i = 0; i < params.length; i++) { 107 | if (i % 2 == 0) { 108 | sb.append("&").append(String.valueOf(params[i])).append("="); 109 | } else { 110 | sb.append(String.valueOf(params[i])); 111 | } 112 | } 113 | 114 | return sb.toString(); 115 | } 116 | } 117 | 118 | /** 119 | * Convert params query to map 120 | */ 121 | public static JSONObject explodeQuery(String query) { 122 | 123 | try { 124 | query = URLEncoder.encode(query, "UTF-8"); 125 | } catch (UnsupportedEncodingException ignored) { 126 | } 127 | 128 | Map map = new HashMap<>(); 129 | 130 | String[] arr = query.split("&"); 131 | 132 | for (String param : arr) { 133 | String[] tmp_arr = param.split("="); 134 | String key = tmp_arr[0], value = tmp_arr[1]; 135 | 136 | if (tmp_arr[1].contains(",")) { 137 | map.put(key, new JSONArray(Arrays.asList(value.split(",")))); 138 | } else { 139 | map.put(key, value); 140 | } 141 | } 142 | 143 | return new JSONObject(map); 144 | } 145 | 146 | /** 147 | * Calculcating size of file in url 148 | * 149 | * @param url URL 150 | * @param dim Bits, KBits or MBits 151 | * @return Size 152 | */ 153 | public static int sizeOfFile(String url, String dim) { 154 | 155 | try { 156 | URL URL = new URL(url); 157 | HttpURLConnection conn = (HttpURLConnection) URL.openConnection(); 158 | conn.getInputStream(); 159 | 160 | double sizeInBits = conn.getContentLength(); 161 | 162 | switch (dim) { 163 | case "bits": { 164 | return (int) sizeInBits; 165 | } 166 | case "kbits": { 167 | return (int) Math.round(sizeInBits / 1024.0); 168 | } 169 | case "mbits": { 170 | return (int) Math.round(sizeInBits / (1024.0 * 1024.0)); 171 | } 172 | } 173 | } catch (IOException ignored) { 174 | LOG.error("IOException when calculating size of file {} , error: {}", url, ignored.toString()); 175 | } 176 | 177 | return 0; 178 | } 179 | 180 | public static String guessFileNameByContentType(String contentType) { 181 | 182 | contentType = contentType 183 | .replace("mpeg", "mp3") 184 | .replace("svg+xml", "svg") 185 | .replace("javascript", "js") 186 | .replace("plain", "txt") 187 | .replace("markdown", "md"); 188 | 189 | String mainType = contentType.substring(0, contentType.indexOf('/')); 190 | if (contentType.contains(" ")) { 191 | contentType = contentType.substring(0, contentType.indexOf(' ')); 192 | } 193 | String subType = contentType.substring(contentType.lastIndexOf('/') + 1); 194 | if (subType.contains("-") || subType.contains(".") || subType.contains("+")) 195 | subType = "unknown"; 196 | 197 | return mainType + '.' + subType; 198 | } 199 | 200 | /* 201 | * Methods from commons-lang library of Apache 202 | * Added to not use the library for several methods 203 | */ 204 | 205 | public static byte[] toByteArray(URL url) throws IOException { 206 | URLConnection conn = url.openConnection(); 207 | 208 | byte[] var2; 209 | try { 210 | var2 = toByteArray(conn); 211 | } finally { 212 | close(conn); 213 | } 214 | 215 | return var2; 216 | } 217 | 218 | public static byte[] toByteArray(URLConnection urlConn) throws IOException { 219 | InputStream inputStream = urlConn.getInputStream(); 220 | 221 | byte[] var2; 222 | try { 223 | var2 = toByteArray(inputStream); 224 | } finally { 225 | inputStream.close(); 226 | } 227 | 228 | return var2; 229 | } 230 | 231 | public static byte[] toByteArray(InputStream input) throws IOException { 232 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 233 | copy((InputStream) input, (OutputStream) output); 234 | return output.toByteArray(); 235 | } 236 | 237 | public static int copy(InputStream input, OutputStream output) throws IOException { 238 | long count = copyLarge(input, output); 239 | return count > 2147483647L ? -1 : (int) count; 240 | } 241 | 242 | public static long copyLarge(InputStream input, OutputStream output) throws IOException { 243 | return copy(input, output, 4096); 244 | } 245 | 246 | public static long copy(InputStream input, OutputStream output, int bufferSize) throws IOException { 247 | return copyLarge(input, output, new byte[bufferSize]); 248 | } 249 | 250 | public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) throws IOException { 251 | long count; 252 | int n; 253 | for (count = 0L; (n = input.read(buffer)) != -1; count += (long) n) { 254 | output.write(buffer, 0, n); 255 | } 256 | 257 | return count; 258 | } 259 | 260 | public static void close(URLConnection conn) { 261 | if (conn instanceof HttpURLConnection) { 262 | ((HttpURLConnection) conn).disconnect(); 263 | } 264 | } 265 | 266 | public static void copyURLToFile(URL source, File destination, int connectionTimeout, int readTimeout) throws IOException { 267 | URLConnection connection = source.openConnection(); 268 | connection.setConnectTimeout(connectionTimeout); 269 | connection.setReadTimeout(readTimeout); 270 | copyInputStreamToFile(connection.getInputStream(), destination); 271 | } 272 | 273 | public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { 274 | try { 275 | copyToFile(source, destination); 276 | } finally { 277 | closeQuietly(source); 278 | } 279 | } 280 | 281 | public static void copyToFile(InputStream source, File destination) throws IOException { 282 | FileOutputStream output = openOutputStream(destination); 283 | 284 | try { 285 | copy(source, output); 286 | output.close(); 287 | } finally { 288 | closeQuietly(output); 289 | } 290 | } 291 | 292 | public static FileOutputStream openOutputStream(File file) throws IOException { 293 | return openOutputStream(file, false); 294 | } 295 | 296 | public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { 297 | if (file.exists()) { 298 | if (file.isDirectory()) { 299 | throw new IOException("File '" + file + "' exists but is a directory"); 300 | } 301 | 302 | if (!file.canWrite()) { 303 | throw new IOException("File '" + file + "' cannot be written to"); 304 | } 305 | } else { 306 | File parent = file.getParentFile(); 307 | if (parent != null && !parent.mkdirs() && !parent.isDirectory()) { 308 | throw new IOException("Directory '" + parent + "' could not be created"); 309 | } 310 | } 311 | 312 | return new FileOutputStream(file, append); 313 | } 314 | 315 | public static void closeQuietly(OutputStream output) { 316 | closeQuietly((Closeable) output); 317 | } 318 | 319 | public static void closeQuietly(Closeable closeable) { 320 | try { 321 | if (closeable != null) { 322 | closeable.close(); 323 | } 324 | } catch (IOException var2) { 325 | } 326 | 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/longpoll/LongPoll.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.longpoll; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.AbstractCallback; 4 | import com.petersamokhin.bots.sdk.callbacks.Callback; 5 | import com.petersamokhin.bots.sdk.clients.Client; 6 | import com.petersamokhin.bots.sdk.longpoll.responses.GetLongPollServerResponse; 7 | import com.petersamokhin.bots.sdk.utils.web.Connection; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * com.petersamokhin.bots.sdk.Main class for work with VK longpoll server 15 | * More: link 16 | */ 17 | public class LongPoll { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(LongPoll.class); 20 | 21 | private String server = null; 22 | private String key = null; 23 | private Integer ts = null; 24 | public Integer pts = null; 25 | 26 | private Integer wait = 25; 27 | 28 | /** 29 | * 2 + 32 + 128 30 | * attachments + pts + random_id 31 | */ 32 | private Integer mode = 162; 33 | 34 | private Integer version = 2; 35 | private Integer need_pts = 1; 36 | private Double API = 5.67; 37 | 38 | private volatile boolean longpollIsOn = false; 39 | 40 | private UpdatesHandler updatesHandler; 41 | private Client client; 42 | 43 | /** 44 | * If true, all updates from longpoll server 45 | * will be logged to level 'INFO' 46 | */ 47 | private volatile boolean logUpdates = false; 48 | 49 | /** 50 | * Simple default constructor that requires only access token 51 | * 52 | * @param client client with your access token key, more: link 53 | */ 54 | public LongPoll(Client client) { 55 | 56 | this.updatesHandler = new UpdatesHandler(client); 57 | this.updatesHandler.start(); 58 | this.client = client; 59 | 60 | boolean dataSetted = setData(null, null, null, null, null); 61 | 62 | while (!dataSetted) { 63 | LOG.error("Some error occured when trying to get longpoll settings, aborting. Trying again in 1 sec."); 64 | try { 65 | Thread.sleep(1000); 66 | } catch (InterruptedException ignored) { 67 | } 68 | dataSetted = setData(null, null, null, null, null); 69 | } 70 | 71 | if (!longpollIsOn) { 72 | longpollIsOn = true; 73 | Thread threadLongpollListener = new Thread(this::startListening); 74 | threadLongpollListener.setName("threadLongpollListener"); 75 | threadLongpollListener.start(); 76 | } 77 | } 78 | 79 | /** 80 | * Custom constructor 81 | * 82 | * @param client client with your access token key, more: link 83 | * @param need_pts more: link 84 | * @param version more: link 85 | * @param API more: link 86 | * @param wait more: link 87 | * @param mode more: link 88 | */ 89 | public LongPoll(Client client, Integer need_pts, Integer version, Double API, Integer wait, Integer mode) { 90 | 91 | this.updatesHandler = new UpdatesHandler(client); 92 | this.updatesHandler.start(); 93 | this.client = client; 94 | 95 | boolean dataSetted = setData(need_pts, version, API, wait, mode); 96 | 97 | while (!dataSetted) { 98 | LOG.error("Some error occured when trying to get longpoll settings, aborting. Trying again in 1 sec."); 99 | try { 100 | Thread.sleep(1000); 101 | } catch (InterruptedException ignored) { 102 | } 103 | dataSetted = setData(need_pts, version, API, wait, mode); 104 | } 105 | 106 | if (!longpollIsOn) { 107 | longpollIsOn = true; 108 | Thread threadLongpollListener = new Thread(this::startListening); 109 | threadLongpollListener.setName("threadLongpollListener"); 110 | threadLongpollListener.start(); 111 | } 112 | } 113 | 114 | /** 115 | * If you need to set new longpoll server, or restart listening 116 | * off old before. 117 | */ 118 | public void off() { 119 | longpollIsOn = false; 120 | } 121 | 122 | /** 123 | * Add callback to the map 124 | * 125 | * @param name Callback name 126 | * @param callback Callback 127 | */ 128 | public void registerCallback(String name, Callback callback) { 129 | updatesHandler.registerCallback(name, callback); 130 | } 131 | 132 | /** 133 | * Add callback to the map 134 | * 135 | * @param name Callback name 136 | * @param callback Callback 137 | */ 138 | public void registerChatCallback(String name, AbstractCallback callback) { 139 | updatesHandler.registerChatCallback(name, callback); 140 | } 141 | 142 | /** 143 | * Setting all necessary parameters 144 | * 145 | * @param need_pts param, info: link 146 | * @param version param, info: link 147 | * @param API param, info: link 148 | * @param wait param, info: link 149 | * @param mode param, info: link 150 | */ 151 | private boolean setData(Integer need_pts, Integer version, Double API, Integer wait, Integer mode) { 152 | 153 | this.need_pts = need_pts == null ? this.need_pts : need_pts; 154 | this.version = version == null ? this.version : version; 155 | this.API = API == null ? this.API : API; 156 | this.wait = wait == null ? this.wait : wait; 157 | this.mode = mode == null ? this.mode : mode; 158 | 159 | GetLongPollServerResponse serverResponse = getLongPollServer(client.getAccessToken()); 160 | 161 | if (serverResponse == null) { 162 | LOG.error("Some error occured, bad response returned from getting LongPoll server settings (server, key, ts, pts)."); 163 | return false; 164 | } 165 | 166 | this.server = serverResponse.getServer(); 167 | this.key = serverResponse.getKey(); 168 | this.ts = serverResponse.getTs(); 169 | this.pts = serverResponse.getPts(); 170 | 171 | return true; 172 | } 173 | 174 | /** 175 | * First getting of longpoll server params 176 | * 177 | * @param access_token Access token 178 | * @return LongPoll params 179 | */ 180 | private GetLongPollServerResponse getLongPollServer(String access_token) { 181 | 182 | StringBuilder query = new StringBuilder(); 183 | query.append("https://api.vk.com/method/messages.getLongPollServer?need_pts=").append(need_pts).append("&lp_version=").append(version).append("&access_token=").append(access_token).append("&v=").append(API); 184 | 185 | JSONObject response; 186 | 187 | try { 188 | response = new JSONObject(Connection.getRequestResponse(query.toString())); 189 | } catch (JSONException e) { 190 | LOG.error("Bad responce of getting longpoll server."); 191 | return null; 192 | } 193 | 194 | LOG.info("GetLongPollServerResponse: \n{}\n", response); 195 | 196 | if (!response.has("response") || !response.getJSONObject("response").has("key") || !response.getJSONObject("response").has("server") || !response.getJSONObject("response").has("ts")) { 197 | LOG.error("Bad response of getting longpoll server!\nQuery: {}\n Response: {}", query, response); 198 | return null; 199 | } 200 | 201 | JSONObject data = response.getJSONObject("response"); 202 | 203 | return new GetLongPollServerResponse( 204 | data.getString("key"), 205 | data.getString("server"), 206 | data.getInt("ts"), 207 | data.getInt("pts") 208 | ); 209 | } 210 | 211 | 212 | /** 213 | * Listening to events from VK longpoll server 214 | * and call callbacks on events. 215 | * You can override only necessary methods in callback to get necessary events. 216 | */ 217 | private void startListening() { 218 | 219 | LOG.info("Started listening to events from VK LongPoll server..."); 220 | 221 | while (longpollIsOn) { 222 | 223 | JSONObject response; 224 | String responseString = "{}"; 225 | 226 | try { 227 | String query = "https://" + server + "?act=a_check&key=" + key + "&ts=" + ts + "&wait=" + wait + "&mode=" + mode + "&version=" + version + "&msgs_limit=100000"; 228 | responseString = Connection.getRequestResponse(query); 229 | response = new JSONObject(responseString); 230 | } catch (JSONException ignored) { 231 | LOG.error("Some error occured, no updates got from longpoll server: {}", responseString); 232 | try { 233 | Thread.sleep(1000); 234 | } catch (InterruptedException ignored1) { 235 | } 236 | continue; 237 | } 238 | 239 | if (logUpdates) { 240 | LOG.info("Response of getting updates: \n{}\n", response); 241 | } 242 | 243 | if (response.has("failed")) { 244 | 245 | int code = response.getInt("failed"); 246 | 247 | LOG.error("Response of VK LongPoll fallen with error code {}", code); 248 | 249 | switch (code) { 250 | 251 | default: { 252 | 253 | if (response.has("ts")) { 254 | ts = response.getInt("ts"); 255 | } 256 | 257 | setData(null, null, null, null, null); 258 | break; 259 | } 260 | 261 | case 4: { 262 | 263 | version = response.getInt("max_version"); 264 | setData(null, null, null, null, null); 265 | break; 266 | } 267 | } 268 | } else { 269 | 270 | if (response.has("ts")) 271 | ts = response.getInt("ts"); 272 | 273 | if (response.has("pts")) 274 | this.pts = response.getInt("pts"); 275 | 276 | if (this.updatesHandler.callbacksCount() > 0 || this.updatesHandler.commandsCount() > 0 || this.updatesHandler.chatCallbacksCount() > 0) { 277 | 278 | if (response.has("ts") && response.has("updates")) { 279 | 280 | this.updatesHandler.handle(response.getJSONArray("updates")); 281 | 282 | } else { 283 | LOG.error("Bad response from VK LongPoll server: no `ts` or `updates` array: {}", response); 284 | try { 285 | Thread.sleep(1000); 286 | } catch (InterruptedException ignored) { 287 | } 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | /** 295 | * If the client need to start typing 296 | * after receiving message 297 | * and until client's message is sent 298 | */ 299 | public void enableTyping(boolean enable) { 300 | this.updatesHandler.sendTyping = enable; 301 | } 302 | 303 | public void enableLoggingUpdates(boolean enable) { 304 | this.logUpdates = enable; 305 | } 306 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/clients/Group.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.clients; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.utils.Utils; 5 | import com.petersamokhin.bots.sdk.utils.vkapi.CallbackApiHandler; 6 | import com.petersamokhin.bots.sdk.utils.vkapi.CallbackApiSettings; 7 | import com.petersamokhin.bots.sdk.utils.web.MultipartUtility; 8 | import org.json.JSONObject; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.net.URL; 15 | 16 | /** 17 | * Group client, that contains important methods to work with groups 18 | */ 19 | public class Group extends Client { 20 | 21 | private CallbackApiHandler callbackApiHandler = null; 22 | 23 | private static final Logger LOG = LoggerFactory.getLogger(Group.class); 24 | 25 | /** 26 | * Default constructor 27 | * 28 | * @param id User or group id 29 | * @param access_token Access token key 30 | */ 31 | public Group(Integer id, String access_token) { 32 | super(id, access_token); 33 | } 34 | 35 | /** 36 | * Default constructor 37 | * 38 | * @param access_token Access token key 39 | */ 40 | public Group(String access_token) { 41 | super(access_token); 42 | } 43 | 44 | /** 45 | * Upload group cover by file from url or from disk 46 | */ 47 | public void uploadCover(String cover, Callback callback) { 48 | 49 | if (this.getId() == null || this.getId() == 0) { 50 | LOG.error("Please, provide group_id when initialising the client, because it's impossible to upload cover to group not knowing it id."); 51 | return; 52 | } 53 | 54 | byte[] bytes; 55 | 56 | File coverFile = new File(cover); 57 | if (coverFile.exists()) { 58 | try { 59 | bytes = Utils.toByteArray(coverFile.toURI().toURL()); 60 | } catch (IOException ignored) { 61 | LOG.error("Cover file was exists, but IOException occured: {}", ignored.toString()); 62 | return; 63 | } 64 | } else { 65 | URL coverUrl; 66 | try { 67 | coverUrl = new URL(cover); 68 | bytes = Utils.toByteArray(coverUrl); 69 | } catch (IOException ignored) { 70 | LOG.error("Bad string was proviced to uploadCocver method: path to file or url was expected, but got this: {}, error: {}", cover, ignored.toString()); 71 | return; 72 | } 73 | } 74 | 75 | updateCoverByFile(bytes, callback); 76 | } 77 | 78 | /** 79 | * Updating cover by bytes (of file or url) 80 | * 81 | * @param bytes bytes[] 82 | * @param callback response will return to callback 83 | */ 84 | private void updateCoverByFile(byte[] bytes, Callback... callback) { 85 | 86 | JSONObject params_getUploadServer = new JSONObject() 87 | .put("group_id", getId()) 88 | .put("crop_x", 0) 89 | .put("crop_y", 0) 90 | .put("crop_x2", 1590) 91 | .put("crop_y2", 400); 92 | 93 | api().call("photos.getOwnerCoverPhotoUploadServer", params_getUploadServer, response -> { 94 | 95 | String uploadUrl = new JSONObject(response.toString()).getString("upload_url"); 96 | 97 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 98 | multipartUtility.addBytesPart("photo", "photo.png", bytes); 99 | String coverUploadedResponseString = multipartUtility.finish(); 100 | 101 | coverUploadedResponseString = (coverUploadedResponseString != null && coverUploadedResponseString.length() > 2) ? coverUploadedResponseString : "{}"; 102 | 103 | JSONObject coverUploadedResponse = new JSONObject(coverUploadedResponseString); 104 | 105 | if (coverUploadedResponse.has("hash") && coverUploadedResponse.has("photo")) { 106 | 107 | String hash_field = coverUploadedResponse.getString("hash"); 108 | String photo_field = coverUploadedResponse.getString("photo"); 109 | 110 | JSONObject params_saveCover = new JSONObject() 111 | .put("hash", hash_field) 112 | .put("photo", photo_field); 113 | 114 | boolean sync = true; // vk, please fix `execute` method! 115 | if (sync) { 116 | JSONObject responseS = new JSONObject(api().callSync("photos.saveOwnerCoverPhoto", params_saveCover)); 117 | System.out.println("params is " + params_saveCover); 118 | if (responseS.toString().length() < 10 || responseS.toString().contains("error")) { 119 | LOG.error("Some error occured, cover not uploaded: {}", responseS); 120 | } 121 | if (callback.length > 0) 122 | callback[0].onResult(responseS); 123 | } else { 124 | api().call("photos.saveOwnerCoverPhoto", params_saveCover, response1 -> { 125 | 126 | if (response1.toString().length() < 10 || response1.toString().contains("error")) { 127 | LOG.error("Some error occured, cover not uploaded: {}", response1); 128 | } 129 | if (callback.length > 0) { 130 | callback[0].onResult(response1); 131 | } 132 | }); 133 | } 134 | } else { 135 | LOG.error("Error occured when uploading cover: no 'photo' or 'hash' param in response {}", coverUploadedResponse); 136 | if (callback.length > 0) 137 | callback[0].onResult("false"); 138 | } 139 | }); 140 | } 141 | 142 | /** 143 | * Work with vk callback api 144 | * 145 | * @param path '/path' to listen 146 | */ 147 | public Group callbackApi(String path) { 148 | if (callbackApiHandler == null) { 149 | callbackApiHandler = new CallbackApiHandler(path); 150 | callbackApiHandler.setGroup(this); 151 | } 152 | return this; 153 | } 154 | 155 | /** 156 | * Work with vk callback api 157 | * 158 | * @param settings (host, path, port etc) 159 | */ 160 | public Group callbackApi(CallbackApiSettings settings) { 161 | if (callbackApiHandler == null) { 162 | callbackApiHandler = new CallbackApiHandler(settings); 163 | callbackApiHandler.setGroup(this); 164 | } 165 | return this; 166 | } 167 | 168 | /** 169 | * If need to set own port, host, etc 170 | * 171 | * @param settings Settings: host, port, path etc 172 | */ 173 | public void setCallbackApiSettings(CallbackApiSettings settings) { 174 | callbackApiHandler = new CallbackApiHandler(settings); 175 | callbackApiHandler.setGroup(this); 176 | } 177 | 178 | /** 179 | * Default: will be listening for events from VK on port 80 180 | */ 181 | public void setCallbackApiSettings(String path) { 182 | callbackApiHandler = new CallbackApiHandler(path); 183 | callbackApiHandler.setGroup(this); 184 | } 185 | 186 | /* Callback API */ 187 | 188 | public void onAudioNew(Callback callback) { 189 | callbackApiHandler.registerCallback("audio_new", callback); 190 | } 191 | 192 | public void onBoardPostDelete(Callback callback) { 193 | callbackApiHandler.registerCallback("board_post_delete", callback); 194 | } 195 | 196 | public void onBoardPostEdit(Callback callback) { 197 | callbackApiHandler.registerCallback("board_post_edit", callback); 198 | } 199 | 200 | public void onBoardPostNew(Callback callback) { 201 | callbackApiHandler.registerCallback("board_post_new", callback); 202 | } 203 | 204 | public void onBoardPostRestore(Callback callback) { 205 | callbackApiHandler.registerCallback("board_post_restore", callback); 206 | } 207 | 208 | public void onGroupChangePhoto(Callback callback) { 209 | callbackApiHandler.registerCallback("group_change_photo", callback); 210 | } 211 | 212 | public void onGroupChangeSettings(Callback callback) { 213 | callbackApiHandler.registerCallback("group_change_settings", callback); 214 | } 215 | 216 | public void onGroupJoin(Callback callback) { 217 | callbackApiHandler.registerCallback("group_join", callback); 218 | } 219 | 220 | public void onGroupLeave(Callback callback) { 221 | callbackApiHandler.registerCallback("group_leave", callback); 222 | } 223 | 224 | public void onGroupOfficersEdit(Callback callback) { 225 | callbackApiHandler.registerCallback("group_officers_edit", callback); 226 | } 227 | 228 | public void onPollVoteNew(Callback callback) { 229 | callbackApiHandler.registerCallback("poll_vote_new", callback); 230 | } 231 | 232 | public void onMarketCommentDelete(Callback callback) { 233 | callbackApiHandler.registerCallback("market_comment_delete", callback); 234 | } 235 | 236 | public void onMarketCommentEdit(Callback callback) { 237 | callbackApiHandler.registerCallback("market_comment_edit", callback); 238 | } 239 | 240 | public void onMarketCommentNew(Callback callback) { 241 | callbackApiHandler.registerCallback("market_comment_new", callback); 242 | } 243 | 244 | public void onMarketCommentRestore(Callback callback) { 245 | callbackApiHandler.registerCallback("market_comment_restore", callback); 246 | } 247 | 248 | public void onMessageAllow(Callback callback) { 249 | callbackApiHandler.registerCallback("message_allow", callback); 250 | } 251 | 252 | public void onMessageDeny(Callback callback) { 253 | callbackApiHandler.registerCallback("message_deny", callback); 254 | } 255 | 256 | public void onMessageNew(Callback callback) { 257 | callbackApiHandler.registerCallback("message_new", callback); 258 | } 259 | 260 | public void onMessageReply(Callback callback) { 261 | callbackApiHandler.registerCallback("message_reply", callback); 262 | } 263 | 264 | public void onPhotoCommentEdit(Callback callback) { 265 | callbackApiHandler.registerCallback("photo_comment_edit", callback); 266 | } 267 | 268 | public void onPhotoCommentNew(Callback callback) { 269 | callbackApiHandler.registerCallback("photo_comment_new", callback); 270 | } 271 | 272 | public void onPhotoCommentRestore(Callback callback) { 273 | callbackApiHandler.registerCallback("photo_comment_restore", callback); 274 | } 275 | 276 | public void onPhotoNew(Callback callback) { 277 | callbackApiHandler.registerCallback("photo_new", callback); 278 | } 279 | 280 | public void onPhotoCommentDelete(Callback callback) { 281 | callbackApiHandler.registerCallback("photo_comment_delete", callback); 282 | } 283 | 284 | public void onVideoCommentEdit(Callback callback) { 285 | callbackApiHandler.registerCallback("video_comment_edit", callback); 286 | } 287 | 288 | public void onVideoCommentNew(Callback callback) { 289 | callbackApiHandler.registerCallback("video_comment_new", callback); 290 | } 291 | 292 | public void onVideoCommentRestore(Callback callback) { 293 | callbackApiHandler.registerCallback("video_comment_restore", callback); 294 | } 295 | 296 | public void onVideoNew(Callback callback) { 297 | callbackApiHandler.registerCallback("video_new", callback); 298 | } 299 | 300 | public void onVideoCommentDelete(Callback callback) { 301 | callbackApiHandler.registerCallback("video_comment_delete", callback); 302 | } 303 | 304 | public void onWallPostNew(Callback callback) { 305 | callbackApiHandler.registerCallback("wall_post_new", callback); 306 | } 307 | 308 | public void onWallReplyDelete(Callback callback) { 309 | callbackApiHandler.registerCallback("wall_reply_delete", callback); 310 | } 311 | 312 | public void onWallReplyEdit(Callback callback) { 313 | callbackApiHandler.registerCallback("wall_reply_edit", callback); 314 | } 315 | 316 | public void onWallReplyNew(Callback callback) { 317 | callbackApiHandler.registerCallback("wall_reply_new", callback); 318 | } 319 | 320 | public void onWallReplyRestore(Callback callback) { 321 | callbackApiHandler.registerCallback("wall_reply_restore", callback); 322 | } 323 | 324 | public void onWallRepost(Callback callback) { 325 | callbackApiHandler.registerCallback("wall_repost", callback); 326 | } 327 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/utils/vkapi/CallbackApiHandler.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.utils.vkapi; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.clients.Group; 5 | import org.json.JSONObject; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import spark.Spark; 9 | 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.OutputStream; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | /** 15 | * Interacting with VK Callback API 16 | */ 17 | public class CallbackApiHandler { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(CallbackApiHandler.class); 20 | 21 | private final String ok = "ok"; 22 | 23 | private Map callbacks = new HashMap<>(); 24 | 25 | public static volatile boolean autoSetEvents = true; 26 | private Group group; 27 | 28 | private volatile boolean serverIsStarted = false; 29 | 30 | /** 31 | * Simple constructor 32 | * 33 | * @param path path to listening: /callback 34 | */ 35 | public CallbackApiHandler(String path) { 36 | 37 | if (serverIsStarted) { 38 | Spark.stop(); 39 | serverIsStarted = false; 40 | } else { 41 | serverIsStarted = true; 42 | } 43 | 44 | // We need to listen port 80 to get events from VK 45 | // But if you have another server, you can set any port 46 | // And pre-roam 47 | int port = 80; 48 | Spark.port(port); 49 | LOG.info("Started listening to VK Callback API on port {}.", port); 50 | 51 | // Handle 52 | Spark.post(path, (request, response) -> { 53 | 54 | JSONObject req = new JSONObject(request.body()); 55 | 56 | String type = req.has("type") ? req.getString("type") : ""; 57 | 58 | if (type.equals("confirmation")) { 59 | LOG.info("New confirmation request: {}", req); 60 | return new JSONObject(group.api().callSync("groups.getCallbackConfirmationCode", "group_id", group.getId())).getJSONObject("response").getString("code"); 61 | 62 | } else { 63 | handle(req); 64 | return ok; 65 | } 66 | }); 67 | } 68 | 69 | /** 70 | * Non-default handler 71 | */ 72 | public CallbackApiHandler(CallbackApiSettings settings) { 73 | 74 | if (serverIsStarted) { 75 | Spark.stop(); 76 | serverIsStarted = false; 77 | } else { 78 | serverIsStarted = true; 79 | } 80 | 81 | // We need to listen port 80 to get events from VK 82 | // But if you have another server, you can set any port 83 | // And pre-roam 84 | Spark.port(settings.getPort()); 85 | LOG.info("Started listening to VK Callback API on port 80."); 86 | 87 | // Set server if it's not setted 88 | if (settings.getHost() == null || settings.getHost().length() < 2) { 89 | LOG.error("Server is not set: trying to set..."); 90 | boolean server_ok = false; 91 | while (!server_ok) { 92 | JSONObject response = new JSONObject(group.api().callSync("groups.setCallbackServer", "group_id", group.getId(), "server_url", settings.getHost() + settings.getPath())).getJSONObject("response"); 93 | LOG.error("New attempt to set server. Response: {}", response); 94 | if (response.getString("state").equals("ok")) { 95 | server_ok = true; 96 | LOG.info("Server is installed."); 97 | } 98 | } 99 | } 100 | 101 | // Handle 102 | Spark.post(settings.getPath(), (request, response) -> { 103 | 104 | JSONObject req = new JSONObject(request.body()); 105 | 106 | String type = req.has("type") ? req.getString("type") : ""; 107 | 108 | if (type.equals("confirmation")) { 109 | 110 | LOG.info("New confirmation request: {}", req); 111 | return settings.confirmationCode; 112 | 113 | } else { 114 | 115 | // Autoanswer needed if you want to answer "ok" immediatly 116 | // Because if error or something else will occure 117 | // VK will repeat requests until you will answer "ok" 118 | if (settings.isAutoAnswer()) { 119 | HttpServletResponse resp = response.raw(); 120 | OutputStream os = resp.getOutputStream(); 121 | os.write(ok.getBytes("UTF-8")); 122 | resp.setStatus(HttpServletResponse.SC_OK); 123 | resp.setContentLength(2); 124 | os.close(); 125 | } 126 | 127 | handle(req); 128 | 129 | return ok; 130 | } 131 | }); 132 | } 133 | 134 | /** 135 | * Set callback 136 | * 137 | * @param callback Callback 138 | */ 139 | public void registerCallback(String name, Callback callback) { 140 | 141 | if (autoSetEvents) { 142 | group.api().callSync("groups.setCallbackSettings", "group_id", group.getId(), name, 1); 143 | } 144 | 145 | this.callbacks.put(name, callback); 146 | } 147 | 148 | /** 149 | * Call the necessary methods in callback 150 | * 151 | * @param request Incoming request 152 | */ 153 | private void handle(JSONObject request) { 154 | 155 | if (request.has("type") && request.has("object")) { 156 | String type = request.getString("type"); 157 | JSONObject object = request.getJSONObject("object"); 158 | 159 | switch (type) { 160 | 161 | case "message_new": { 162 | if (callbacks.containsKey("message_new")) { 163 | callbacks.get("message_new").onResult(object); 164 | } 165 | break; 166 | } 167 | case "message_reply": { 168 | if (callbacks.containsKey("message_reply")) { 169 | callbacks.get("message_reply").onResult(object); 170 | } 171 | break; 172 | } 173 | case "message_allow": { 174 | if (callbacks.containsKey("message_allow")) { 175 | callbacks.get("message_allow").onResult(object); 176 | } 177 | break; 178 | } 179 | case "message_deny": { 180 | if (callbacks.containsKey("message_deny")) { 181 | callbacks.get("message_deny").onResult(object); 182 | } 183 | break; 184 | } 185 | case "photo_new": { 186 | if (callbacks.containsKey("photo_new")) { 187 | callbacks.get("photo_new").onResult(object); 188 | } 189 | break; 190 | } 191 | case "photo_comment_new": { 192 | if (callbacks.containsKey("photo_comment_new")) { 193 | callbacks.get("photo_comment_new").onResult(object); 194 | } 195 | break; 196 | } 197 | case "photo_comment_edit": { 198 | if (callbacks.containsKey("photo_comment_edit")) { 199 | callbacks.get("photo_comment_edit").onResult(object); 200 | } 201 | break; 202 | } 203 | case "photo_comment_restore": { 204 | if (callbacks.containsKey("photo_comment_restore")) { 205 | callbacks.get("photo_comment_restore").onResult(object); 206 | } 207 | break; 208 | } 209 | case "photo_comment_delete": { 210 | if (callbacks.containsKey("photo_comment_delete")) { 211 | callbacks.get("photo_comment_delete").onResult(object); 212 | } 213 | break; 214 | } 215 | case "audio_new": { 216 | if (callbacks.containsKey("audio_new")) { 217 | callbacks.get("audio_new").onResult(object); 218 | } 219 | break; 220 | } 221 | case "video_new": { 222 | if (callbacks.containsKey("video_new")) { 223 | callbacks.get("video_new").onResult(object); 224 | } 225 | break; 226 | } 227 | case "video_comment_new": { 228 | if (callbacks.containsKey("video_comment_new")) { 229 | callbacks.get("video_comment_new").onResult(object); 230 | } 231 | break; 232 | } 233 | case "video_comment_edit": { 234 | if (callbacks.containsKey("video_comment_edit")) { 235 | callbacks.get("video_comment_edit").onResult(object); 236 | } 237 | break; 238 | } 239 | case "video_comment_restore": { 240 | if (callbacks.containsKey("video_comment_restore")) { 241 | callbacks.get("video_comment_restore").onResult(object); 242 | } 243 | break; 244 | } 245 | case "video_comment_delete": { 246 | if (callbacks.containsKey("video_comment_delete")) { 247 | callbacks.get("video_comment_delete").onResult(object); 248 | } 249 | break; 250 | } 251 | case "wall_post_new": { 252 | if (callbacks.containsKey("wall_post_new")) { 253 | callbacks.get("wall_post_new").onResult(object); 254 | } 255 | break; 256 | } 257 | case "wall_repost": { 258 | if (callbacks.containsKey("wall_repost")) { 259 | callbacks.get("wall_repost").onResult(object); 260 | } 261 | break; 262 | } 263 | case "wall_reply_new": { 264 | if (callbacks.containsKey("wall_reply_new")) { 265 | callbacks.get("wall_reply_new").onResult(object); 266 | } 267 | break; 268 | } 269 | case "wall_reply_edit": { 270 | if (callbacks.containsKey("wall_reply_edit")) { 271 | callbacks.get("wall_reply_edit").onResult(object); 272 | } 273 | break; 274 | } 275 | case "wall_reply_restore": { 276 | if (callbacks.containsKey("wall_reply_restore")) { 277 | callbacks.get("wall_reply_restore").onResult(object); 278 | } 279 | break; 280 | } 281 | case "wall_reply_delete": { 282 | if (callbacks.containsKey("wall_reply_delete")) { 283 | callbacks.get("wall_reply_delete").onResult(object); 284 | } 285 | break; 286 | } 287 | case "board_post_new": { 288 | if (callbacks.containsKey("board_post_new")) { 289 | callbacks.get("board_post_new").onResult(object); 290 | } 291 | break; 292 | } 293 | case "board_post_edit": { 294 | if (callbacks.containsKey("board_post_edit")) { 295 | callbacks.get("board_post_edit").onResult(object); 296 | } 297 | break; 298 | } 299 | case "board_post_restore": { 300 | if (callbacks.containsKey("board_post_restore")) { 301 | callbacks.get("board_post_restore").onResult(object); 302 | } 303 | break; 304 | } 305 | case "board_post_delete": { 306 | if (callbacks.containsKey("board_post_delete")) { 307 | callbacks.get("board_post_delete").onResult(object); 308 | } 309 | break; 310 | } 311 | case "market_comment_new": { 312 | if (callbacks.containsKey("market_comment_new")) { 313 | callbacks.get("market_comment_new").onResult(object); 314 | } 315 | break; 316 | } 317 | case "market_comment_edit": { 318 | if (callbacks.containsKey("market_comment_edit")) { 319 | callbacks.get("market_comment_edit").onResult(object); 320 | } 321 | break; 322 | } 323 | case "market_comment_restore": { 324 | if (callbacks.containsKey("market_comment_restore")) { 325 | callbacks.get("market_comment_restore").onResult(object); 326 | } 327 | break; 328 | } 329 | case "market_comment_delete": { 330 | if (callbacks.containsKey("market_comment_delete")) { 331 | callbacks.get("market_comment_delete").onResult(object); 332 | } 333 | break; 334 | } 335 | case "group_leave": { 336 | if (callbacks.containsKey("group_leave")) { 337 | callbacks.get("group_leave").onResult(object); 338 | } 339 | break; 340 | } 341 | case "group_join": { 342 | if (callbacks.containsKey("group_join")) { 343 | callbacks.get("group_join").onResult(object); 344 | } 345 | break; 346 | } 347 | case "poll_vote_new": { 348 | if (callbacks.containsKey("poll_vote_new")) { 349 | callbacks.get("poll_vote_new").onResult(object); 350 | } 351 | break; 352 | } 353 | case "group_officers_edit": { 354 | if (callbacks.containsKey("group_officers_edit")) { 355 | callbacks.get("group_officers_edit").onResult(object); 356 | } 357 | break; 358 | } 359 | case "group_change_settings": { 360 | if (callbacks.containsKey("group_change_settings")) { 361 | callbacks.get("group_change_settings").onResult(object); 362 | } 363 | break; 364 | } 365 | case "group_change_photo": { 366 | if (callbacks.containsKey("group_change_photo")) { 367 | callbacks.get("group_change_photo").onResult(object); 368 | } 369 | break; 370 | } 371 | } 372 | } 373 | } 374 | 375 | public void setGroup(Group group) { 376 | this.group = group; 377 | } 378 | } -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/longpoll/UpdatesHandler.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.longpoll; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.*; 4 | import com.petersamokhin.bots.sdk.clients.Client; 5 | import com.petersamokhin.bots.sdk.objects.Chat; 6 | import com.petersamokhin.bots.sdk.objects.Message; 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import static com.petersamokhin.bots.sdk.clients.Client.scheduler; 14 | import static com.petersamokhin.bots.sdk.clients.Client.service; 15 | 16 | /** 17 | * Class for handling all updates in other thread 18 | */ 19 | public class UpdatesHandler extends Thread { 20 | 21 | private volatile Queue queue = new Queue(); 22 | 23 | volatile boolean sendTyping = false; 24 | 25 | /** 26 | * Maps with callbacks 27 | */ 28 | private ConcurrentHashMap callbacks = new ConcurrentHashMap<>(); 29 | private ConcurrentHashMap chatCallbacks = new ConcurrentHashMap<>(); 30 | 31 | /** 32 | * Client with access_token 33 | */ 34 | private Client client; 35 | 36 | UpdatesHandler(Client client) { 37 | this.client = client; 38 | } 39 | 40 | @Override 41 | public void run() { 42 | scheduler.scheduleWithFixedDelay(this::handleCurrentUpdate, 0, 1, TimeUnit.MILLISECONDS); 43 | } 44 | 45 | /** 46 | * Handle the array of updates 47 | */ 48 | void handle(JSONArray updates) { 49 | this.queue.putAll(updates); 50 | } 51 | 52 | /** 53 | * Handle one event from longpoll server 54 | */ 55 | private void handleCurrentUpdate() { 56 | 57 | JSONArray currentUpdate; 58 | 59 | if (this.queue.updates.isEmpty()) { 60 | return; 61 | } else { 62 | currentUpdate = this.queue.shift(); 63 | } 64 | 65 | int updateType = currentUpdate.getInt(0); 66 | 67 | switch (updateType) { 68 | 69 | // Handling new message 70 | case 4: { 71 | 72 | int messageFlags = currentUpdate.getInt(2); 73 | 74 | // check if message is received 75 | if ((messageFlags & 2) == 0) { 76 | service.submit(() -> handleMessageUpdate(currentUpdate)); 77 | } 78 | 79 | // handle every 80 | handleEveryLongPollUpdate(currentUpdate); 81 | break; 82 | } 83 | 84 | // Handling update (user started typing) 85 | case 61: { 86 | handleTypingUpdate(currentUpdate); 87 | 88 | // handle every 89 | handleEveryLongPollUpdate(currentUpdate); 90 | break; 91 | } 92 | 93 | // Handling other 94 | default: { 95 | handleEveryLongPollUpdate(currentUpdate); 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Handle chat events 102 | */ 103 | @SuppressWarnings("unchecked") 104 | private void handleChatEvents(JSONArray updateObject) { 105 | 106 | Integer chatId = updateObject.getInt(3); 107 | 108 | JSONObject attachments = (updateObject.length() > 6 ? (updateObject.get(6).toString().startsWith("{") ? new JSONObject(updateObject.get(6).toString()) : null) : null); 109 | 110 | // Return if no attachments 111 | // Because there no events, 112 | // and because simple chat messages will be handled 113 | if (attachments == null) return; 114 | 115 | if (attachments.has("source_act")) { 116 | String sourceAct = attachments.getString("source_act"); 117 | 118 | Integer from = Integer.parseInt(attachments.getString("from")); 119 | 120 | switch (sourceAct) { 121 | case "chat_create": { 122 | 123 | String title = attachments.getString("source_text"); 124 | 125 | if (chatCallbacks.containsKey("onChatCreatedCallback")) { 126 | ((CallbackTriple) chatCallbacks.get("onChatCreatedCallback")).onEvent(title, from, chatId); 127 | } 128 | break; 129 | } 130 | case "chat_title_update": { 131 | 132 | String oldTitle = attachments.getString("source_old_text"); 133 | String newTitle = attachments.getString("source_text"); 134 | 135 | if (chatCallbacks.containsKey("OnChatTitleChangedCallback")) { 136 | ((CallbackFourth) chatCallbacks.get("OnChatTitleChangedCallback")).onEvent(oldTitle, newTitle, from, chatId); 137 | } 138 | break; 139 | } 140 | case "chat_photo_update": { 141 | 142 | JSONObject photo = new JSONObject(client.api().callSync("messages.getById", "message_ids", updateObject.getInt(1))).getJSONObject("response").getJSONArray("items").getJSONObject(0).getJSONArray("attachments").getJSONObject(0).getJSONObject("photo"); 143 | 144 | if (chatCallbacks.containsKey("onChatPhotoChangedCallback")) { 145 | ((CallbackTriple) chatCallbacks.get("onChatPhotoChangedCallback")).onEvent(photo, from, chatId); 146 | } 147 | 148 | break; 149 | } 150 | case "chat_invite_user": { 151 | 152 | Integer user = Integer.valueOf(attachments.getString("source_mid")); 153 | 154 | if (chatCallbacks.containsKey("OnChatJoinCallback")) { 155 | ((CallbackTriple) chatCallbacks.get("OnChatJoinCallback")).onEvent(from, user, chatId); 156 | } 157 | break; 158 | } 159 | case "chat_kick_user": { 160 | 161 | Integer user = Integer.valueOf(attachments.getString("source_mid")); 162 | 163 | if (chatCallbacks.containsKey("OnChatLeaveCallback")) { 164 | ((CallbackTriple) chatCallbacks.get("OnChatLeaveCallback")).onEvent(from, user, chatId); 165 | } 166 | break; 167 | } 168 | case "chat_photo_remove": { 169 | 170 | if (chatCallbacks.containsKey("onChatPhotoRemovedCallback")) { 171 | ((CallbackDouble) chatCallbacks.get("onChatPhotoRemovedCallback")).onEvent(from, chatId); 172 | } 173 | break; 174 | } 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * Handle every longpoll event 181 | */ 182 | @SuppressWarnings("unchecked") 183 | private void handleEveryLongPollUpdate(JSONArray updateObject) { 184 | if (callbacks.containsKey("OnEveryLongPollEventCallback")) { 185 | callbacks.get("OnEveryLongPollEventCallback").onResult(updateObject); 186 | } 187 | } 188 | 189 | /** 190 | * Handle new message 191 | */ 192 | @SuppressWarnings("unchecked") 193 | private void handleMessageUpdate(JSONArray updateObject) { 194 | 195 | // Flag 196 | boolean messageIsAlreadyHandled = false; 197 | 198 | // All necessary data 199 | Integer messageId = updateObject.getInt(1), 200 | messageFlags = updateObject.getInt(2), 201 | peerId = updateObject.getInt(3), 202 | chatId = 0, 203 | timestamp = updateObject.getInt(4); 204 | 205 | String messageText = updateObject.getString(5); 206 | 207 | JSONObject attachments = (updateObject.length() > 6 ? (updateObject.get(6).toString().startsWith("{") ? new JSONObject(updateObject.get(6).toString()) : null) : null); 208 | 209 | Integer randomId = updateObject.length() > 7 ? updateObject.getInt(7) : null; 210 | 211 | // Check for chat 212 | if (peerId > Chat.CHAT_PREFIX) { 213 | chatId = peerId - Chat.CHAT_PREFIX; 214 | if (attachments != null) { 215 | peerId = Integer.parseInt(attachments.getString("from")); 216 | } 217 | } 218 | 219 | Message message = new Message( 220 | this.client, 221 | messageId, 222 | messageFlags, 223 | peerId, 224 | timestamp, 225 | messageText, 226 | attachments, 227 | randomId 228 | ); 229 | 230 | if (chatId > 0) { 231 | message.setChatId(chatId); 232 | message.setChatIdLong(Chat.CHAT_PREFIX + chatId); 233 | 234 | // chat events 235 | handleChatEvents(updateObject); 236 | } 237 | 238 | // check for commands 239 | if (this.client.commands.size() > 0) { 240 | messageIsAlreadyHandled = handleCommands(message); 241 | } 242 | 243 | if (message.hasFwds()) { 244 | if (callbacks.containsKey("OnMessageWithFwdsCallback")) { 245 | callbacks.get("OnMessageWithFwdsCallback").onResult(message); 246 | messageIsAlreadyHandled = true; 247 | 248 | handleSendTyping(message); 249 | } 250 | } 251 | 252 | if (!messageIsAlreadyHandled) { 253 | switch (message.messageType()) { 254 | 255 | case "voiceMessage": { 256 | if (callbacks.containsKey("OnVoiceMessageCallback")) { 257 | callbacks.get("OnVoiceMessageCallback").onResult(message); 258 | messageIsAlreadyHandled = true; 259 | 260 | handleSendTyping(message); 261 | } 262 | break; 263 | } 264 | 265 | case "stickerMessage": { 266 | if (callbacks.containsKey("OnStickerMessageCallback")) { 267 | callbacks.get("OnStickerMessageCallback").onResult(message); 268 | messageIsAlreadyHandled = true; 269 | 270 | handleSendTyping(message); 271 | } 272 | break; 273 | } 274 | 275 | case "gifMessage": { 276 | if (callbacks.containsKey("OnGifMessageCallback")) { 277 | callbacks.get("OnGifMessageCallback").onResult(message); 278 | messageIsAlreadyHandled = true; 279 | 280 | handleSendTyping(message); 281 | } 282 | break; 283 | } 284 | 285 | case "audioMessage": { 286 | if (callbacks.containsKey("OnAudioMessageCallback")) { 287 | callbacks.get("OnAudioMessageCallback").onResult(message); 288 | messageIsAlreadyHandled = true; 289 | 290 | handleSendTyping(message); 291 | } 292 | break; 293 | } 294 | 295 | case "videoMessage": { 296 | if (callbacks.containsKey("OnVideoMessageCallback")) { 297 | callbacks.get("OnVideoMessageCallback").onResult(message); 298 | messageIsAlreadyHandled = true; 299 | 300 | handleSendTyping(message); 301 | } 302 | break; 303 | } 304 | 305 | case "docMessage": { 306 | if (callbacks.containsKey("OnDocMessageCallback")) { 307 | callbacks.get("OnDocMessageCallback").onResult(message); 308 | messageIsAlreadyHandled = true; 309 | 310 | handleSendTyping(message); 311 | } 312 | break; 313 | } 314 | 315 | case "wallMessage": { 316 | if (callbacks.containsKey("OnWallMessageCallback")) { 317 | callbacks.get("OnWallMessageCallback").onResult(message); 318 | messageIsAlreadyHandled = true; 319 | 320 | handleSendTyping(message); 321 | } 322 | break; 323 | } 324 | 325 | case "photoMessage": { 326 | if (callbacks.containsKey("OnPhotoMessageCallback")) { 327 | callbacks.get("OnPhotoMessageCallback").onResult(message); 328 | messageIsAlreadyHandled = true; 329 | 330 | handleSendTyping(message); 331 | } 332 | break; 333 | } 334 | 335 | case "linkMessage": { 336 | if (callbacks.containsKey("OnLinkMessageCallback")) { 337 | callbacks.get("OnLinkMessageCallback").onResult(message); 338 | messageIsAlreadyHandled = true; 339 | 340 | handleSendTyping(message); 341 | } 342 | break; 343 | } 344 | 345 | case "simpleTextMessage": { 346 | if (callbacks.containsKey("OnSimpleTextMessageCallback")) { 347 | callbacks.get("OnSimpleTextMessageCallback").onResult(message); 348 | messageIsAlreadyHandled = true; 349 | 350 | handleSendTyping(message); 351 | } 352 | break; 353 | } 354 | } 355 | } 356 | 357 | if (callbacks.containsKey("OnMessageCallback") && !messageIsAlreadyHandled) { 358 | callbacks.get("OnMessageCallback").onResult(message); 359 | 360 | handleSendTyping(message); 361 | } 362 | 363 | if (callbacks.containsKey("OnChatMessageCallback") && !messageIsAlreadyHandled) { 364 | callbacks.get("OnChatMessageCallback").onResult(message); 365 | } 366 | 367 | if (callbacks.containsKey("OnEveryMessageCallback")) { 368 | callbacks.get("OnEveryMessageCallback").onResult(message); 369 | 370 | handleSendTyping(message); 371 | } 372 | } 373 | 374 | /** 375 | * Handle dialog with typing user 376 | */ 377 | @SuppressWarnings("unchecked") 378 | private void handleTypingUpdate(JSONArray updateObject) { 379 | 380 | if (callbacks.containsKey("OnTypingCallback")) { 381 | callbacks.get("OnTypingCallback").onResult(updateObject.getInt(1)); 382 | } 383 | } 384 | 385 | /** 386 | * Add callback to the map 387 | * 388 | * @param name Callback name 389 | * @param callback Callback 390 | */ 391 | void registerCallback(String name, Callback callback) { 392 | this.callbacks.put(name, callback); 393 | } 394 | 395 | /** 396 | * Add callback to the map 397 | * 398 | * @param name Callback name 399 | * @param callback Callback 400 | */ 401 | void registerChatCallback(String name, AbstractCallback callback) { 402 | this.chatCallbacks.put(name, callback); 403 | } 404 | 405 | /** 406 | * Returns count of callbacks 407 | */ 408 | int callbacksCount() { 409 | return this.callbacks.size(); 410 | } 411 | 412 | /** 413 | * Returns count of callbacks 414 | */ 415 | int chatCallbacksCount() { 416 | return this.chatCallbacks.size(); 417 | } 418 | 419 | /** 420 | * Returns count of commands 421 | */ 422 | int commandsCount() { 423 | return this.client.commands.size(); 424 | } 425 | 426 | /** 427 | * Handle message and call back if it contains any command 428 | * 429 | * @param message received message 430 | */ 431 | private boolean handleCommands(Message message) { 432 | 433 | boolean is = false; 434 | 435 | for (Client.Command command : this.client.commands) { 436 | for (int i = 0; i < command.getCommands().length; i++) { 437 | if (message.getText().toLowerCase().contains(command.getCommands()[i].toString().toLowerCase())) { 438 | command.getCallback().onResult(message); 439 | is = true; 440 | 441 | handleSendTyping(message); 442 | } 443 | } 444 | } 445 | 446 | return is; 447 | } 448 | 449 | /** 450 | * Send typing 451 | */ 452 | private void handleSendTyping(Message message) { 453 | 454 | // Send typing 455 | if (sendTyping) { 456 | if (!message.isMessageFromChat()) { 457 | this.client.api().call("messages.setActivity", "{type:'typing',peer_id:" + message.authorId() + "}", response -> { 458 | }); 459 | } else { 460 | this.client.api().call("messages.setActivity", "{type:'typing',peer_id:" + message.getChatIdLong() + "}", response -> { 461 | }); 462 | } 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/com/petersamokhin/bots/sdk/objects/Message.java: -------------------------------------------------------------------------------- 1 | package com.petersamokhin.bots.sdk.objects; 2 | 3 | import com.petersamokhin.bots.sdk.callbacks.Callback; 4 | import com.petersamokhin.bots.sdk.clients.Client; 5 | import com.petersamokhin.bots.sdk.utils.Utils; 6 | import com.petersamokhin.bots.sdk.utils.vkapi.API; 7 | import com.petersamokhin.bots.sdk.utils.vkapi.docs.DocTypes; 8 | import com.petersamokhin.bots.sdk.utils.web.Connection; 9 | import com.petersamokhin.bots.sdk.utils.web.MultipartUtility; 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.net.URLConnection; 21 | import java.nio.file.Files; 22 | import java.nio.file.Paths; 23 | import java.util.Arrays; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.concurrent.CopyOnWriteArrayList; 27 | import java.util.regex.Pattern; 28 | 29 | /** 30 | * Message object for both (received and sent) messages 31 | */ 32 | public class Message { 33 | 34 | private static final Logger LOG = LoggerFactory.getLogger(Message.class); 35 | 36 | private Integer messageId, flags, peerId, timestamp, randomId, stickerId, chatId, chatIdLong; 37 | private String text, accessToken, title; 38 | private API api; 39 | 40 | /** 41 | * Attachments in format of received event from longpoll server 42 | * More: link 43 | */ 44 | private JSONObject attachmentsOfReceivedMessage; 45 | 46 | /** 47 | * Attahments in format [photo62802565_456241137, photo111_111, doc100_500] 48 | */ 49 | private CopyOnWriteArrayList attachments = new CopyOnWriteArrayList<>(), forwardedMessages = new CopyOnWriteArrayList<>(), photosToUpload = new CopyOnWriteArrayList<>(); 50 | private CopyOnWriteArrayList docsToUpload = new CopyOnWriteArrayList<>(); 51 | 52 | /** 53 | * Constructor for sent message 54 | */ 55 | public Message() { 56 | } 57 | 58 | /** 59 | * Constructor for received message 60 | */ 61 | public Message(Client client, Integer messageId, Integer flags, Integer peerId, Integer timestamp, String text, JSONObject attachments, Integer randomId) { 62 | 63 | setAccessToken(client.getAccessToken()); 64 | setMessageId(messageId); 65 | setFlags(flags); 66 | setPeerId(peerId); 67 | setTimestamp(timestamp); 68 | setText(text); 69 | setAttachments(attachments); 70 | setRandomId(randomId); 71 | setTitle(attachments.has("title") ? attachments.getString("title") : " ... "); 72 | 73 | api = client.api(); 74 | } 75 | 76 | /** 77 | * Your client with id, access token 78 | */ 79 | public Message from(Client client) { 80 | setAccessToken(client.getAccessToken()); 81 | api = client.api(); 82 | return this; 83 | } 84 | 85 | /** 86 | * ID of target dialog 87 | */ 88 | public Message to(Integer peerId) { 89 | this.peerId = peerId; 90 | return this; 91 | } 92 | 93 | /** 94 | * ID of sticker, only for user tokens 95 | */ 96 | public Message sticker(Integer id) { 97 | this.stickerId = id; 98 | return this; 99 | } 100 | 101 | /** 102 | * IDs of forwarded messages 103 | */ 104 | public Message forwardedMessages(Object... ids) { 105 | 106 | for (Object id : ids) { 107 | this.forwardedMessages.add(String.valueOf(id)); 108 | } 109 | return this; 110 | } 111 | 112 | /** 113 | * Message text 114 | */ 115 | public Message text(Object text) { 116 | this.text = String.valueOf(text); 117 | return this; 118 | } 119 | 120 | /** 121 | * Message title (bold text) 122 | */ 123 | public Message title(Object title) { 124 | this.title = String.valueOf(title); 125 | return this; 126 | } 127 | 128 | /** 129 | * Message attachments 130 | */ 131 | public Message attachments(String... attachments) { 132 | 133 | if (attachments.length > 10) 134 | LOG.error("Trying to send message with illegal count of attachments: {} (> 10)", attachments.length); 135 | else if (attachments.length == 1 && attachments[0].contains(",")) { 136 | this.attachments.addAllAbsent(Arrays.asList(attachments[0].split(","))); 137 | } else { 138 | this.attachments.addAllAbsent(Arrays.asList(attachments)); 139 | } 140 | return this; 141 | } 142 | 143 | /** 144 | * Message random_id 145 | */ 146 | public Message randomId(Integer randomId) { 147 | this.randomId = randomId; 148 | return this; 149 | } 150 | 151 | /** 152 | * Synchronous adding photo to the message 153 | * 154 | * @param photo String URL, link to vk doc or path to file 155 | */ 156 | public Message photo(String photo) { 157 | 158 | // Use already loaded photo 159 | if (Pattern.matches("[htps:/vk.com]?photo-?\\d+_\\d+", photo)) { 160 | this.attachments.add(photo.substring(photo.lastIndexOf("photo"))); 161 | return this; 162 | } 163 | 164 | String type = null; 165 | File photoFile = new File(photo); 166 | if (photoFile.exists()) { 167 | type = "fromFile"; 168 | } 169 | 170 | URL photoUrl = null; 171 | if (type == null) { 172 | try { 173 | photoUrl = new URL(photo); 174 | type = "fromUrl"; 175 | } catch (MalformedURLException ignored) { 176 | LOG.error("Error when trying add photo to message: file not found, or url is bad. Your param: {}", photo); 177 | return this; 178 | } 179 | } 180 | 181 | byte[] photoBytes; 182 | 183 | switch (type) { 184 | 185 | case "fromFile": { 186 | try { 187 | photoBytes = Files.readAllBytes(Paths.get(photoFile.toURI())); 188 | } catch (IOException ignored) { 189 | LOG.error("Error when reading file {}", photoFile.getAbsolutePath()); 190 | return this; 191 | } 192 | break; 193 | } 194 | 195 | case "fromUrl": { 196 | try { 197 | photoBytes = Utils.toByteArray(photoUrl); 198 | } catch (IOException ignored) { 199 | LOG.error("Error {} occured when reading URL {}", ignored.toString(), photo); 200 | return this; 201 | } 202 | break; 203 | } 204 | 205 | default: { 206 | LOG.error("Bad 'photo' string: path to file, URL or already uploaded 'photo()_()' was expected."); 207 | return this; 208 | } 209 | } 210 | 211 | if (photoBytes != null) { 212 | 213 | // Getting of server for uploading the photo 214 | String getUploadServerQuery = "https://api.vk.com/method/photos.getMessagesUploadServer?access_token=" + accessToken + "&peer_id=" + this.peerId + "&v=5.67"; 215 | JSONObject getUploadServerResponse = new JSONObject(Connection.getRequestResponse(getUploadServerQuery)); 216 | String uploadUrl = getUploadServerResponse.has("response") ? getUploadServerResponse.getJSONObject("response").has("upload_url") ? getUploadServerResponse.getJSONObject("response").getString("upload_url") : null : null; 217 | 218 | // Some error 219 | if (uploadUrl == null) { 220 | LOG.error("No upload url in response: {}", getUploadServerResponse); 221 | return this; 222 | } 223 | 224 | // Uploading the photo 225 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 226 | multipartUtility.addBytesPart("photo", "photo.png", photoBytes); 227 | String uploadingOfPhotoResponseString = multipartUtility.finish(); 228 | 229 | JSONObject uploadingOfPhotoResponse; 230 | 231 | try { 232 | uploadingOfPhotoResponse = new JSONObject(uploadingOfPhotoResponseString); 233 | } catch (JSONException ignored) { 234 | LOG.error("Bad response of uploading photo: {}, error: {}", uploadingOfPhotoResponseString, ignored.toString()); 235 | return this; 236 | } 237 | 238 | // Getting necessary params 239 | String server, photo_param, hash; 240 | if (uploadingOfPhotoResponse.has("server") & uploadingOfPhotoResponse.has("photo") && uploadingOfPhotoResponse.has("hash")) { 241 | server = "" + uploadingOfPhotoResponse.getInt("server"); 242 | photo_param = uploadingOfPhotoResponse.get("photo").toString(); 243 | hash = uploadingOfPhotoResponse.getString("hash"); 244 | } else { 245 | LOG.error("No 'photo', 'server' or 'hash' param in response {}", uploadingOfPhotoResponseString); 246 | return this; 247 | } 248 | 249 | // Saving the photo 250 | String saveMessagesPhotoQuery = "https://api.vk.com/method/photos.saveMessagesPhoto?access_token=" + accessToken + "&v=5.67&server=" + server + "&photo=" + photo_param + "&hash=" + hash; 251 | JSONObject saveMessagesPhotoResponse = new JSONObject(Connection.getRequestResponse(saveMessagesPhotoQuery)); 252 | String photoAsAttach = saveMessagesPhotoResponse.has("response") ? "photo" + saveMessagesPhotoResponse.getJSONArray("response").getJSONObject(0).getInt("owner_id") + "_" + saveMessagesPhotoResponse.getJSONArray("response").getJSONObject(0).getInt("id") : ""; 253 | 254 | this.attachments.add(photoAsAttach); 255 | } 256 | return this; 257 | } 258 | 259 | /** 260 | * Synchronous adding doc to the message 261 | * 262 | * @param doc String URL, link to vk doc or path to file 263 | * @param typeOfDoc Type of doc, 'audio_message' or 'graffiti' ('doc' as default) 264 | */ 265 | public Message doc(String doc, DocTypes typeOfDoc) { 266 | 267 | // Use already loaded photo 268 | if (Pattern.matches("[htps:/vk.com]?doc-?\\d+_\\d+", doc)) { 269 | this.attachments.add(doc.substring(doc.lastIndexOf("doc"))); 270 | return this; 271 | } 272 | 273 | String type = null; 274 | File docFile = new File(doc); 275 | if (docFile.exists()) { 276 | type = "fromFile"; 277 | } 278 | 279 | URL docUrl = null; 280 | if (type == null) { 281 | try { 282 | docUrl = new URL(doc); 283 | type = "fromUrl"; 284 | } catch (MalformedURLException ignored) { 285 | LOG.error("Error when trying add doc to message: file not found, or url is bad. Your param: {}", doc); 286 | return this; 287 | } 288 | } 289 | 290 | byte[] docBytes; 291 | String fileNameField; 292 | 293 | switch (type) { 294 | 295 | case "fromFile": { 296 | try { 297 | docBytes = Files.readAllBytes(Paths.get(docFile.toURI())); 298 | fileNameField = docFile.getName(); 299 | } catch (IOException ignored) { 300 | LOG.error("Error when reading file {}", docFile.getAbsolutePath()); 301 | return this; 302 | } 303 | break; 304 | } 305 | 306 | case "fromUrl": { 307 | try { 308 | URLConnection conn = docUrl.openConnection(); 309 | 310 | try { 311 | docBytes = Utils.toByteArray(conn); 312 | fileNameField = Utils.guessFileNameByContentType(conn.getContentType()); 313 | } finally { 314 | Utils.close(conn); 315 | } 316 | } catch (IOException ignored) { 317 | LOG.error("Error {} occured when reading URL {}", ignored.toString(), doc); 318 | return this; 319 | } 320 | break; 321 | } 322 | 323 | default: { 324 | LOG.error("Bad 'doc' string: path to file, URL or already uploaded 'doc()_()' was expected, but got this: {}", doc); 325 | return this; 326 | } 327 | } 328 | 329 | docFromBytes(docBytes, typeOfDoc, fileNameField); 330 | 331 | return this; 332 | } 333 | 334 | public Message docFromBytes(byte[] docBytes, DocTypes typeOfDoc, String fileNameField) { 335 | 336 | if (docBytes != null) { 337 | 338 | // Getting of server for uploading the photo 339 | String getUploadServerQuery = "https://api.vk.com/method/docs.getMessagesUploadServer?access_token=" + accessToken + "&peer_id=" + this.peerId + "&v=5.67" + "&type=" + typeOfDoc.getType(); 340 | JSONObject getUploadServerResponse = new JSONObject(Connection.getRequestResponse(getUploadServerQuery)); 341 | String uploadUrl = getUploadServerResponse.has("response") ? getUploadServerResponse.getJSONObject("response").has("upload_url") ? getUploadServerResponse.getJSONObject("response").getString("upload_url") : null : null; 342 | 343 | // Some error 344 | if (uploadUrl == null) { 345 | LOG.error("No upload url in response: {}", getUploadServerResponse); 346 | return this; 347 | } 348 | 349 | // Uploading the photo 350 | String uploadingOfDocResponseString; 351 | 352 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 353 | multipartUtility.addBytesPart("file", fileNameField, docBytes); 354 | uploadingOfDocResponseString = multipartUtility.finish(); 355 | 356 | JSONObject uploadingOfDocResponse; 357 | 358 | try { 359 | uploadingOfDocResponse = new JSONObject(uploadingOfDocResponseString); 360 | } catch (JSONException ignored) { 361 | LOG.error("Bad response of uploading doc: {}, error: {}", uploadingOfDocResponseString, ignored.toString()); 362 | return this; 363 | } 364 | 365 | // Getting necessary params 366 | String file; 367 | if (uploadingOfDocResponse.has("file")) { 368 | file = uploadingOfDocResponse.getString("file"); 369 | } else { 370 | LOG.error("No 'file' param in response {}", uploadingOfDocResponseString); 371 | return this; 372 | } 373 | 374 | // Saving the photo 375 | String saveMessagesDocQuery = "https://api.vk.com/method/docs.save?access_token=" + accessToken + "&v=5.67&file=" + file; 376 | JSONObject saveMessagesDocResponse = new JSONObject(Connection.getRequestResponse(saveMessagesDocQuery)); 377 | String docAsAttach = saveMessagesDocResponse.has("response") ? "doc" + saveMessagesDocResponse.getJSONArray("response").getJSONObject(0).getInt("owner_id") + "_" + saveMessagesDocResponse.getJSONArray("response").getJSONObject(0).getInt("id") : ""; 378 | 379 | this.attachments.add(docAsAttach); 380 | } else { 381 | LOG.error("Got file or url of doc to be uploaded, but some error occured and readed 0 bytes."); 382 | } 383 | 384 | return this; 385 | } 386 | 387 | /** 388 | * Synchronous adding doc to the message 389 | * 390 | * @param doc String URL, link to vk doc or path to file 391 | */ 392 | public Message doc(String doc) { 393 | this.doc(doc, DocTypes.DOC); 394 | return this; 395 | } 396 | 397 | /** 398 | * Attach photo to message 399 | *

400 | * Works slower that sync photo adding, but will be called from execute 401 | * 402 | * @param photo Photo link: url, from disk or already uploaded to VK as photo{owner_id}_{id} 403 | */ 404 | public Message photoAsync(String photo) { 405 | 406 | // Use already loaded photo 407 | if (Pattern.matches("[htps:/vk.com]?photo-?\\d+_\\d+", photo)) { 408 | this.attachments.add(photo.substring(photo.lastIndexOf("photo"))); 409 | return this; 410 | } 411 | 412 | // Use photo from url of disc 413 | this.photosToUpload.add(photo); 414 | return this; 415 | } 416 | 417 | /** 418 | * Async uploading photos 419 | */ 420 | public void uploadPhoto(String photo, Callback callback) { 421 | 422 | String type = null; 423 | File photoFile = new File(photo); 424 | if (photoFile.exists()) { 425 | type = "fromFile"; 426 | } 427 | 428 | URL photoUrl = null; 429 | if (type == null) { 430 | try { 431 | photoUrl = new URL(photo); 432 | type = "fromUrl"; 433 | } catch (MalformedURLException ignored) { 434 | LOG.error("Error when trying add photo to message: file not found, or url is bad. Your param: {}", photo); 435 | callback.onResult("false"); 436 | return; 437 | } 438 | } 439 | 440 | byte[] photoBytes; 441 | switch (type) { 442 | 443 | case "fromFile": { 444 | try { 445 | photoBytes = Files.readAllBytes(Paths.get(photoFile.toURI())); 446 | } catch (IOException ignored) { 447 | LOG.error("Error when reading file {}", photoFile.getAbsolutePath()); 448 | callback.onResult("false"); 449 | return; 450 | } 451 | break; 452 | } 453 | 454 | case "fromUrl": { 455 | try { 456 | photoBytes = Utils.toByteArray(photoUrl); 457 | } catch (IOException ignored) { 458 | LOG.error("Error {} occured when reading URL {}", ignored.toString(), photo); 459 | callback.onResult("false"); 460 | return; 461 | } 462 | break; 463 | } 464 | 465 | default: { 466 | LOG.error("Bad 'photo' string: path to file, URL or already uploaded 'photo()_()' was expected."); 467 | callback.onResult("false"); 468 | return; 469 | } 470 | } 471 | 472 | if (photoBytes != null) { 473 | 474 | JSONObject params_getMessagesUploadServer = new JSONObject().put("peer_id", peerId); 475 | api.call("photos.getMessagesUploadServer", params_getMessagesUploadServer, response -> { 476 | 477 | if (response.toString().equalsIgnoreCase("false")) { 478 | LOG.error("Can't get messages upload server, aborting. Photo wont be attached to message."); 479 | callback.onResult(false); 480 | return; 481 | } 482 | 483 | String uploadUrl = new JSONObject(response.toString()).getString("upload_url"); 484 | 485 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 486 | multipartUtility.addBytesPart("photo", "photo.png", photoBytes); 487 | 488 | String response_uploadFileString = multipartUtility.finish(); 489 | 490 | if (response_uploadFileString.length() < 2 || response_uploadFileString.contains("error") || !response_uploadFileString.contains("photo")) { 491 | LOG.error("Photo wan't uploaded: {}", response_uploadFileString); 492 | callback.onResult("false"); 493 | return; 494 | } 495 | 496 | JSONObject getPhotoStringResponse; 497 | 498 | try { 499 | getPhotoStringResponse = new JSONObject(response_uploadFileString); 500 | } catch (JSONException ignored) { 501 | LOG.error("Bad response of uploading photo: {}", response_uploadFileString); 502 | callback.onResult("false"); 503 | return; 504 | } 505 | 506 | if (!getPhotoStringResponse.has("photo") || !getPhotoStringResponse.has("server") || !getPhotoStringResponse.has("hash")) { 507 | LOG.error("Bad response of uploading photo, no 'photo', 'server' of 'hash' param: {}", getPhotoStringResponse.toString()); 508 | callback.onResult("false"); 509 | return; 510 | } 511 | 512 | String photoParam = getPhotoStringResponse.getString("photo"); 513 | Object serverParam = getPhotoStringResponse.get("server"); 514 | String hashParam = getPhotoStringResponse.getString("hash"); 515 | 516 | JSONObject params_photosSaveMessagesPhoto = new JSONObject().put("photo", photoParam).put("server", serverParam + "").put("hash", hashParam); 517 | 518 | api.call("photos.saveMessagesPhoto", params_photosSaveMessagesPhoto, response1 -> { 519 | 520 | 521 | if (response1.toString().equalsIgnoreCase("false")) { 522 | LOG.error("Error when saving uploaded photo: response is 'false', see execution errors."); 523 | callback.onResult("false"); 524 | return; 525 | } 526 | 527 | JSONObject response_saveMessagesPhotoe = new JSONArray(response1.toString()).getJSONObject(0); 528 | 529 | int ownerId = response_saveMessagesPhotoe.getInt("owner_id"), id = response_saveMessagesPhotoe.getInt("id"); 530 | 531 | String attach = "photo" + ownerId + '_' + id; 532 | callback.onResult(attach); 533 | }); 534 | }); 535 | } 536 | } 537 | 538 | /** 539 | * Async uploading doc 540 | */ 541 | public void uploadDoc(JSONObject doc, Callback callback) { 542 | 543 | String type = null, fileNameField; 544 | File docFile = new File(doc.getString("doc")); 545 | if (docFile.exists()) { 546 | type = "fromFile"; 547 | } 548 | 549 | URL docUrl = null; 550 | if (type == null) { 551 | try { 552 | docUrl = new URL(doc.getString("doc")); 553 | type = "fromUrl"; 554 | } catch (MalformedURLException ignored) { 555 | LOG.error("Error when trying add doc to message: file not found, or url is bad. Your param: {}", doc); 556 | callback.onResult("false"); 557 | return; 558 | } 559 | } 560 | 561 | byte[] docBytes; 562 | switch (type) { 563 | 564 | case "fromFile": { 565 | try { 566 | docBytes = Files.readAllBytes(Paths.get(docFile.toURI())); 567 | fileNameField = docFile.getName(); 568 | } catch (IOException ignored) { 569 | LOG.error("Error when reading file {}", docFile.getAbsolutePath()); 570 | callback.onResult("false"); 571 | return; 572 | } 573 | break; 574 | } 575 | 576 | case "fromUrl": { 577 | try { 578 | URLConnection conn = docUrl.openConnection(); 579 | 580 | try { 581 | docBytes = Utils.toByteArray(conn); 582 | fileNameField = Utils.guessFileNameByContentType(conn.getContentType()); 583 | } finally { 584 | Utils.close(conn); 585 | } 586 | } catch (IOException ignored) { 587 | LOG.error("Error when reading URL {}", doc); 588 | callback.onResult("false"); 589 | return; 590 | } 591 | break; 592 | } 593 | 594 | default: { 595 | LOG.error("Bad file or url provided as doc: {}", doc); 596 | return; 597 | } 598 | } 599 | 600 | if (docBytes != null) { 601 | 602 | JSONObject params_getMessagesUploadServer = new JSONObject().put("peer_id", peerId).put("type", doc.getString("type")); 603 | api.call("docs.getMessagesUploadServer", params_getMessagesUploadServer, response -> { 604 | 605 | if (response.toString().equalsIgnoreCase("false")) { 606 | LOG.error("Can't get messages upload server, aborting. Doc wont be attached to message."); 607 | callback.onResult("false"); 608 | return; 609 | } 610 | 611 | String uploadUrl = new JSONObject(response.toString()).getString("upload_url"); 612 | 613 | MultipartUtility multipartUtility = new MultipartUtility(uploadUrl); 614 | multipartUtility.addBytesPart("file", fileNameField, docBytes); 615 | String response_uploadFileString = multipartUtility.finish(); 616 | 617 | if (response_uploadFileString.length() < 2 || response_uploadFileString.contains("error") || !response_uploadFileString.contains("file")) { 618 | LOG.error("Doc won't uploaded: {}", response_uploadFileString); 619 | callback.onResult("false"); 620 | return; 621 | } 622 | 623 | JSONObject getFileStringResponse; 624 | 625 | try { 626 | getFileStringResponse = new JSONObject(response_uploadFileString); 627 | } catch (JSONException ignored) { 628 | LOG.error("Bad response of uploading file: {}", response_uploadFileString); 629 | callback.onResult("false"); 630 | return; 631 | } 632 | 633 | if (!getFileStringResponse.has("file")) { 634 | LOG.error("Bad response of uploading doc, no 'file' param: {}", getFileStringResponse.toString()); 635 | callback.onResult("false"); 636 | return; 637 | } 638 | 639 | String fileParam = getFileStringResponse.getString("file"); 640 | 641 | JSONObject params_photosSaveMessagesPhoto = new JSONObject().put("file", fileParam); 642 | 643 | api.call("docs.save", params_photosSaveMessagesPhoto, response1 -> { 644 | 645 | if (response1.toString().equalsIgnoreCase("false")) { 646 | LOG.error("Error when saving uploaded doc: response is 'false', see execution errors."); 647 | callback.onResult("false"); 648 | return; 649 | } 650 | 651 | JSONObject response_saveMessagesPhotoe = new JSONArray(response1.toString()).getJSONObject(0); 652 | 653 | int ownerId = response_saveMessagesPhotoe.getInt("owner_id"), id = response_saveMessagesPhotoe.getInt("id"); 654 | 655 | String attach = "doc" + ownerId + '_' + id; 656 | callback.onResult(attach); 657 | }); 658 | }); 659 | } 660 | } 661 | 662 | /** 663 | * Attach doc to message 664 | * 665 | * @param doc Doc link: url, from disk or already uploaded to VK as doc{owner_id}_{id} 666 | */ 667 | public Message docAsync(String doc, DocTypes type) { 668 | 669 | // Use already loaded photo 670 | if (Pattern.matches("[htps:/vk.com]?doc-?\\d+_\\d+", doc)) { 671 | this.attachments.add(doc); 672 | return this; 673 | } 674 | 675 | this.docsToUpload.add(new JSONObject().put("doc", doc).put("type", type.getType())); 676 | return this; 677 | } 678 | 679 | /** 680 | * Attach doc to message 681 | * 682 | * @param doc Doc link: url, from disk or already uploaded to VK as doc{owner_id}_{id} 683 | */ 684 | public Message docAsync(String doc) { 685 | 686 | this.docAsync(doc, DocTypes.DOC); 687 | return this; 688 | } 689 | 690 | /** 691 | * Send voice message 692 | * 693 | * @param doc URL or path to file 694 | * @param callback response will returns to callback 695 | */ 696 | public void sendVoiceMessage(String doc, Callback... callback) { 697 | this.doc(doc, DocTypes.AUDIO_MESSAGE).send(callback); 698 | } 699 | 700 | /** 701 | * Send the message 702 | * 703 | * @param callback will be called with response object 704 | */ 705 | public void send(Callback... callback) { 706 | 707 | if (photosToUpload.size() > 0) { 708 | String photo = photosToUpload.get(0); 709 | photosToUpload.remove(0); 710 | uploadPhoto(photo, response -> { 711 | if (!response.toString().equalsIgnoreCase("false")) { 712 | this.attachments.addIfAbsent(response.toString()); 713 | send(callback); 714 | } else { 715 | LOG.error("Some error occured when uploading photo."); 716 | } 717 | }); 718 | return; 719 | } 720 | 721 | if (docsToUpload.size() > 0) { 722 | JSONObject doc = docsToUpload.get(0); 723 | docsToUpload.remove(0); 724 | uploadDoc(doc, response -> { 725 | if (!response.toString().equalsIgnoreCase("false")) { 726 | this.attachments.addIfAbsent(response.toString()); 727 | send(callback); 728 | } else { 729 | LOG.error("Some error occured when uploading doc."); 730 | } 731 | }); 732 | return; 733 | } 734 | 735 | text = (text != null && text.length() > 0) ? text : ""; 736 | title = (title != null && title.length() > 0) ? title : ""; 737 | 738 | randomId = randomId != null && randomId > 0 ? randomId : 0; 739 | peerId = peerId != null ? peerId : -142409596; 740 | attachments = attachments != null && attachments.size() > 0 ? attachments : new CopyOnWriteArrayList<>(); 741 | forwardedMessages = forwardedMessages != null && forwardedMessages.size() > 0 ? forwardedMessages : new CopyOnWriteArrayList<>(); 742 | stickerId = stickerId != null && stickerId > 0 ? stickerId : 0; 743 | 744 | JSONObject params = new JSONObject(); 745 | 746 | params.put("message", text); 747 | if (title != null && title.length() > 0) params.put("title", title); 748 | if (randomId != null && randomId > 0) params.put("random_id", randomId); 749 | params.put("peer_id", peerId); 750 | if (attachments.size() > 0) params.put("attachment", String.join(",", attachments)); 751 | if (forwardedMessages.size() > 0) params.put("forward_messages", String.join(",", forwardedMessages)); 752 | if (stickerId != null && stickerId > 0) params.put("sticker_id", stickerId); 753 | 754 | if(callback != null && callback.length > 0) 755 | api.call("messages.send", params, response -> { 756 | if (callback.length > 0) { 757 | callback[0].onResult(response); 758 | } 759 | if (!(response instanceof Integer)) { 760 | LOG.error("Message not sent: {}", response); 761 | } 762 | }); 763 | else { 764 | api.callSync("messages.send", params); 765 | } 766 | } 767 | 768 | /** 769 | * Get the type of message 770 | */ 771 | public String messageType() { 772 | 773 | if (isVoiceMessage()) { 774 | return "voiceMessage"; 775 | } else if (isStickerMessage()) { 776 | return "stickerMessage"; 777 | } else if (isGifMessage()) { 778 | return "gifMessage"; 779 | } else if (isAudioMessage()) { 780 | return "audioMessage"; 781 | } else if (isVideoMessage()) { 782 | return "videoMessage"; 783 | } else if (isDocMessage()) { 784 | return "docMessage"; 785 | } else if (isWallMessage()) { 786 | return "wallMessage"; 787 | } else if (isPhotoMessage()) { 788 | return "photoMessage"; 789 | } else if (isLinkMessage()) { 790 | return "linkMessage"; 791 | } else if (isSimpleTextMessage()) { 792 | return "simpleTextMessage"; 793 | } else return "error"; 794 | } 795 | 796 | /** 797 | * @return true if message has forwarded messages 798 | */ 799 | public boolean hasFwds() { 800 | boolean answer = false; 801 | 802 | if (attachmentsOfReceivedMessage.has("fwd")) 803 | answer = true; 804 | 805 | return answer; 806 | } 807 | 808 | /** 809 | * @return array of forwarded messages or [] 810 | */ 811 | public JSONArray getForwardedMessages() { 812 | if (hasFwds()) { 813 | JSONObject response = new JSONObject(api.callSync("messages.getById", "message_ids", getMessageId())); 814 | 815 | if (response.has("response") && response.getJSONObject("response").getJSONArray("items").getJSONObject(0).has("fwd_messages")) { 816 | return response.getJSONObject("response").getJSONArray("items").getJSONObject(0).getJSONArray("fwd_messages"); 817 | } 818 | } 819 | 820 | return new JSONArray(); 821 | } 822 | 823 | /** 824 | * Get attachments from message 825 | */ 826 | public JSONArray getAttachments() { 827 | 828 | JSONObject response = new JSONObject(api.callSync("messages.getById", "message_ids", getMessageId())); 829 | 830 | if (response.has("response") && response.getJSONObject("response").getJSONArray("items").getJSONObject(0).has("attachments")) 831 | return response.getJSONObject("response").getJSONArray("items").getJSONObject(0).getJSONArray("attachments"); 832 | 833 | return new JSONArray(); 834 | } 835 | 836 | /* 837 | * Priority: voice, sticker, gif, ... , simple text 838 | */ 839 | public boolean isPhotoMessage() { 840 | return getCountOfAttachmentsByType().get("photo") > 0; 841 | } 842 | 843 | public boolean isSimpleTextMessage() { 844 | return getCountOfAttachmentsByType().get("summary") == 0; 845 | } 846 | 847 | public boolean isVoiceMessage() { 848 | return getCountOfAttachmentsByType().get("voice") > 0; 849 | } 850 | 851 | public boolean isAudioMessage() { 852 | return getCountOfAttachmentsByType().get("audio") > 0; 853 | } 854 | 855 | public boolean isVideoMessage() { 856 | return getCountOfAttachmentsByType().get("video") > 0; 857 | } 858 | 859 | public boolean isDocMessage() { 860 | return getCountOfAttachmentsByType().get("doc") > 0; 861 | } 862 | 863 | public boolean isWallMessage() { 864 | return getCountOfAttachmentsByType().get("wall") > 0; 865 | } 866 | 867 | public boolean isStickerMessage() { 868 | return getCountOfAttachmentsByType().get("sticker") > 0; 869 | } 870 | 871 | public boolean isLinkMessage() { 872 | return getCountOfAttachmentsByType().get("link") > 0; 873 | } 874 | 875 | public boolean isGifMessage() { 876 | JSONArray attachments = getAttachments(); 877 | 878 | for (int i = 0; i < attachments.length(); i++) { 879 | if (attachments.getJSONObject(i).has("type") && attachments.getJSONObject(i).getJSONObject(attachments.getJSONObject(i).getString("type")).has("type") && attachments.getJSONObject(i).getJSONObject(attachments.getJSONObject(i).getString("type")).getInt("type") == 3) 880 | return true; 881 | } 882 | 883 | return false; 884 | } 885 | 886 | // Getters and setters for handling new message 887 | 888 | /** 889 | * Method helps to identify kind of message 890 | * 891 | * @return Map: key=type of attachment, value=count of attachments, key=summary - value=count of all attachments. 892 | */ 893 | public Map getCountOfAttachmentsByType() { 894 | 895 | int photo = 0, video = 0, audio = 0, doc = 0, wall = 0, link = 0; 896 | 897 | Map answer = new HashMap() {{ 898 | put("photo", 0); 899 | put("video", 0); 900 | put("audio", 0); 901 | put("doc", 0); 902 | put("wall", 0); 903 | put("sticker", 0); 904 | put("link", 0); 905 | put("voice", 0); 906 | put("summary", 0); 907 | }}; 908 | 909 | if (attachmentsOfReceivedMessage.toString().contains("sticker")) { 910 | answer.put("sticker", 1); 911 | answer.put("summary", 1); 912 | return answer; 913 | } else { 914 | if (attachmentsOfReceivedMessage.toString().contains("audiomsg")) { 915 | answer.put("voice", 1); 916 | answer.put("summary", 1); 917 | return answer; 918 | } else { 919 | for (String key : attachmentsOfReceivedMessage.keySet()) { 920 | if (key.startsWith("attach") && key.endsWith("type")) { 921 | 922 | String value = attachmentsOfReceivedMessage.getString(key); 923 | switch (value) { 924 | 925 | case "photo": { 926 | answer.put(value, ++photo); 927 | break; 928 | } 929 | case "video": { 930 | answer.put(value, ++video); 931 | break; 932 | } 933 | case "audio": { 934 | answer.put(value, ++audio); 935 | break; 936 | } 937 | case "doc": { 938 | answer.put(value, ++doc); 939 | break; 940 | } 941 | case "wall": { 942 | answer.put(value, ++wall); 943 | break; 944 | } 945 | case "link": { 946 | answer.put(value, ++link); 947 | break; 948 | } 949 | } 950 | } 951 | 952 | } 953 | } 954 | } 955 | 956 | int summary = 0; 957 | for (String key : answer.keySet()) { 958 | if (answer.get(key) > 0) 959 | summary++; 960 | } 961 | answer.put("summary", summary); 962 | 963 | return answer; 964 | } 965 | 966 | /* Public getters */ 967 | 968 | public Integer getMessageId() { 969 | return messageId; 970 | } 971 | 972 | public Integer getFlags() { 973 | return flags; 974 | } 975 | 976 | public Integer authorId() { 977 | return peerId; 978 | } 979 | 980 | public Integer getTimestamp() { 981 | return timestamp; 982 | } 983 | 984 | public String getText() { 985 | return text; 986 | } 987 | 988 | public JSONArray getPhotos() { 989 | JSONArray attachments = getAttachments(); 990 | JSONArray answer = new JSONArray(); 991 | 992 | for (int i = 0; i < attachments.length(); i++) { 993 | if (attachments.getJSONObject(i).getString("type").contains("photo")) 994 | answer.put(attachments.getJSONObject(i).getJSONObject("photo")); 995 | } 996 | 997 | return answer; 998 | } 999 | 1000 | public Integer getChatIdLong() { 1001 | return chatIdLong; 1002 | } 1003 | 1004 | /* Private setters */ 1005 | 1006 | private void setMessageId(Integer messageId) { 1007 | this.messageId = messageId; 1008 | } 1009 | 1010 | private void setFlags(Integer flags) { 1011 | this.flags = flags; 1012 | } 1013 | 1014 | private void setPeerId(Integer peerId) { 1015 | this.peerId = peerId; 1016 | } 1017 | 1018 | private void setTimestamp(Integer timestamp) { 1019 | this.timestamp = timestamp; 1020 | } 1021 | 1022 | private void setText(String text) { 1023 | this.text = text; 1024 | } 1025 | 1026 | public void setChatIdLong(Integer chatIdLong) { 1027 | this.chatIdLong = chatIdLong; 1028 | } 1029 | 1030 | /** 1031 | * @param photos JSONArray with photo objects 1032 | * @return URL of biggest image file 1033 | */ 1034 | public String getBiggestPhotoUrl(JSONArray photos) { 1035 | 1036 | String currentBiggestPhoto = null; 1037 | 1038 | for (int i = 0; i < photos.length(); i++) { 1039 | if (photos.getJSONObject(i).has("photo_1280")) 1040 | currentBiggestPhoto = photos.getJSONObject(i).getString("photo_1280"); 1041 | else if (photos.getJSONObject(i).has("photo_807")) 1042 | currentBiggestPhoto = photos.getJSONObject(i).getString("photo_807"); 1043 | else if (photos.getJSONObject(i).has("photo_604")) 1044 | currentBiggestPhoto = photos.getJSONObject(i).getString("photo_604"); 1045 | else if (photos.getJSONObject(i).has("photo_130")) 1046 | currentBiggestPhoto = photos.getJSONObject(i).getString("photo_130"); 1047 | else if (photos.getJSONObject(i).has("photo_75")) 1048 | currentBiggestPhoto = photos.getJSONObject(i).getString("photo_75"); 1049 | } 1050 | 1051 | return currentBiggestPhoto; 1052 | } 1053 | 1054 | public JSONObject getVoiceMessage() { 1055 | 1056 | JSONArray attachments = getAttachments(); 1057 | JSONObject answer = new JSONObject(); 1058 | 1059 | for (int i = 0; i < attachments.length(); i++) { 1060 | if (attachments.getJSONObject(i).getString("type").contains("doc") && attachments.getJSONObject(i).getJSONObject("doc").toString().contains("waveform")) 1061 | answer = attachments.getJSONObject(i).getJSONObject("doc"); 1062 | } 1063 | 1064 | return answer; 1065 | } 1066 | 1067 | public boolean isMessageFromChat() { 1068 | 1069 | return (chatId != null && chatId > 0) || (chatIdLong != null && chatIdLong > 0); 1070 | } 1071 | 1072 | public Integer chatId() { 1073 | return chatId; 1074 | } 1075 | 1076 | public void setChatId(Integer chatId) { 1077 | this.chatId = chatId; 1078 | } 1079 | 1080 | public void setAccessToken(String accessToken) { 1081 | this.accessToken = accessToken; 1082 | } 1083 | 1084 | private void setAttachments(JSONObject attachments) { 1085 | 1086 | this.attachmentsOfReceivedMessage = attachments; 1087 | } 1088 | 1089 | public Integer getRandomId() { 1090 | return randomId; 1091 | } 1092 | 1093 | private void setRandomId(Integer randomId) { 1094 | this.randomId = randomId; 1095 | } 1096 | 1097 | public String getTitle() { 1098 | return title; 1099 | } 1100 | 1101 | public void setTitle(String title) { 1102 | this.title = title; 1103 | } 1104 | 1105 | private String[] getForwardedMessagesIds() { 1106 | 1107 | if (attachmentsOfReceivedMessage.has("fwd")) { 1108 | return attachmentsOfReceivedMessage.getString("fwd").split(","); 1109 | } 1110 | 1111 | return new String[]{}; 1112 | } 1113 | 1114 | @Override 1115 | public String toString() { 1116 | return '{' + 1117 | "\"message_id\":" + messageId + 1118 | ",\"flags\":" + flags + 1119 | ",\"peer_id\":" + peerId + 1120 | ",\"timestamp\":" + timestamp + 1121 | ",\"random_id\":" + randomId + 1122 | ",\"text\":\"" + text + '\"' + 1123 | ",\"attachments\":" + attachmentsOfReceivedMessage.toString() + 1124 | '}'; 1125 | } 1126 | } 1127 | --------------------------------------------------------------------------------