├── test └── iris │ └── vk │ └── test │ ├── logger.properties │ ├── send_simple.kt │ ├── Util.kt │ ├── MyLoggerConsole.kt │ ├── group_cb_listener.kt │ ├── address_test.kt │ ├── filter_events.kt │ ├── command_handler.kt │ ├── group_cb_multibot.kt │ ├── polling_user.kt │ ├── command_handler_dsl.kt │ ├── polling_group.kt │ ├── send_pack.kt │ ├── command_trigger_style_dsl.kt │ ├── send_future.kt │ ├── keyboard_demo.kt │ ├── command_trigger_style.kt │ └── custom_event_producer.kt ├── src └── iris │ └── vk │ ├── callback │ ├── VkCallbackEventConsumer.kt │ ├── VkCallbackReadWriteBuffer.kt │ ├── GroupSourceSimple.kt │ ├── VkCallbackRequestHandler.kt │ ├── VkCallbackRequestServer.kt │ ├── AddressTester.kt │ ├── VkCallbackGroup.kt │ ├── GroupbotSource.kt │ ├── VkCallbackReadWriteBufferDefault.kt │ ├── VkCallbackRequestServerDefault.kt │ ├── VkCallbackGroupBuilder.kt │ ├── VkCallbackRequestHandlerDefault.kt │ └── AddressTesterDefault.kt │ ├── api │ ├── VkRequestData.kt │ ├── IAccount.kt │ ├── IUsers.kt │ ├── IBoard.kt │ ├── Method2UrlCache.kt │ ├── IUtils.kt │ ├── Requester.kt │ ├── IFriends.kt │ ├── common │ │ ├── Account.kt │ │ ├── Users.kt │ │ ├── SectionAbstract.kt │ │ ├── Board.kt │ │ ├── Utils.kt │ │ ├── Friends.kt │ │ ├── Docs.kt │ │ ├── Wall.kt │ │ ├── Photos.kt │ │ └── Groups.kt │ ├── VkApiInterface.kt │ ├── simple │ │ ├── VkApiConnection.kt │ │ ├── VkApi.kt │ │ ├── DocsSimple.kt │ │ ├── PhotosSimple.kt │ │ └── VkApiConnectionHttpClient.kt │ ├── future │ │ ├── VkApiConnectionFuture.kt │ │ ├── VkApiFuture.kt │ │ ├── DocsFuture.kt │ │ ├── VkApiPack.kt │ │ ├── PhotosFuture.kt │ │ └── VkApiConnectionFutureHttpClient.kt │ ├── LongPollSettings.kt │ ├── IWall.kt │ ├── IDocs.kt │ ├── IGroups.kt │ ├── IPhotos.kt │ ├── constants.kt │ ├── VkApis.kt │ └── IMessages.kt │ ├── event │ ├── MessageEdit.kt │ ├── PinUpdate.kt │ ├── TitleUpdate.kt │ ├── Event.kt │ ├── OtherEvent.kt │ ├── CallbackEvent.kt │ ├── ChatEvent.kt │ ├── group │ │ ├── GroupPinUpdate.kt │ │ ├── GroupMessageWithoutChatInfo.kt │ │ ├── GroupTitleUpdate.kt │ │ ├── GroupCallbackEvent.kt │ │ ├── GroupMessage.kt │ │ └── GroupChatEvent.kt │ ├── user │ │ ├── UserPinUpdate.kt │ │ ├── UserTitleUpdate.kt │ │ ├── UserChatEvent.kt │ │ └── UserMessage.kt │ └── Message.kt │ ├── command │ ├── CommandMatcherWithHash.kt │ ├── Command.kt │ ├── CommandExtractor.kt │ ├── CommandMatcher.kt │ ├── CommandExtractorChars.kt │ ├── CommandMatcherSimple.kt │ ├── CommandExtractorPrefixes.kt │ ├── CommandExtractorBase.kt │ ├── CommandMatcherRegex.kt │ ├── commands_dsl.kt │ └── VkCommandHandler.kt │ ├── VkRetrievable.kt │ ├── VkUpdateProcessor.kt │ ├── VkUpdateProcessorMultisource.kt │ ├── VkGroupSourceList.kt │ ├── VkEventHandler.kt │ ├── VkException.kt │ ├── VkEventProducer.kt │ ├── VkEventFilter.kt │ ├── VkEventFilterAdapter.kt │ ├── VkEventHandlerAdapter.kt │ ├── VkPollingGroup.kt │ ├── VkEventHandlerArray.kt │ ├── VkEventHandlerList.kt │ ├── Options.kt │ ├── VkEventFilterHandler.kt │ ├── VkKeyboard.kt │ ├── VkPollingUser.kt │ ├── VkUpdateProcessorGroupDefault.kt │ ├── VkUpdateProcessorUserDefault.kt │ └── VkTriggerEventHandler.kt └── README.md /test/iris/vk/test/logger.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iris2iris/iris-vk-api/HEAD/test/iris/vk/test/logger.properties -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackEventConsumer.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.json.JsonItem 4 | 5 | interface VkCallbackEventConsumer { 6 | fun send(event: JsonItem) 7 | } -------------------------------------------------------------------------------- /src/iris/vk/api/VkRequestData.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | class VkRequestData(val method: String, val options: Options? = null, val token: String? = null) -------------------------------------------------------------------------------- /src/iris/vk/event/MessageEdit.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface MessageEdit: Message { 8 | 9 | } -------------------------------------------------------------------------------- /src/iris/vk/event/PinUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface PinUpdate : ChatEvent { 8 | 9 | } -------------------------------------------------------------------------------- /src/iris/vk/event/TitleUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface TitleUpdate : ChatEvent { 8 | val text: String 9 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IAccount.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 29.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IAccount { 8 | fun ban(id: Int): SingleType 9 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandMatcherWithHash.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | /** 4 | * @created 27.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface CommandMatcherWithHash : CommandMatcher { 8 | fun hashChars(): CharArray? 9 | } -------------------------------------------------------------------------------- /src/iris/vk/command/Command.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 27.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | 10 | interface Command { 11 | fun run(message: Message) 12 | } -------------------------------------------------------------------------------- /src/iris/vk/event/Event.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface Event { 10 | val source: JsonItem 11 | val sourcePeerId: Int 12 | } -------------------------------------------------------------------------------- /src/iris/vk/VkRetrievable.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 02.12.2019 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkRetrievable { 10 | fun retrieve(wait: Boolean = true): List 11 | } -------------------------------------------------------------------------------- /src/iris/vk/VkUpdateProcessor.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 31.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkUpdateProcessor { 10 | fun processUpdates(updates: List) 11 | } -------------------------------------------------------------------------------- /src/iris/vk/event/OtherEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 27.11.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class OtherEvent(override val source: JsonItem, override val sourcePeerId: Int) : Event -------------------------------------------------------------------------------- /src/iris/vk/api/IUsers.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IUsers { 8 | fun get(users: List? = null, fields: String? = null, token: String? = null): SingleType 9 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandExtractor.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 27.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface CommandExtractor { 10 | fun extractCommand(message: Message): String? 11 | } -------------------------------------------------------------------------------- /src/iris/vk/VkUpdateProcessorMultisource.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 02.11.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkUpdateProcessorMultisource { 10 | fun processUpdates(updates: List) 11 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackReadWriteBuffer.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.vk.VkRetrievable 4 | 5 | /** 6 | * @created 26.12.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkCallbackReadWriteBuffer : VkCallbackEventConsumer, VkRetrievable { 10 | 11 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandMatcher.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 27.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface CommandMatcher { 10 | fun testAndExecute(command: String, message: Message): Boolean 11 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/GroupSourceSimple.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | class GroupSourceSimple(private val gb: GroupbotSource.Groupbot) : GroupbotSource { 4 | override fun isGetByRequest() = false 5 | override fun getGroupbot(request: VkCallbackRequestHandler.Request) = gb 6 | override fun getGroupbot(groupId: Int) = gb 7 | } -------------------------------------------------------------------------------- /src/iris/vk/event/CallbackEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface CallbackEvent : Event { 8 | val eventId: String 9 | val userId: Int 10 | val payload: String 11 | val peerId: Int 12 | val conversationMessageId: Int 13 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IBoard.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 29.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface IBoard { 10 | fun getComments(groupId: Int, topicId: Int, startCommentId: Int, options: Options? = null, token: String? = null): SingleType 11 | } -------------------------------------------------------------------------------- /src/iris/vk/event/ChatEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface ChatEvent: Event { 8 | val id: Int 9 | val peerId: Int 10 | val fromId: Int 11 | val chatId: Int 12 | val date: Long 13 | val userId: Int 14 | val conversationMessageId: Int 15 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupPinUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.PinUpdate 5 | 6 | /** 7 | * @created 28.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class GroupPinUpdate(source: JsonItem, sourcePeerId: Int) : GroupChatEvent(source, sourcePeerId), PinUpdate { 11 | 12 | } -------------------------------------------------------------------------------- /src/iris/vk/api/Method2UrlCache.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 29.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | object Method2UrlCache { 8 | private val map = HashMap() 9 | 10 | fun getUrl(method: String): String { 11 | return map.getOrPut(method) { "https://api.vk.com/method/$method" } 12 | } 13 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IUtils.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 29.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IUtils { 8 | 9 | fun checkLink(url: String): SingleType 10 | 11 | fun getServerTime(token: String? = null): SingleType 12 | 13 | fun getShortLink(url: String, isPrivate: Boolean = false, token: String? = null): SingleType 14 | } -------------------------------------------------------------------------------- /src/iris/vk/event/user/UserPinUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.user 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.PinUpdate 5 | 6 | /** 7 | * @created 28.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class UserPinUpdate(fullItemSource: ApiSource, source: JsonItem, sourcePeerId: Int) : UserChatEvent(fullItemSource, source, sourcePeerId), PinUpdate { 11 | 12 | } -------------------------------------------------------------------------------- /src/iris/vk/event/Message.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event 2 | 3 | import iris.json.JsonItem 4 | import iris.json.JsonObject 5 | 6 | /** 7 | * @created 28.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | interface Message: ChatEvent { 11 | 12 | val text: String? 13 | val attachments: List? 14 | val forwardedMessages: List? 15 | val replyMessage: JsonObject? 16 | } -------------------------------------------------------------------------------- /src/iris/vk/api/Requester.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface Requester { 10 | fun request(method: String, options: Options?, token: String? = null): SingleType 11 | fun execute(data: List, token: String? = null): ListType 12 | fun emptyOfListType(): ListType 13 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupMessageWithoutChatInfo.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.Message 5 | 6 | /** 7 | * @created 01.11.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | open class GroupMessageWithoutChatInfo(source: JsonItem, sourcePeerId: Int) : GroupMessage(source, sourcePeerId), Message { 11 | override val message: JsonItem by lazy(LazyThreadSafetyMode.NONE) { this.source } 12 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupTitleUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.TitleUpdate 5 | import kotlin.LazyThreadSafetyMode.NONE 6 | 7 | /** 8 | * @created 28.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class GroupTitleUpdate(source: JsonItem, sourcePeerId: Int) : GroupChatEvent(source, sourcePeerId), TitleUpdate { 12 | override val text: String by lazy(NONE) { message["action"]["text"].asStringOrNull()?: "" } 13 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IFriends.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IFriends { 8 | 9 | fun add(userId: Int): SingleType 10 | 11 | fun getRequests(out: Int = 0, count: Int = 100, token: String? = null): SingleType 12 | 13 | fun delete(id: Int, token: String? = null): SingleType 14 | 15 | fun get(amount: Int = 1000, token: String? = null): SingleType 16 | 17 | fun delete(userId: Int): SingleType 18 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Account.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IAccount 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Account(api: Requester) : SectionAbstract(api), IAccount { 12 | override fun ban(id: Int): SingleType { 13 | return request("account.ban", Options("owner_id" to id)) 14 | } 15 | } -------------------------------------------------------------------------------- /test/iris/vk/test/send_simple.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.api.simple.VkApi 4 | 5 | 6 | /** 7 | * @created 27.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | fun main() { 11 | TestUtil.init() 12 | val props = TestUtil.getProperties() 13 | val token = props.getProperty("group.token") 14 | val userToId = props.getProperty("userTo.id").toInt() 15 | 16 | val vk = VkApi(token) 17 | val res = vk.messages.send(userToId, "Привет. Это сообщение с Kotlin") 18 | println(res?.obj()) 19 | } -------------------------------------------------------------------------------- /src/iris/vk/event/user/UserTitleUpdate.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.user 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.TitleUpdate 5 | import kotlin.LazyThreadSafetyMode.NONE 6 | 7 | /** 8 | * @created 28.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class UserTitleUpdate(fullItemSource: ApiSource, source: JsonItem, sourcePeerId: Int) : UserChatEvent(fullItemSource, source, sourcePeerId), TitleUpdate { 12 | override val text: String by lazy(NONE) { source[7]["text"].asStringOrNull() ?: "" } 13 | } -------------------------------------------------------------------------------- /src/iris/vk/api/VkApiInterface.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface VkApiInterface { 8 | val messages: IMessages 9 | val friends: IFriends 10 | val groups: IGroups 11 | val users: IUsers 12 | val photos: IPhotos 13 | val docs: IDocs 14 | val wall: IWall 15 | val utils: IUtils 16 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackRequestHandler.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import java.net.InetSocketAddress 4 | import java.net.URI 5 | 6 | /** 7 | * @created 26.12.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | interface VkCallbackRequestHandler { 11 | fun handle(request: Request) 12 | 13 | interface Request { 14 | fun findHeader(key: String): String? 15 | val requestUri: URI 16 | val remoteAddress: InetSocketAddress 17 | 18 | fun writeResponse(response: String, code: Int = 200) 19 | 20 | fun body(): String 21 | } 22 | } -------------------------------------------------------------------------------- /src/iris/vk/api/simple/VkApiConnection.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.simple 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 07.09.2019 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkApiConnection { 10 | fun request(url:String, data: Map?): VkApiConnectResponse? 11 | fun request(url:String, data:String? = null): VkApiConnectResponse? 12 | fun requestUpload(url:String, files:Map, data: Map? = null): VkApiConnectResponse? 13 | 14 | data class VkApiConnectResponse(val code: Int, val responseText:String) 15 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackRequestServer.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | /** 4 | * Можно взаимодействовать с любой реализацией сервера входящих запросов через данный интерфейс 5 | * в метод `setHandler` передаётся обработчик запросов по указанному URI 6 | * Данный сервер должен вызывать метод `VkCallbackRequestHandler.handle(request: Request)` каждый раз, как получает входящий запрос 7 | * @see VkCallbackRequestServerDefault — базовая реализация сервера входящих запросов 8 | */ 9 | interface VkCallbackRequestServer { 10 | 11 | fun setHandler(path: String, handler: VkCallbackRequestHandler) 12 | fun start() 13 | fun stop(seconds: Int) 14 | 15 | } -------------------------------------------------------------------------------- /src/iris/vk/VkGroupSourceList.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.callback.GroupbotSource 4 | import iris.vk.callback.GroupbotSource.Groupbot 5 | import iris.vk.callback.VkCallbackRequestHandler 6 | 7 | /** 8 | * @created 29.09.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class VkGroupSourceList(groups: List) : GroupbotSource { 12 | 13 | private val groups = groups.associateBy { it.id } 14 | 15 | override fun isGetByRequest() = false 16 | 17 | override fun getGroupbot(request: VkCallbackRequestHandler.Request): Groupbot? = null 18 | 19 | override fun getGroupbot(groupId: Int): Groupbot? { 20 | return groups[groupId] 21 | } 22 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/VkApiConnectionFuture.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.simple.VkApiConnection.VkApiConnectResponse 5 | import java.util.concurrent.CompletableFuture 6 | 7 | /** 8 | * @created 07.09.2019 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | interface VkApiConnectionFuture { 12 | fun request(url:String, data: Map?): CompletableFuture 13 | fun request(url:String, data:String? = null): CompletableFuture 14 | fun requestUpload(url:String, files:Map, data: Map? = null): CompletableFuture 15 | } -------------------------------------------------------------------------------- /test/iris/vk/test/Util.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import java.io.File 4 | import java.util.* 5 | import java.util.logging.LogManager 6 | 7 | /** 8 | * @created 28.09.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | object TestUtil { 12 | 13 | const val confPath = "excl/cfg.properties" 14 | 15 | fun getProperties(): Properties { 16 | val props = Properties() 17 | File(confPath).reader().use { props.load(it) } 18 | return props 19 | } 20 | 21 | fun init() { 22 | initLogger() 23 | } 24 | 25 | private fun initLogger() { 26 | val ist = this.javaClass.getResourceAsStream("logger.properties") 27 | LogManager.getLogManager().readConfiguration(ist) 28 | ist.close() 29 | } 30 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandExtractorChars.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | /** 4 | * @created 27.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | class CommandExtractorChars(prefixChars: String?, maxCommandLength: Int = 150, allowNoPrefix: Boolean = true) 8 | : CommandExtractorBase(CommandExtractorChar(prefixChars, allowNoPrefix), maxCommandLength) 9 | { 10 | 11 | class CommandExtractorChar(private val prefixChars: String? = null, private val allowNoPrefix: Boolean = true) : PrefixTester { 12 | override fun find(text: String): Int { 13 | return if (prefixChars != null) { 14 | if (!prefixChars.contains(text.first())) -1 else 1 15 | } else 16 | if (allowNoPrefix) 0 else -1 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventHandler.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | /** 6 | * @created 08.09.2019 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface VkEventHandler { 10 | fun processMessages(messages: List) 11 | fun processEditedMessages(messages: List) 12 | fun processInvites(invites:List) 13 | fun processLeaves(leaves:List) 14 | fun processTitleUpdates(updaters:List) 15 | fun processPinUpdates(updaters:List) 16 | fun processUnpinUpdates(updates: List) 17 | fun processCallbacks(callbacks: List) 18 | fun processScreenshots(screenshots: List) 19 | fun processOthers(others: List) 20 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupCallbackEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.CallbackEvent 5 | 6 | /** 7 | * @created 28.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class GroupCallbackEvent(override val source: JsonItem, override val sourcePeerId: Int) : CallbackEvent { 11 | override val eventId: String by lazy { source["event_id"].asString() } 12 | override val userId: Int by lazy { source["user_id"].asInt() } 13 | override val payload: String by lazy { source["payload"].asString() } 14 | override val peerId: Int by lazy { source["peer_id"].asInt() } 15 | override val conversationMessageId: Int by lazy { source["conversation_message_id"].asInt() } 16 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Users.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IUsers 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Users(api: Requester) : SectionAbstract(api), IUsers { 12 | override fun get(users: List?, fields: String?, token: String?): SingleType { 13 | val options = Options() 14 | if (users != null && users.isNotEmpty()) 15 | options["user_ids"] = users.joinToString(",") 16 | if (fields != null) 17 | options["fields"] = fields 18 | return request("users.get", options, token) 19 | } 20 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandMatcherSimple.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | open class CommandMatcherSimple(private val commandTemplate: String, private val runCommand: Command) : CommandMatcherWithHash { 6 | 7 | constructor(commandPattern: String, runCommand: (message: Message) -> Unit) : this(commandPattern, object : Command { 8 | override fun run(message: Message) { 9 | runCommand(message) 10 | } 11 | } ) 12 | 13 | override fun testAndExecute(command: String, message: Message): Boolean { 14 | if (commandTemplate != command) return false 15 | runCommand.run(message) 16 | return true 17 | } 18 | 19 | override fun hashChars() = commandTemplate.firstOrNull()?.let { charArrayOf(it) } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /src/iris/vk/api/LongPollSettings.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | open class LongPollSettings(server: String, key: String, mode: String, wait: Int = 10) { 10 | 11 | private val link = "$server?act=a_check&key=$key&wait=$wait&mode=$mode&ts=" 12 | 13 | open fun getUpdatesLink(ts: String): String { 14 | return StringBuilder(link.length + ts.length).append(link).append(ts).toString() 15 | } 16 | 17 | companion object { 18 | fun build(data: Options): LongPollSettings { 19 | return LongPollSettings(data.getString("server"), data.getString("key"), data.getString("mode"), data.getIntOrNull("wait")?: 10) 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandExtractorPrefixes.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | /** 4 | * @created 27.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | class CommandExtractorPrefixes(prefixChars: Collection?, maxCommandLength: Int = 150, allowNoPrefix: Boolean = true) 8 | : CommandExtractorBase(CommandExtractorStrings(prefixChars, allowNoPrefix), maxCommandLength) 9 | { 10 | 11 | class CommandExtractorStrings(private val prefixes: Collection? = null, private val allowNoPrefix: Boolean = true) : PrefixTester { 12 | override fun find(text: String): Int { 13 | return when { 14 | prefixes != null -> prefixes.find { text.startsWith(it) }?.length ?: -1 15 | allowNoPrefix -> 0 16 | else -> -1 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/SectionAbstract.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.Requester 5 | import iris.vk.api.VkRequestData 6 | 7 | /** 8 | * @created 28.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | abstract class SectionAbstract(protected open val api: Requester) { 12 | 13 | fun emptyListType(): ListType { 14 | return api.emptyOfListType() 15 | } 16 | 17 | internal inline fun request(method: String, options: Options?, token: String? = null): SingleType { 18 | return api.request(method, options, token) 19 | } 20 | 21 | internal inline fun execute(data: List, token: String? = null): ListType { 22 | return api.execute(data, token) 23 | } 24 | } -------------------------------------------------------------------------------- /src/iris/vk/VkException.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 08.09.2019 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class VkException(val message: String, val code: Int = 0, val requestParams: Any? = null) { 10 | 11 | companion object { 12 | fun create(error: Any?, options: Options? = null): VkException { 13 | if (error !is JsonItem) return VkException("", 0) 14 | val error = error["error"] 15 | val errorParams = if (options == null && !error["request_params"][0].isNull()) error["request_params"].asMap() else options 16 | return VkException(error["error_msg"].asString(), error["error_code"].asInt(), errorParams) 17 | } 18 | } 19 | 20 | override fun toString(): String { 21 | return "$message ($code)" 22 | } 23 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Board.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IBoard 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Board(api: Requester) : SectionAbstract(api), IBoard { 12 | override fun getComments(groupId: Int, topicId: Int, startCommentId: Int, options: Options?, token: String?): SingleType { 13 | val options = options?: Options() 14 | options["group_id"] = groupId 15 | options["topic_id"] = topicId 16 | options["start_comment_id"] = startCommentId 17 | if (!options.containsKey("count")) 18 | options["count"] = 1 19 | return request("board.getComments", options, token) 20 | } 21 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/AddressTester.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | /** 4 | * Проверяет подлинность источника входящего запроса 5 | * 6 | * [AddressTesterDefault] — реализует проверку входящего адреса на принадлежность указанным подсетям. По умолчанию 7 | * `95.142.192.0/21` и `2a00:bdc0::/32` 8 | * 9 | * @see AddressTesterDefault 10 | */ 11 | interface AddressTester { 12 | 13 | /** 14 | * Проверяет подлинность источника. 15 | */ 16 | fun isGoodHost(request: VkCallbackRequestHandler.Request): Boolean 17 | 18 | /** 19 | * Должен вернуть IP адрес реального источника. Если запрос происходит от источника через прокси,например, Cloudflare 20 | * или локальный проброс порта. 21 | * 22 | * Вызывается исключительно для логгирования неизвестных IP адресов 23 | */ 24 | fun getRealHost(request: VkCallbackRequestHandler.Request): String 25 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventProducer.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.* 5 | 6 | interface VkEventProducer { 7 | fun message(obj: JsonItem, sourcePeerId: Int): Message 8 | fun messageWithoutChatInfo(obj: JsonItem, sourcePeerId: Int): Message 9 | fun invite(obj: JsonItem, sourcePeerId: Int): ChatEvent 10 | fun leave(obj: JsonItem, sourcePeerId: Int): ChatEvent 11 | fun titleUpdate(obj: JsonItem, sourcePeerId: Int): TitleUpdate 12 | fun pin(obj: JsonItem, sourcePeerId: Int): PinUpdate 13 | fun unpin(obj: JsonItem, sourcePeerId: Int): PinUpdate 14 | fun screenshot(obj: JsonItem, sourcePeerId: Int): ChatEvent 15 | fun callback(obj: JsonItem, sourcePeerId: Int): CallbackEvent 16 | fun otherEvent(obj: JsonItem, sourcePeerId: Int): OtherEvent 17 | } 18 | 19 | interface VkEventProducerFactory { 20 | fun producer(): VkEventProducer 21 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventFilter.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package iris.vk 4 | 5 | import iris.vk.event.* 6 | 7 | /** 8 | * @created 20.09.2019 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | interface VkEventFilter { 12 | fun filterInvites(invites: List): List 13 | fun filterLeaves(leaves: List): List 14 | fun filterMessages(messages: List): List 15 | fun filterTitleUpdates(updaters: List): List 16 | fun filterPinUpdates(updaters: List): List 17 | fun filterUnpinUpdates(updaters: List): List 18 | fun filterCallbacks(callbacks: List): List 19 | fun filterScreenshots(screenshots: List): List 20 | fun filterOthers(others: List): List 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/iris/vk/api/common/Utils.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IUtils 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Utils(api: Requester) : SectionAbstract(api), IUtils { 12 | 13 | override fun checkLink(url: String): SingleType { 14 | return request("utils.checkLink", Options("url" to url)) 15 | } 16 | 17 | override fun getServerTime(token: String?): SingleType { 18 | return request("utils.getServerTime", null, token) 19 | } 20 | 21 | override fun getShortLink(url: String, isPrivate: Boolean, token: String?): SingleType { 22 | val options = Options("url" to url, "private" to if (isPrivate) "1" else "0") 23 | return request("utils.getShortLink", options, token) 24 | } 25 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupMessage.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | import iris.json.JsonObject 6 | import iris.vk.event.Message 7 | import kotlin.LazyThreadSafetyMode.NONE 8 | 9 | /** 10 | * @created 27.09.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | open class GroupMessage(source: JsonItem, sourcePeerId: Int) : GroupChatEvent(source, sourcePeerId), Message { 14 | 15 | override val text by lazy(NONE) { message["text"].asStringOrNull()?.replace("\r", "") } 16 | override val attachments: List? by lazy(NONE) { val res = message["attachments"]; (if (res.isNull()) null else res as JsonArray)?.getList() } 17 | override val forwardedMessages: List? by lazy(NONE) { (message["fwd_messages"] as? JsonArray)?.getList() } 18 | override val replyMessage: JsonObject? by lazy(NONE) { message["reply_message"] as? JsonObject } 19 | 20 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackGroup.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.vk.VkUpdateProcessor 4 | 5 | /** 6 | * @created 02.11.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class VkCallbackGroup( 10 | private val server: VkCallbackRequestServer, 11 | private val buffer: VkCallbackReadWriteBuffer, 12 | private val updateProcessor: VkUpdateProcessor 13 | ) : Runnable { 14 | 15 | private var working = true 16 | 17 | override fun run() { 18 | server.start() 19 | working = true 20 | val thisThread = thread ?: Thread.currentThread() 21 | while (!thisThread.isInterrupted && working) { 22 | val items = buffer.retrieve() 23 | if (items.isEmpty()) continue 24 | updateProcessor.processUpdates(items) 25 | } 26 | } 27 | 28 | private var thread: Thread? = null 29 | 30 | fun startPolling() { 31 | thread = Thread(this).also { it.start() } 32 | } 33 | 34 | fun stop() { 35 | server.stop(5) 36 | working = false 37 | thread?.interrupt() 38 | } 39 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IWall.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface IWall { 10 | fun get(ownerId: Int, offset: Int = 0, count: Int = 100): SingleType 11 | 12 | fun delete(id: Int): SingleType 13 | 14 | fun deleteComment(ownerId: Int, commentId: Int, token: String? = null): SingleType 15 | 16 | fun reportComment(ownerId: Int, commentId: Int, reason: Int = 0, token: String? = null): SingleType 17 | 18 | fun post(ownerId: Int, message: String?, fromGroup: Boolean = false, options: Options? = null): SingleType 19 | 20 | fun getComments(ownerId: Int, postId: Int, offset: Int = 0, count: Int = 100): SingleType 21 | 22 | fun createComment(ownerId: Int, postId: Int, text: String?, options: Options? = null, token: String? = null): SingleType 23 | 24 | fun getReposts(ownerId: Int, postId: Int, offset: Int = 0, count: Int = 10, token: String? = null): SingleType 25 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/GroupbotSource.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | interface GroupbotSource { 4 | /** 5 | * Указывает, содержится ли информация о группе в URI или query запроса. 6 | * 7 | * Полезно фильтровать ложные запросы, не тратя ресурсы на извлечение информации из JSON 8 | */ 9 | fun isGetByRequest(): Boolean 10 | 11 | /** 12 | * Извлекает информацию о группе из запроса, содержащуюся в URI или query. 13 | * 14 | * Например, URI может содержать такую информацию `/callback/fa33a6`, где код `fa33a6` сопоставляется с 15 | * одной из имеющихся групп. 16 | * 17 | * Выполняется в случае `isGetByRequest() == true` 18 | */ 19 | fun getGroupbot(request: VkCallbackRequestHandler.Request): Groupbot? 20 | 21 | /** 22 | * Извлекает информацию о группе по её ID. 23 | * 24 | * Выполняется в случае `isGetByRequest() == false` 25 | */ 26 | fun getGroupbot(groupId: Int): Groupbot? 27 | 28 | class Groupbot(val id: Int, val confirmation: String, val secret: String?) 29 | 30 | 31 | } -------------------------------------------------------------------------------- /src/iris/vk/event/group/GroupChatEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.group 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.api.VkApis 5 | import iris.vk.event.ChatEvent 6 | import kotlin.LazyThreadSafetyMode.NONE 7 | 8 | /** 9 | * @created 28.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | open class GroupChatEvent(source: JsonItem, override val sourcePeerId: Int) : ChatEvent { 13 | override val source: JsonItem = source 14 | open val message by lazy(NONE) { source["message"] } 15 | override val id: Int by lazy(NONE) { message["id"].asInt() } 16 | override val fromId: Int by lazy(NONE) { message["from_id"].asInt() } 17 | override val chatId: Int by lazy(NONE) { VkApis.peer2ChatId(peerId) } 18 | override val userId: Int by lazy(NONE) { message["action"]["member_id"].asInt() } 19 | override val peerId: Int by lazy(NONE) { message["peer_id"].asInt() } 20 | override val conversationMessageId: Int by lazy(NONE) { message["conversation_message_id"].asInt() } 21 | override val date: Long by lazy(NONE) { message["date"].asLong() } 22 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandExtractorBase.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 04.11.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | open class CommandExtractorBase(private val prefixTester: PrefixTester, private val maxCommandLength: Int = 150) : CommandExtractor { 10 | 11 | interface PrefixTester { 12 | fun find(text: String): Int 13 | } 14 | 15 | override fun extractCommand(message: Message): String? { 16 | val text = message.text ?: return null 17 | if (text.isEmpty()) return null 18 | 19 | val offset = prefixTester.find(text) 20 | if (offset == -1) return null 21 | 22 | 23 | val ind = text.indexOf('\n') 24 | return when { 25 | ind == -1 -> if (text.length <= maxCommandLength) correctText(text, offset) else null 26 | ind <= maxCommandLength -> text.substring(offset, ind) 27 | else -> null 28 | }?.trim()?.toLowerCase() 29 | } 30 | 31 | private inline fun correctText(text: String, startIndex: Int): String { 32 | return if (startIndex == 0) text else text.substring(startIndex) 33 | } 34 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventFilterAdapter.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | open class VkEventFilterAdapter : VkEventFilter { 6 | override fun filterInvites(invites: List): List { 7 | return invites 8 | } 9 | 10 | override fun filterLeaves(leaves: List): List { 11 | return leaves 12 | } 13 | 14 | override fun filterMessages(messages: List): List { 15 | return messages 16 | } 17 | 18 | override fun filterTitleUpdates(updaters: List): List { 19 | return updaters 20 | } 21 | 22 | override fun filterCallbacks(callbacks: List): List { 23 | return callbacks 24 | } 25 | 26 | override fun filterScreenshots(screenshots: List): List { 27 | return screenshots 28 | } 29 | 30 | override fun filterPinUpdates(updaters: List): List { 31 | return updaters 32 | } 33 | 34 | override fun filterUnpinUpdates(updaters: List): List { 35 | return updaters 36 | } 37 | 38 | override fun filterOthers(others: List): List { 39 | return others 40 | } 41 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Friends.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IFriends 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Friends(api: Requester) : SectionAbstract(api), IFriends { 12 | override fun add(userId: Int): SingleType { 13 | return request("friends.add", Options("user_id" to userId)) 14 | } 15 | 16 | override fun getRequests(out: Int, count: Int, token: String?): SingleType { 17 | return request("friends.getRequests", Options("need_viewed" to 1, "count" to count, "out" to out), token) 18 | } 19 | 20 | override fun delete(id: Int, token: String?): SingleType { 21 | return request("friends.delete", Options("user_id" to id), token) 22 | } 23 | 24 | override fun get(amount: Int, token: String?): SingleType { 25 | return request("friends.get", Options("count" to amount), token) 26 | } 27 | 28 | override fun delete(userId: Int): SingleType { 29 | return request("friends.delete", Options("user_id" to userId)) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/iris/vk/VkEventHandlerAdapter.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | /** 6 | * @created 26.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | open class VkEventHandlerAdapter: VkEventHandler { 10 | 11 | open fun processMessage(message: Message) {} 12 | open fun processEditedMessage(message: Message) {} 13 | 14 | override fun processMessages(messages: List) { 15 | for (message in messages) 16 | processMessage(message) 17 | } 18 | 19 | override fun processEditedMessages(messages: List) { 20 | for (message in messages) 21 | processEditedMessage(message) 22 | } 23 | 24 | override fun processInvites(invites: List) {} 25 | 26 | override fun processTitleUpdates(updaters: List) {} 27 | 28 | override fun processPinUpdates(updaters: List) {} 29 | 30 | override fun processUnpinUpdates(updates: List) {} 31 | 32 | override fun processLeaves(leaves: List) {} 33 | 34 | override fun processCallbacks(callbacks: List) {} 35 | 36 | override fun processScreenshots(screenshots: List) {} 37 | 38 | override fun processOthers(others: List) {} 39 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IDocs.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IDocs { 8 | 9 | fun getMessagesUploadServer(peerId: Int, type: String? = null, token: String? = null): SingleType 10 | 11 | fun save(file: String, title: String? = null, tags: String? = null, token: String? = null): SingleType 12 | 13 | fun getWallUploadServer(groupId: Int, token: String? = null): SingleType 14 | 15 | fun add(ownerId: Int, docId: Int, accessKey: String? = null, token: String? = null): SingleType 16 | 17 | 18 | // Не VK API методы 19 | 20 | fun upload(filePath: String, peerId: Int, type: String? = null, title: String? = null, tags: String? = null, token: String? = null): SingleType 21 | 22 | fun upload(data: ByteArray, peerId: Int, type: String? = null, title: String? = null, tags: String? = null, token: String? = null): SingleType 23 | 24 | fun uploadWall(filePath: String, groupId: Int, title: String? = null, tags: String? = null, token: String? = null): SingleType 25 | 26 | fun uploadWall(data: ByteArray, groupId: Int, title: String? = null, tags: String? = null, token: String? = null): SingleType 27 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackReadWriteBufferDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.json.JsonItem 4 | import java.util.concurrent.ArrayBlockingQueue 5 | import java.util.logging.Logger 6 | 7 | /** 8 | * @created 02.11.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class VkCallbackReadWriteBufferDefault(queueSize: Int) : VkCallbackReadWriteBuffer { 12 | 13 | private val queue: ArrayBlockingQueue = ArrayBlockingQueue(queueSize) 14 | private val queueWait = Object() 15 | 16 | companion object { 17 | private val logger = Logger.getLogger("iris.vk") 18 | } 19 | 20 | override fun send(event: JsonItem) { 21 | synchronized(queueWait) { 22 | if (queue.offer(event)) 23 | queueWait.notify() 24 | else { 25 | logger.warning { "Callback API queue is full (${queue.size} elements). Clearing..." } 26 | queue.clear() 27 | } 28 | } 29 | } 30 | 31 | override fun retrieve(wait: Boolean): List { 32 | synchronized(queueWait) { 33 | do { 34 | if (queue.size != 0) { 35 | val res = queue.toList() 36 | queue.clear() 37 | return res 38 | } 39 | if (!wait) 40 | return emptyList() 41 | queueWait.wait() 42 | } while (true) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /test/iris/vk/test/MyLoggerConsole.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.logging.Formatter 5 | import java.util.logging.LogRecord 6 | import java.util.logging.StreamHandler 7 | 8 | /** 9 | * @created 27.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class MyLoggerConsole : StreamHandler(System.out, F()) { 13 | 14 | companion object { 15 | val dateFormat = SimpleDateFormat("YYYY-MM-dd HH:mm:ss") 16 | } 17 | 18 | override fun publish(record: LogRecord?) { 19 | super.publish(record) 20 | flush() 21 | } 22 | 23 | private class F : Formatter() { 24 | override fun format(record: LogRecord): String? { 25 | var source: String? 26 | if (record.sourceClassName != null) { 27 | source = record.sourceClassName 28 | if (record.sourceMethodName != null) { 29 | source += "::" + record.sourceMethodName 30 | } 31 | } else { 32 | source = record.loggerName 33 | } 34 | val message = formatMessage(record) 35 | val throwable = record.thrown?.stackTraceToString() 36 | 37 | return record.level.name + "\t" + dateFormat.format((record.instant.toEpochMilli())) + " $source\n-- $message" + (if (!throwable.isNullOrEmpty()) "\n" + throwable else "") + '\n' 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/iris/vk/VkPollingGroup.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.api.LongPollSettings 5 | import iris.vk.api.VK_API_VERSION 6 | import iris.vk.api.VkApis 7 | import iris.vk.api.simple.VkApi 8 | 9 | /** 10 | * @created 08.09.2019 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | open class VkPollingGroup(api: VkApi, updateProcessor: VkUpdateProcessor, groupId: Int = 0): VkPollingUser(api, updateProcessor) { 14 | 15 | constructor(token: String, messageHandler: VkEventHandler, version: String? = null) : this(VkApi(token, version?: VK_API_VERSION), VkUpdateProcessorGroupDefault(messageHandler)) 16 | 17 | private val groupId = 18 | if (groupId == 0) { 19 | val res = api.groups.getById(emptyList()) ?: throw IllegalStateException("Can't connect to vk.com") 20 | if (VkApis.isError(res)) { 21 | throw IllegalStateException(VkApis.errorString(res)) 22 | } 23 | res["response"][0]["id"].asInt() 24 | } else 25 | groupId 26 | 27 | override fun getLongPollServer(): JsonItem? { 28 | return vkApi.groups.getLongPollServer(groupId) 29 | } 30 | 31 | override fun getLongPollSettings(server: String, key: String, accessMode: String): LongPollSettings { 32 | return LongPollSettings(server, key, accessMode) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Docs.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IDocs 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | abstract class Docs(api: Requester) : SectionAbstract(api), IDocs { 12 | 13 | override fun getMessagesUploadServer(peerId: Int, type: String?, token: String?): SingleType { 14 | val options = Options("peer_id" to peerId) 15 | if (type != null) 16 | options["type"] =type 17 | return request("docs.getMessagesUploadServer", options, token) 18 | } 19 | 20 | override fun save(file: String, title: String?, tags: String?, token: String?): SingleType { 21 | return request("docs.save", Options("file" to file), token) 22 | } 23 | 24 | override fun getWallUploadServer(groupId: Int, token: String?): SingleType { 25 | return request("docs.getWallUploadServer", Options("group_id" to groupId), token) 26 | } 27 | 28 | 29 | override fun add(ownerId: Int, docId: Int, accessKey: String?, token: String?): SingleType { 30 | val params = Options("owner_id" to ownerId, "doc_id" to docId) 31 | if (accessKey != null) 32 | params["access_key"] = accessKey 33 | return request("docs.add", params, token) 34 | } 35 | } -------------------------------------------------------------------------------- /test/iris/vk/test/group_cb_listener.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.VkEventHandlerAdapter 4 | import iris.vk.api.simple.VkApi 5 | import iris.vk.callback.VkCallbackGroupBuilder 6 | import iris.vk.callback.GroupbotSource.Groupbot 7 | import iris.vk.callback.GroupSourceSimple 8 | import iris.vk.event.Message 9 | 10 | /** 11 | * @created 28.09.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | fun main() { 15 | TestUtil.init() 16 | val props = TestUtil.getProperties() 17 | 18 | val secret = props.getProperty("group.secret").ifBlank { null } 19 | val confirmation = props.getProperty("group.confirmation") 20 | val groupId = props.getProperty("group.id").toInt() 21 | val token = props.getProperty("group.token") 22 | 23 | val vk = VkApi(token) 24 | val messageHandler = object : VkEventHandlerAdapter() { 25 | override fun processMessage(message: Message) { 26 | println("Событие получено. Group ID: ${message.sourcePeerId} текст: ${message.text}") 27 | } 28 | } 29 | 30 | val groupCb = VkCallbackGroupBuilder.build { 31 | groupbotSource = GroupSourceSimple(Groupbot(groupId, confirmation, secret)) 32 | path = "/kotlin/callback" 33 | vkTimeVsLocalTimeDiff = vk.request("utils.getServerTime", null)!!["response"].asLong() * 1000L - System.currentTimeMillis() 34 | eventHandler = messageHandler 35 | } 36 | 37 | groupCb.run() 38 | } -------------------------------------------------------------------------------- /src/iris/vk/event/user/UserChatEvent.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.user 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.api.VkApis 5 | import iris.vk.event.ChatEvent 6 | 7 | /** 8 | * @created 28.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class UserChatEvent(private val fullItemSource: ApiSource, override val source: JsonItem, override val sourcePeerId: Int) : ChatEvent { 12 | 13 | interface ApiSource { 14 | fun getFullEvent(messageId: Int): JsonItem? 15 | } 16 | 17 | override val id: Int by lazy(LazyThreadSafetyMode.NONE) { source[1].asInt() } 18 | 19 | override val peerId: Int by lazy(LazyThreadSafetyMode.NONE) { source[3].asInt() } 20 | 21 | override val fromId: Int by lazy(LazyThreadSafetyMode.NONE) { 22 | if (chatId > 0) { 23 | source[7]["from"].asInt() 24 | } else 25 | peerId 26 | } 27 | 28 | override val userId: Int by lazy(LazyThreadSafetyMode.NONE) { source[7]["source_mid"].asIntOrNull()?: 0 } 29 | override val chatId: Int by lazy(LazyThreadSafetyMode.NONE) { VkApis.peer2ChatId(peerId) } 30 | 31 | override val date: Long by lazy(LazyThreadSafetyMode.NONE) { source[4].asLong() } 32 | 33 | override val conversationMessageId: Int by lazy(LazyThreadSafetyMode.NONE) { fullItem?.let { it["conversation_message_id"].asInt() }?: 0 } 34 | 35 | protected val fullItem: JsonItem? by lazy { fullItemSource.getFullEvent(id) } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /test/iris/vk/test/address_test.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.VkUpdateProcessor 5 | import iris.vk.callback.AddressTesterDefault 6 | import iris.vk.callback.VkCallbackGroupBuilder 7 | import iris.vk.callback.GroupbotSource.Groupbot 8 | import java.util.logging.Logger 9 | 10 | /** 11 | * @created 28.09.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | fun main() { 15 | TestUtil.init() 16 | val props = TestUtil.getProperties() 17 | val logger = Logger.getLogger("iris.vk") 18 | val secret = props.getProperty("group.secret") 19 | val confirmation = props.getProperty("group.confirmation") 20 | val groupId = props.getProperty("group.id").toInt() 21 | 22 | val addressTester = AddressTesterDefault( 23 | ipSubnets = arrayOf("95.142.192.0/21", "2a00:bdc0::/32") 24 | ) 25 | 26 | val updateProcessor = object : VkUpdateProcessor { 27 | override fun processUpdates(updates: List) { 28 | updates.forEach { 29 | println("Новое событие:" + it.obj()) 30 | } 31 | } 32 | } 33 | 34 | val groupCb = VkCallbackGroupBuilder.build { 35 | groupbot = Groupbot(groupId, confirmation, secret) 36 | this.addressTester = addressTester 37 | path = "/kotlin/callback" 38 | this.updateProcessor = updateProcessor 39 | } 40 | 41 | 42 | groupCb.startPolling() // Запускаем сервер. Открываем порт для входящих. Неблокирующий вызов */ 43 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventHandlerArray.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | /** 6 | * @created 22.03.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class VkEventHandlerArray(val list: Array) : VkEventHandler { 10 | 11 | override fun processMessages(messages: List) { 12 | for (l in list) { 13 | l.processMessages(messages) 14 | } 15 | } 16 | 17 | override fun processInvites(invites: List) { 18 | for (l in list) 19 | l.processInvites(invites) 20 | } 21 | 22 | override fun processTitleUpdates(updaters: List) { 23 | for (l in list) 24 | l.processTitleUpdates(updaters) 25 | } 26 | 27 | override fun processPinUpdates(updaters: List) { 28 | for (l in list) 29 | l.processPinUpdates(updaters) 30 | } 31 | 32 | override fun processUnpinUpdates(updates: List) { 33 | for (l in list) 34 | l.processUnpinUpdates(updates) 35 | } 36 | 37 | override fun processLeaves(leaves: List) { 38 | for (l in list) 39 | l.processLeaves(leaves) 40 | } 41 | 42 | override fun processEditedMessages(messages: List) { 43 | for (l in list) 44 | l.processEditedMessages(messages) 45 | } 46 | 47 | override fun processCallbacks(callbacks: List) { 48 | for (l in list) 49 | l.processCallbacks(callbacks) 50 | } 51 | 52 | override fun processScreenshots(screenshots: List) { 53 | for (l in list) 54 | l.processScreenshots(screenshots) 55 | } 56 | 57 | override fun processOthers(others: List) { 58 | for (l in list) 59 | l.processOthers(others) 60 | } 61 | } -------------------------------------------------------------------------------- /src/iris/vk/command/CommandMatcherRegex.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 27.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | open class CommandMatcherRegex(private val commandPattern: Regex, private val runCommand: CommandRegex, private val charHash: CharArray? = null): CommandMatcherWithHash { 10 | 11 | constructor(commandPattern: String, runCommand: CommandRegex, charHash: CharArray? = null) : this(Regex(commandPattern), runCommand, charHash) 12 | 13 | constructor(commandPattern: String, runCommand: (message: Message, params: List) -> Unit) : this(Regex(commandPattern), runCommand) 14 | 15 | constructor(commandPattern: Regex, runCommand: (message: Message, params: List) -> Unit) : this(commandPattern, object : CommandRegex { 16 | 17 | override fun run(message: Message) { 18 | run(message, emptyList()) 19 | } 20 | 21 | override fun run(message: Message, groupValues: List) { 22 | runCommand(message,groupValues) 23 | } 24 | }) 25 | 26 | override fun testAndExecute(command: String, message: Message): Boolean { 27 | val matcher = commandPattern.matchEntire(command)?: return false 28 | runCommand.run(message, matcher.groupValues) 29 | return false 30 | } 31 | 32 | override fun hashChars(): CharArray? { 33 | charHash?.run { return this } 34 | 35 | return commandPattern.pattern.firstOrNull()?.let { 36 | if (it.isLetterOrDigit()) 37 | charArrayOf(it) 38 | else 39 | null 40 | } 41 | } 42 | 43 | interface CommandRegex : Command { 44 | fun run(message: Message, groupValues: List) 45 | } 46 | } -------------------------------------------------------------------------------- /test/iris/vk/test/filter_events.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.* 4 | import iris.vk.api.simple.VkApi 5 | import iris.vk.command.CommandMatcherSimple 6 | import iris.vk.command.VkCommandHandler 7 | import iris.vk.event.Message 8 | import kotlin.system.exitProcess 9 | 10 | /** 11 | * @created 28.10.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | fun main() { 15 | TestUtil.init() 16 | val props = TestUtil.getProperties() 17 | val token = props.getProperty("group.token") 18 | val userToId = props.getProperty("userTo.id").toInt() 19 | // Создаём класс для отправки сообщений 20 | val vk = VkApi(token) 21 | 22 | // Определяем обработчик команд 23 | val commandsHandler = VkCommandHandler() 24 | 25 | commandsHandler += CommandMatcherSimple("пинг") { 26 | vk.messages.send(it.peerId, "ПОНГ!") 27 | } 28 | 29 | // Отфильтруем все сообщения, которые поступают только от конкретного пользователя 30 | val personalFilter = object : VkEventFilterAdapter() { 31 | override fun filterMessages(messages: List): List { 32 | return messages.filter { it.fromId == userToId } 33 | } 34 | } 35 | 36 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 37 | val listener = VkPollingGroup( 38 | token, 39 | VkEventFilterHandler( 40 | arrayOf(personalFilter), 41 | commandsHandler 42 | ) 43 | ) 44 | listener.startPolling() // Можно запустить неблокирующего слушателя 45 | listener.join() // Даст дождаться завершения работы слушателя 46 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 47 | 48 | exitProcess(0) 49 | } -------------------------------------------------------------------------------- /test/iris/vk/test/command_handler.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.api.future.VkApiPack 4 | import iris.vk.VkPollingGroup 5 | import iris.vk.command.* 6 | import kotlin.system.exitProcess 7 | 8 | /** 9 | * @created 27.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | fun main() { 13 | TestUtil.init() 14 | val props = TestUtil.getProperties() 15 | val token = props.getProperty("group.token") 16 | 17 | // Создаём класс для отправки сообщений 18 | val vk = VkApiPack(token) 19 | 20 | // Определяем обработчик команд 21 | val commandsHandler = VkCommandHandler("!.") 22 | 23 | commandsHandler += CommandMatcherSimple("пинг") { 24 | vk.messages.send(it.peerId, "ПОНГ!") 25 | } 26 | 27 | commandsHandler += CommandMatcherSimple("мой ид") { 28 | vk.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 29 | } 30 | 31 | commandsHandler += CommandMatcherRegex("""рандом (\d+) (\d+)""") { vkMessage, params -> 32 | 33 | var first = params[1].toInt() 34 | var second = params[2].toInt() 35 | if (second < first) 36 | first = second.also { second = first } 37 | 38 | vk.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 39 | } 40 | 41 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 42 | val listener = VkPollingGroup(token, commandsHandler) 43 | listener.startPolling() // Можно запустить неблокирующего слушателя 44 | listener.join() // Даст дождаться завершения работы слушателя 45 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 46 | 47 | exitProcess(0) 48 | } -------------------------------------------------------------------------------- /test/iris/vk/test/group_cb_multibot.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.VkEventHandlerAdapter 4 | import iris.vk.callback.AddressTesterDefault 5 | import iris.vk.callback.GroupbotSource.Groupbot 6 | import iris.vk.VkGroupSourceList 7 | import iris.vk.api.future.VkApiPack 8 | import iris.vk.callback.VkCallbackGroupBuilder 9 | import iris.vk.event.Message 10 | 11 | /** 12 | * @created 29.09.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | fun main() { 16 | TestUtil.init() 17 | val props = TestUtil.getProperties() 18 | val secret = props.getProperty("group.secret").ifBlank { null } 19 | val confirmation = props.getProperty("group.confirmation") 20 | val groupId = props.getProperty("group.id").toInt() 21 | val token = props.getProperty("group.token") 22 | 23 | val groupSource = VkGroupSourceList(listOf( 24 | Groupbot(groupId, confirmation, secret)/*, 25 | Groupbot(111111, "41541541", null), 26 | Groupbot(111112, "41541541", null)*/ 27 | )) 28 | 29 | val api = VkApiPack(token) 30 | 31 | val messageHandler = object : VkEventHandlerAdapter() { 32 | override fun processMessage(message: Message) { 33 | println("Событие получено. Group ID: ${message.sourcePeerId} текст: ${message.text}") 34 | if (message.text == "пинг") 35 | api.messages.send(message.peerId, "ПОНГ!") 36 | } 37 | } 38 | 39 | val groupCb = VkCallbackGroupBuilder.build { 40 | this.groupbotSource = groupSource 41 | path = "/kotlin/callback" 42 | addressTester = AddressTesterDefault() 43 | vkTimeVsLocalTimeDiff = api.utils.getServerTime().get()!!["response"].asLong() * 1000L - System.currentTimeMillis() 44 | eventHandler = messageHandler 45 | } 46 | 47 | groupCb.run() 48 | } -------------------------------------------------------------------------------- /test/iris/vk/test/polling_user.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.* 4 | import iris.vk.api.future.VkApiPack 5 | import iris.vk.event.Message 6 | 7 | /** 8 | * @created 26.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | 12 | fun main() { 13 | TestUtil.init() 14 | val props = TestUtil.getProperties() 15 | val token = props.getProperty("user.token") 16 | 17 | // Создаём класс для отправки сообщений 18 | val vk = VkApiPack(token) 19 | 20 | // Определяем простой обработчик событий 21 | val simpleMessageHandler = object : VkEventHandlerAdapter() { 22 | 23 | override fun processMessage(message: Message) { 24 | 25 | // message.text — это метод, подготавливает текст для дальнейшей работы 26 | val text = message.text 27 | println("Получено сообщение[cid ${message.conversationMessageId}]: $text") 28 | 29 | if (text =="пинг") { 30 | println("Команда пинг получена") 31 | // Шлём ответ 32 | vk.messages.send(message.peerId, "ПОНГ") 33 | } 34 | val attachments = message.attachments 35 | if (!attachments.isNullOrEmpty()) 36 | println(" Attachment: ${attachments.joinToString { it.obj().toString() }}") 37 | val forwardedMessages = message.forwardedMessages 38 | if (!forwardedMessages.isNullOrEmpty()) 39 | println(" Forwarded: ${forwardedMessages.joinToString { it.obj().toString() }}") 40 | } 41 | } 42 | 43 | // Передаём в параметрах слушателя событий токен и созданный обработчик событий 44 | val listener = VkPollingUser(token, simpleMessageHandler) 45 | while (true) 46 | try { 47 | listener.run() // блокирует дальнейшее продвижение, пока не будет остановлено 48 | } catch (e: Throwable) { 49 | e.printStackTrace() 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /test/iris/vk/test/command_handler_dsl.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.api.future.VkApiPack 4 | import iris.vk.VkPollingGroup 5 | import iris.vk.command.* 6 | import kotlin.system.exitProcess 7 | 8 | /** 9 | * @created 27.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | fun main() { 13 | TestUtil.init() 14 | val props = TestUtil.getProperties() 15 | val token = props.getProperty("group.token") 16 | 17 | // Создаём класс для отправки сообщений 18 | val api = VkApiPack(token) 19 | 20 | // Определяем обработчик команд 21 | val commandsHandler = VkCommandHandler() 22 | 23 | // Конфигурирование команд в стиле DSL 24 | commandsHandler += commands { 25 | "пинг" runs { 26 | api.messages.send(it.peerId, "ПОНГ!") 27 | } 28 | 29 | "мой ид" runs { 30 | api.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 31 | } 32 | 33 | list("!dev", "!debug") runs { 34 | api.messages.send(it.peerId, "Debug is here!") 35 | } 36 | 37 | regex("""рандом (\d+) (\d+)""") runs { vkMessage, params -> 38 | 39 | var first = params[1].toInt() 40 | var second = params[2].toInt() 41 | if (second < first) 42 | first = second.also { second = first } 43 | 44 | api.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 45 | } 46 | } 47 | 48 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 49 | val listener = VkPollingGroup(token, commandsHandler) 50 | listener.startPolling() // Можно запустить неблокирующего слушателя 51 | listener.join() // Даст дождаться завершения работы слушателя 52 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 53 | 54 | exitProcess(0) 55 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IGroups.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface IGroups { 10 | 11 | fun leave(groupId: Int): SingleType 12 | 13 | fun get(userId: Int? = null, extended: Boolean = false, filter: String? = null, fields: String? = null, offset: Int = 0, count: Int = 0, token: String? = null): SingleType 14 | 15 | fun getById(ids: Collection, groupId: Int = 0, fields: String? = null, token: String? = null): SingleType 16 | 17 | fun getLongPollSettings(groupId: Int, token: String? = null): SingleType 18 | 19 | fun setLongPollSettings(groupId: Int, options: Options?, token: String? = null): SingleType 20 | 21 | fun getLongPollServer(groupId: Int = 0): SingleType 22 | 23 | fun getBanned(groupId: Int, offset: Int = 0, count: Int = 0, fields: String? = null, ownerId: Int = 0, token: String? = null): SingleType 24 | 25 | fun addCallbackServer(groupId: Int, url: String, title: String, secret: String): SingleType 26 | 27 | fun deleteCallbackServer(groupId: Int, serverId: Int): SingleType 28 | 29 | fun getCallbackConfirmationCode(groupId: Int): SingleType 30 | 31 | fun getCallbackSettings(groupId: Int): SingleType 32 | 33 | fun getMembers(groupId: Int, filter: String? = null, offset: Int = 0, count: Int = 0, token: String? = null): SingleType 34 | 35 | fun setCallbackSettings(groupId: Int, serverId: Int, options: Options? = null): SingleType 36 | 37 | fun getCallbackServers(groupId: Int, serverIds: Collection? = null): SingleType 38 | 39 | fun isMember(groupId: Int, usersId: Collection, extended: Boolean = false, token: String? = null): SingleType 40 | 41 | fun isMember(groupId: Int, userId: Int, extended: Boolean = false, token: String? = null): SingleType 42 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventHandlerList.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | /** 6 | * @created 08.02.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class VkEventHandlerList(val list: List) : VkEventHandler { 10 | 11 | operator fun plusAssign(handler: VkEventHandler) { 12 | add(handler) 13 | } 14 | 15 | fun add(handler: VkEventHandler) { 16 | (list as MutableList).add(handler) 17 | } 18 | 19 | override fun processMessages(messages: List) { 20 | for (l in list) { 21 | l.processMessages(messages) 22 | } 23 | } 24 | 25 | override fun processInvites(invites: List) { 26 | for (l in list) 27 | l.processInvites(invites) 28 | } 29 | 30 | override fun processTitleUpdates(updaters: List) { 31 | for (l in list) 32 | l.processTitleUpdates(updaters) 33 | } 34 | 35 | override fun processPinUpdates(updaters: List) { 36 | for (l in list) 37 | l.processPinUpdates(updaters) 38 | } 39 | 40 | override fun processUnpinUpdates(updates: List) { 41 | for (l in list) 42 | l.processUnpinUpdates(updates) 43 | } 44 | 45 | override fun processLeaves(leaves: List) { 46 | for (l in list) 47 | l.processLeaves(leaves) 48 | } 49 | 50 | override fun processEditedMessages(messages: List) { 51 | for (l in list) 52 | l.processEditedMessages(messages) 53 | } 54 | 55 | override fun processCallbacks(callbacks: List) { 56 | for (l in list) 57 | l.processCallbacks(callbacks) 58 | } 59 | 60 | override fun processScreenshots(screenshots: List) { 61 | for (l in list) 62 | l.processScreenshots(screenshots) 63 | } 64 | 65 | override fun processOthers(others: List) { 66 | for (l in list) 67 | l.processOthers(others) 68 | } 69 | } -------------------------------------------------------------------------------- /test/iris/vk/test/polling_group.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.api.future.VkApiPack 4 | import iris.vk.VkPollingGroup 5 | import iris.vk.VkEventHandlerAdapter 6 | import iris.vk.event.CallbackEvent 7 | import iris.vk.event.ChatEvent 8 | import iris.vk.event.Message 9 | import kotlin.system.exitProcess 10 | 11 | /** 12 | * @created 27.09.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | 16 | fun main() { 17 | TestUtil.init() 18 | val props = TestUtil.getProperties() 19 | val token = props.getProperty("group.token") 20 | 21 | 22 | 23 | // Определяем обработчик событий 24 | val simpleMessageHandler = object : VkEventHandlerAdapter() { 25 | 26 | private val vk = VkApiPack(token) 27 | 28 | override fun processMessage(message: Message) { 29 | val text = message.text 30 | println("Получено сообщение: $text") 31 | 32 | if (text =="пинг") { 33 | println("Команда пинг получена") 34 | 35 | // Шлём ответ 36 | vk.messages.send(message.peerId, "ПОНГ") 37 | } 38 | } 39 | 40 | override fun processCallbacks(callbacks: List) { 41 | for (callback in callbacks) { 42 | println("Получено callback-событие: ${callback.eventId} payload=${callback.payload}") 43 | } 44 | } 45 | 46 | override fun processScreenshots(screenshots: List) { 47 | for (screenshot in screenshots) { 48 | println("Получено screenshot-событие: ${screenshot.peerId} fromId=${screenshot.fromId}") 49 | } 50 | } 51 | } 52 | 53 | // Передаём в параметрах слушателя событий токен и созданный обработчик событий 54 | val listener = VkPollingGroup(token, simpleMessageHandler) 55 | listener.startPolling() // Можно запустить неблокирующего слушателя 56 | listener.join() // Даст дождаться завершения работы слушателя 57 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 58 | 59 | exitProcess(0) 60 | } -------------------------------------------------------------------------------- /test/iris/vk/test/send_pack.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.VkRequestData 5 | import iris.vk.api.future.VkApiPack 6 | import kotlin.system.exitProcess 7 | 8 | /** 9 | * @created 27.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | fun main() { 13 | TestUtil.init() 14 | val props = TestUtil.getProperties() 15 | val token = props.getProperty("group.token") 16 | val userToId = props.getProperty("userTo.id").toInt() 17 | 18 | val vk = VkApiPack(token) 19 | val futuresList = vk.messages.sendMulti(listOf( 20 | Options("peer_id" to userToId, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553"), 21 | Options("peer_id" to 2, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553"), 22 | ) 23 | ) 24 | println("Прошёл сюда без задержек") 25 | val secondFutures = vk.execute(listOf( 26 | VkRequestData("messages.send", Options("peer_id" to userToId, "random_id" to (0..2_000_000).random(), "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553")) 27 | , VkRequestData("messages.edit", Options("peer_id" to userToId, "conversation_message_id" to 1, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553")) 28 | )) 29 | 30 | println("И сюда тоже без задержек. Но вот ниже нужно подождать\n") 31 | println("Первый пакет:") 32 | for (it in futuresList.futures) 33 | println(it.get()?.obj()) 34 | 35 | println() 36 | println("Второй пакет скорее всего без задержек:") 37 | for (it in secondFutures.futures) 38 | println(it.get()?.obj()) 39 | println() 40 | println("Завершились") 41 | 42 | // У нас была создана фабрика потоков, поэтому так просто программа не завершится. Нужно принудительно 43 | exitProcess(0) 44 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IPhotos.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface IPhotos { 10 | 11 | fun saveMessagesPhotoByObject(responseImage: Options, token: String? = null): SingleType 12 | 13 | fun getMessagesUploadServer(peerId: Int, token: String? = null): SingleType 14 | 15 | fun getUploadServer(albumId: Int, groupId: Int? = null, token: String? = null): SingleType 16 | 17 | fun saveByObject(responseImage: Options, albumId: Int, groupId: Int? = null, caption: String? = null, options: Options? = null, token: String? = null): SingleType 18 | 19 | fun getWallUploadServer(userId: Int? = null, groupId: Int? = null, token: String? = null): SingleType 20 | 21 | fun saveWallPhotoByObject(responseImage: Options, userId: Int? = null, groupId: Int? = null, token: String? = null): SingleType 22 | 23 | fun copy(ownerId: Int, photoId: Int, accessKey: Int? = null, token: String? = null): SingleType 24 | 25 | 26 | // Не VK API методы 27 | 28 | fun uploadWallPhoto(photoPath: String, userId: Int? = null, groupId: Int? = null, token: String? = null): SingleType 29 | 30 | fun uploadWallPhoto(data: ByteArray, userId: Int? = null, groupId: Int? = null, type: String = "jpg", token: String? = null): SingleType 31 | 32 | fun uploadAlbumPhoto(photoPath: String, albumId: Int, groupId: Int? = null, caption: String? = null, options: Options? = null, token: String? = null): SingleType 33 | 34 | fun uploadAlbumPhoto(data: ByteArray, albumId: Int, groupId: Int? = null, type: String = "jpg", caption: String? = null, options: Options? = null, token: String? = null): SingleType 35 | 36 | fun uploadMessagePhoto(photoPath: String, peerId: Int = 0, token: String? = null): SingleType 37 | 38 | fun uploadMessagePhoto(data: ByteArray, peerId: Int = 0, type: String = "jpg", token: String? = null): SingleType 39 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackRequestServerDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import com.sun.net.httpserver.HttpHandler 5 | import com.sun.net.httpserver.HttpServer 6 | import java.net.InetSocketAddress 7 | import java.net.URI 8 | 9 | /** 10 | * Обёртка для HttpServer и обработки входящих запросов 11 | */ 12 | class VkCallbackRequestServerDefault(private val server: HttpServer): HttpHandler, VkCallbackRequestServer { 13 | 14 | private class DefaultRequest(private val request: HttpExchange) : VkCallbackRequestHandler.Request { 15 | 16 | override val requestUri: URI = request.requestURI 17 | 18 | override val remoteAddress: InetSocketAddress = request.remoteAddress 19 | 20 | override fun findHeader(key: String): String? { 21 | return request.requestHeaders.getFirst(key) 22 | } 23 | 24 | override fun writeResponse(response: String, code: Int) { 25 | writeResponsePrivate(request, response, code) 26 | } 27 | 28 | override fun body(): String { 29 | return request.requestBody.reader().use { it.readText() } 30 | } 31 | 32 | private fun writeResponsePrivate(request: HttpExchange, str: String, rCode: Int = 200) { 33 | val bytes = str.toByteArray() 34 | request.sendResponseHeaders(rCode, bytes.size.toLong()) 35 | request.responseBody.use { it.write(bytes) } 36 | request.close() 37 | } 38 | } 39 | 40 | private lateinit var handler: VkCallbackRequestHandler 41 | 42 | override fun setHandler(path: String, handler: VkCallbackRequestHandler) { 43 | this.handler = handler 44 | server.createContext(path, this) 45 | } 46 | 47 | override fun handle(exchange: HttpExchange) { 48 | handler.handle(DefaultRequest(exchange)) 49 | } 50 | 51 | override fun start() { 52 | try { 53 | server.start() 54 | } catch (e: IllegalStateException) { 55 | e.printStackTrace() 56 | } 57 | } 58 | 59 | override fun stop(seconds: Int) { 60 | server.stop(seconds) 61 | } 62 | } -------------------------------------------------------------------------------- /test/iris/vk/test/command_trigger_style_dsl.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.VkPollingGroup 4 | import iris.vk.VkTriggerEventHandler 5 | import iris.vk.api.future.VkApiPack 6 | import iris.vk.command.VkCommandHandler 7 | import iris.vk.command.commands 8 | import kotlin.system.exitProcess 9 | 10 | /** 11 | * @created 01.11.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | fun main() { 15 | TestUtil.init() 16 | val props = TestUtil.getProperties() 17 | val token = props.getProperty("group.token") 18 | 19 | // Создаём класс для отправки сообщений 20 | val vk = VkApiPack(token) 21 | 22 | 23 | 24 | // Определяем обработчик триггеров 25 | val triggerHandler = VkTriggerEventHandler { 26 | 27 | onMessage { 28 | for (message in it) 29 | println("Получено сообщение от ${message.peerId}: ${message.text}") 30 | } 31 | 32 | onMessageEdit { 33 | for (message in it) 34 | println("Сообщение исправлено ${message.id}: ${message.text}") 35 | } 36 | 37 | onMessage(VkCommandHandler( 38 | commands = commands { 39 | "пинг" runs { 40 | vk.messages.send(it.peerId, "ПОНГ!") 41 | } 42 | "мой ид" runs { 43 | vk.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 44 | } 45 | regex("""рандом (\d+) (\d+)""") runs { vkMessage, params -> 46 | 47 | var first = params[1].toInt() 48 | var second = params[2].toInt() 49 | if (second < first) 50 | first = second.also { second = first } 51 | 52 | vk.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 53 | } 54 | } 55 | )) 56 | } 57 | 58 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 59 | val listener = VkPollingGroup(token, triggerHandler) 60 | listener.startPolling() // Можно запустить неблокирующего слушателя 61 | listener.join() // Даст дождаться завершения работы слушателя 62 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 63 | 64 | exitProcess(0) 65 | } -------------------------------------------------------------------------------- /test/iris/vk/test/send_future.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.api.future.VkApiFuture 4 | import iris.vk.api.simple.VkApi 5 | 6 | /** 7 | * @created 27.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | 11 | 12 | val props = TestUtil.getProperties() 13 | val token = props.getProperty("group.token") 14 | val userToId = props.getProperty("userTo.id").toInt() 15 | 16 | fun main() { 17 | TestUtil.init() 18 | 19 | warmupFuture() // инициализация и прогрев 20 | warmupSimple() // инициализация и прогрев 21 | 22 | testFuture(5) // проверим 23 | Thread.sleep(5_000L) // чтобы не поймать флуд-контроль 24 | testSimple(5) 25 | } 26 | 27 | fun testFuture(repeats: Int) { 28 | val vk = VkApiFuture(token) 29 | val futures = ArrayList(repeats) 30 | val start = System.nanoTime() 31 | println("Запускаем шалости") 32 | for (i in 1..repeats) 33 | futures += vk.messages.send(userToId, "Извините, я поспамлю: $i из $repeats [VkApiFuture]") 34 | println("До этого места мы дошли без задержки на ожидание ответов.") 35 | println() 36 | println("А при получении результатов нужно подождать:") 37 | for (future in futures) { 38 | val result = future.get() 39 | println(result?.obj()) 40 | } 41 | val end = System.nanoTime() 42 | println("Выполнение $repeats запросов методом VkApiFuture заняло нам: ${(end - start)/1000_000} ms") 43 | } 44 | 45 | fun testSimple(repeats: Int) { 46 | val vk = VkApi(token) 47 | val start = System.nanoTime() 48 | println("Запускаем шалости") 49 | for (i in 1..repeats){ 50 | val result = vk.messages.send(userToId, "Извините, я поспамлю: $i из $repeats [VkApi]") 51 | println(result?.obj()) 52 | } 53 | println("Закончили водные процедуры") 54 | val end = System.nanoTime() 55 | println("Выполнение $repeats запросов методом VkApi заняло нам: ${(end - start)/1000_000} ms") 56 | } 57 | 58 | fun warmupFuture() { 59 | val vk = VkApiFuture(token) 60 | vk.messages.send(userToId, "Извините, я разогреваюсь [VkApiFuture]").get() 61 | } 62 | 63 | fun warmupSimple() { 64 | val vk = VkApi(token) 65 | vk.messages.send(userToId, "Извините, я разогреваюсь [VkApi]") 66 | } -------------------------------------------------------------------------------- /test/iris/vk/test/keyboard_demo.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.Options 4 | import iris.vk.VkKeyboard 5 | import iris.vk.api.simple.VkApi 6 | 7 | /** 8 | * @created 05.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | fun main() { 12 | TestUtil.init() 13 | val props = TestUtil.getProperties() 14 | val token = props.getProperty("group.token") 15 | val userToId = props.getProperty("userTo.id").toInt() 16 | 17 | run { 18 | val keyboard = VkKeyboard.createJson(arrayOf( 19 | arrayOf( 20 | VkKeyboard.text("Simple"), // простая кнопка без payload 21 | VkKeyboard.text("Yes", payload = "{\"command\": \"yes\"}", color = VkKeyboard.COLOR_POSITIVE), // payload тектом 22 | VkKeyboard.text("No", payload = VkKeyboard.cmd(command = "no"), color = VkKeyboard.COLOR_NEGATIVE) // payload через функцию 23 | ), 24 | arrayOf( 25 | VkKeyboard.textCommand(label = "Long button", command = "long"), // payload внутри метода textCommand 26 | VkKeyboard.text(label = "Long button", payload = Options("test" to "yes")) // произвольный payload 27 | ) 28 | ), inline = true /* default value true */) 29 | 30 | val vk = VkApi(token) 31 | val res = vk.messages.send(userToId, "Клавиатура на простых кнопках", options = Options("keyboard" to keyboard)) 32 | println(res?.obj()) 33 | } 34 | 35 | // Пример с callback кнопками 36 | run { 37 | val keyboard = with(VkKeyboard) { 38 | createJson(arrayOf( 39 | arrayOf( 40 | callback("Simple"), // простая кнопка без payload 41 | callback("Yes", payload = "{\"command\": \"yes\"}", color = COLOR_POSITIVE), // payload тектом 42 | callback("No", payload = cmd(command = "no"), color = COLOR_NEGATIVE) // payload через функцию 43 | ), 44 | arrayOf( 45 | callbackCommand(label = "Long button", command = "long"), // payload внутри метода textCommand 46 | callback(label = "Long button", payload = Options("test" to "yes")) // произвольный payload 47 | ) 48 | ), inline = true /* default value true */) 49 | } 50 | 51 | val vk = VkApi(token) 52 | val res = vk.messages.send(userToId, "Клавиатура на callback-кнопках", options = Options("keyboard" to keyboard)) 53 | println(res?.obj()) 54 | } 55 | } -------------------------------------------------------------------------------- /test/iris/vk/test/command_trigger_style.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.vk.VkPollingGroup 4 | import iris.vk.VkTriggerEventHandler 5 | import iris.vk.api.future.VkApiPack 6 | import iris.vk.command.CommandMatcherRegex 7 | import iris.vk.command.CommandMatcherSimple 8 | import iris.vk.command.VkCommandHandler 9 | import kotlin.system.exitProcess 10 | 11 | /** 12 | * @created 01.11.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | fun main() { 16 | TestUtil.init() 17 | val props = TestUtil.getProperties() 18 | val token = props.getProperty("group.token") 19 | 20 | // Создаём класс для отправки сообщений 21 | val vk = VkApiPack(token) 22 | 23 | // Определяем обработчик триггеров 24 | val triggerHandler = VkTriggerEventHandler() 25 | 26 | // можно настраивать лямбдами 27 | triggerHandler.onMessage { 28 | for (message in it) 29 | println("Получено сообщение от ${message.peerId}: ${message.text}") 30 | } 31 | 32 | triggerHandler.onMessageEdit { 33 | for (message in it) 34 | println("Сообщение исправлено ${message.id}: ${message.text}") 35 | } 36 | 37 | // можно настраивать классами триггеров 38 | triggerHandler += VkCommandHandler( 39 | commands = listOf( 40 | CommandMatcherSimple("пинг") { 41 | vk.messages.send(it.peerId, "ПОНГ!") 42 | }, 43 | 44 | CommandMatcherSimple("мой ид") { 45 | vk.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 46 | }, 47 | 48 | CommandMatcherRegex("""рандом (\d+) (\d+)""") { vkMessage, params -> 49 | 50 | var first = params[1].toInt() 51 | var second = params[2].toInt() 52 | if (second < first) 53 | first = second.also { second = first } 54 | 55 | vk.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 56 | } 57 | ) 58 | ) 59 | 60 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 61 | val listener = VkPollingGroup(token, triggerHandler) 62 | listener.startPolling() // Можно запустить неблокирующего слушателя 63 | listener.join() // Даст дождаться завершения работы слушателя 64 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 65 | 66 | exitProcess(0) 67 | } -------------------------------------------------------------------------------- /src/iris/vk/api/simple/VkApi.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.simple 2 | 3 | import iris.json.JsonItem 4 | import iris.json.flow.JsonFlowParser 5 | import iris.vk.Options 6 | import iris.vk.api.* 7 | import iris.vk.api.common.* 8 | import java.util.Collections.emptyList 9 | import kotlin.LazyThreadSafetyMode.NONE 10 | 11 | /** 12 | * @created 06.09.2019 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | 16 | open class VkApi(val token: String, val version: String = VK_API_VERSION, internal val connection: VkApiConnection = VkApiConnectionHttpClient()) : VkApiInterface>, Requester> { 17 | 18 | override val messages = Messages(this) 19 | override val friends by lazy(NONE) { Friends(this) } 20 | override val groups by lazy(NONE) { Groups(this) } 21 | override val users = Users(this) 22 | override val photos by lazy(NONE) { PhotosSimple(this) } 23 | override val docs by lazy(NONE) { DocsSimple(this) } 24 | override val wall by lazy(NONE) { Wall(this) } 25 | override val utils by lazy(NONE) { Utils(this) } 26 | 27 | ////////////////////////////////////// 28 | 29 | open fun request(req: VkRequestData): JsonItem? { 30 | return request(req.method, req.options, req.token) 31 | } 32 | 33 | override fun request(method: String, options: Options?, token: String?): JsonItem? { 34 | val token = token ?: this.token 35 | 36 | val sb = StringBuilder() 37 | if (options != null) 38 | VkApis.encodeOptions(options, sb) 39 | 40 | sb.append("access_token=").append(token).append("&v=").append(version) 41 | val res = connection.request(Method2UrlCache.getUrl(method), sb.toString())?.responseText ?: return null 42 | return parser(res) 43 | } 44 | 45 | override fun execute(data: List, token: String?): List { 46 | val codes = VkApis.generateExecuteCode(data, token?: this.token) 47 | val response = mutableListOf() 48 | for (i in codes) { 49 | val res = request(i.method, i.options, i.token)?: continue 50 | val data = VkApis.prepareExecuteResponses(res) 51 | response.addAll(data) 52 | } 53 | return response 54 | } 55 | 56 | fun parser(res: String): JsonItem { 57 | return JsonFlowParser.start(res) 58 | } 59 | 60 | override fun emptyOfListType(): List { 61 | return emptyList() 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/iris/vk/api/common/Wall.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IWall 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | open class Wall(api: Requester) : SectionAbstract(api), IWall { 12 | 13 | override fun get(ownerId: Int, offset: Int, count: Int): SingleType { 14 | return request("wall.get", Options("count" to count, "filter" to "all", "owner_id" to ownerId, "offset" to offset)) 15 | } 16 | 17 | override fun delete(id: Int): SingleType { 18 | return request("wall.delete", Options("post_id" to id)) 19 | } 20 | 21 | override fun deleteComment(ownerId: Int, commentId: Int, token: String?): SingleType { 22 | return request("wall.deleteComment", Options("owner_id" to ownerId, "comment_id" to commentId), token) 23 | } 24 | 25 | override fun reportComment(ownerId: Int, commentId: Int, reason: Int, token: String?): SingleType { 26 | return request("wall.reportComment", Options("owner_id" to ownerId, "comment_id" to commentId, "reason" to reason), token) 27 | } 28 | 29 | override fun post(ownerId: Int, message: String?, fromGroup: Boolean, options: Options?): SingleType { 30 | val params = options?: Options() 31 | params["owner_id"] = ownerId 32 | params["from_group"] = if (fromGroup) "1" else "0" 33 | if (message != null) 34 | params["message"] = message 35 | 36 | return request("wall.post", params) 37 | } 38 | 39 | override fun getComments(ownerId: Int, postId: Int, offset: Int, count: Int): SingleType { 40 | return request("wall.getComments", Options("owner_id" to ownerId, "post_id" to postId, "offset" to offset, "count" to count)) 41 | } 42 | 43 | override fun createComment(ownerId: Int, postId: Int, text: String?, options: Options?, token: String?): SingleType { 44 | val params = Options() 45 | if (options != null) 46 | params.putAll(options) 47 | params["owner_id"] = ownerId 48 | params["post_id"] =postId 49 | if (text != null) 50 | params["text"] = text 51 | return request("wall.createComment", params, token) 52 | } 53 | 54 | override fun getReposts(ownerId: Int, postId: Int, offset: Int, count: Int, token: String?): SingleType { 55 | val options = Options("owner_id" to ownerId, "post_id" to postId) 56 | if (offset != 0) 57 | options["offset"] = offset 58 | if (count != 0) 59 | options["count"] = count 60 | return request("wall.getReposts", options, token) 61 | } 62 | } -------------------------------------------------------------------------------- /src/iris/vk/api/constants.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | /** 4 | * @created 28.10.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | const val VK_API_VERSION = "5.124" 8 | 9 | const val VK_BOT_ERROR_UNKNOWN = 1 10 | const val VK_BOT_ERROR_APP_IS_OFF = 2 11 | const val VK_BOT_ERROR_UNKNOWN_METHOD = 3 12 | const val VK_BOT_ERROR_WRONG_TOKEN = 4 13 | const val VK_BOT_ERROR_AUTH_FAILED = 5 14 | const val VK_BOT_ERROR_TOO_MANY_REQUESTS = 6 15 | const val VK_BOT_ERROR_NO_RIGHTS_FOR_ACTION = 7 16 | const val VK_BOT_ERROR_WRONG_REQUEST = 8 17 | const val VK_BOT_ERROR_ONE_TYPE_ACTIONS = 9 18 | const val VK_BOT_ERROR_INTERNAL = 10 19 | const val VK_BOT_ERROR_TEST_MODE_APP_MUST_BE_OFF = 11 20 | const val VK_BOT_ERROR_CAPTCHA = 14 21 | const val VK_BOT_ERROR_ACCESS_DENIED = 15 22 | const val VK_BOT_ERROR_HTTPS_REQUIRED = 16 23 | const val VK_BOT_ERROR_VALIDATION_REQUIRED = 17 24 | const val VK_BOT_ERROR_PAGE_DELETED = 18 25 | const val VK_BOT_ERROR_ACTION_DENIED_FOR_STANDALONE = 20 26 | const val VK_BOT_ERROR_ACTION_ALLOWED_ONLY_FOR_STANDALONE = 21 27 | const val VK_BOT_ERROR_METHOD_IS_OFF = 23 28 | const val VK_BOT_ERROR_USER_CONFIRMATION_REQUIRED = 24 29 | const val VK_BOT_ERROR_GROUP_TOKEN_IS_INVALID = 27 30 | const val VK_BOT_ERROR_APP_TOKEN_IS_INVALID = 28 31 | const val VK_BOT_ERROR_DATA_REQUEST_LIMIT = 29 32 | const val VK_BOT_ERROR_PROFILE_IS_PRIVATE = 30 33 | const val VK_BOT_ERROR_ONE_OF_PARAMETERS_IS_WRONG = 100 34 | const val VK_BOT_ERROR_WRONG_APP_API = 101 35 | const val VK_BOT_ERROR_WRONG_USER_ID = 113 36 | const val VK_BOT_ERROR_WRONG_TIMESTAMP = 150 37 | const val VK_BOT_ERROR_USER_NOT_FOUND = 177 38 | const val VK_BOT_ERROR_ALBUM_ACCESS_DENIED = 200 39 | const val VK_BOT_ERROR_AUDIO_ACCESS_DENIED = 201 40 | const val VK_BOT_ERROR_GROUP_ACCESS_DENIED = 203 41 | const val VK_BOT_ERROR_ALBUM_IS_FULL = 300 42 | const val VK_BOT_ERROR_ACTION_IS_DENIED = 500 43 | const val VK_BOT_ERROR_NO_RIGHTS_FOR_ADV_CABINET = 600 44 | const val VK_BOT_ERROR_IN_ADV_CABINET = 603 45 | 46 | const val VK_BOT_ERROR_CANT_SEND_TO_USER_IN_BLACKLIST = 900 47 | const val VK_BOT_ERROR_CANT_SEND_WITHOUT_PERMISSION = 901 48 | const val VK_BOT_ERROR_CANT_SEND_TO_USER_PRIVACY_SETTINGS = 902 49 | const val VK_BOT_ERROR_KEYBOARD_FORMAT_IS_INVALID = 911 50 | const val VK_BOT_ERROR_THIS_IS_CHATBOT_FEATURE = 912 51 | const val VK_BOT_ERROR_TOO_MANY_FORWARDED_MESSAGES = 913 52 | const val VK_BOT_ERROR_MESSAGE_IS_TOO_LONG = 914 53 | const val VK_BOT_ERROR_NO_ACCESS_TO_THIS_CHAT = 917 54 | const val VK_BOT_ERROR_CANT_FORWARD_SELECTED_MESSAGES = 921 55 | const val VK_BOT_ERROR_CANT_DELETE_FOR_ALL_USERS = 924 56 | const val VK_BOT_ERROR_USER_NOT_FOUND_IN_CHAT = 935 57 | const val VK_BOT_ERROR_CONTACT_NOT_FOUND = 936 -------------------------------------------------------------------------------- /test/iris/vk/test/custom_event_producer.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.test 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.* 5 | import iris.vk.api.VkApis 6 | import iris.vk.api.simple.VkApi 7 | import iris.vk.command.CommandMatcherSimple 8 | import iris.vk.command.VkCommandHandler 9 | import iris.vk.event.* 10 | import iris.vk.event.group.GroupMessage 11 | import iris.vk.event.group.GroupMessageWithoutChatInfo 12 | import kotlin.system.exitProcess 13 | 14 | /** 15 | * @created 25.11.2020 16 | * @author [Ivan Ivanov](https://vk.com/irisism) 17 | */ 18 | 19 | fun main() { 20 | TestUtil.init() 21 | val props = TestUtil.getProperties() 22 | val token = props.getProperty("group.token") 23 | val userToId = props.getProperty("userTo.id").toInt() 24 | // Создаём класс для отправки сообщений 25 | val vk = VkApi(token) 26 | 27 | // Определяем обработчик команд 28 | val commandsHandler = VkCommandHandler() 29 | 30 | commandsHandler += CommandMatcherSimple("пинг") { 31 | vk.messages.send(it.peerId, "ПОНГ!") 32 | } 33 | 34 | // Отфильтруем все сообщения, которые поступают только от конкретного пользователя 35 | val personalFilter = object : VkEventFilterAdapter() { 36 | override fun filterMessages(messages: List): List { 37 | (messages as List).forEach { 38 | println("Message: [${it.conversationMessageId}] isChat = ${it.isChat()} | ${it.text}") 39 | } 40 | return messages 41 | } 42 | } 43 | 44 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 45 | val listener = VkPollingGroup( 46 | api = vk, 47 | updateProcessor = VkUpdateProcessorGroupDefault( 48 | VkEventFilterHandler( 49 | filters = arrayOf(personalFilter), 50 | handler = commandsHandler 51 | ), 52 | eventProducerFactory = GroupProcessorExt() 53 | ) 54 | ) 55 | listener.startPolling() // Можно запустить неблокирующего слушателя 56 | listener.join() // Даст дождаться завершения работы слушателя 57 | //listener.run() // Можно заблокировать дальнейшую работу потока, пока не будет остановлено 58 | 59 | exitProcess(0) 60 | } 61 | 62 | class GroupProcessorExt : VkUpdateProcessorGroupDefault.GroupEventProducerDefault() { 63 | 64 | class GroupMessageExt(source: JsonItem, sourcePeerId: Int) : GroupMessageWithoutChatInfo(source, sourcePeerId) { 65 | fun isChat(): Boolean = VkApis.isChat(peerId) 66 | fun isPavelDurov(): Boolean = fromId == 1 67 | } 68 | 69 | override fun message(obj: JsonItem, sourcePeerId: Int): Message { 70 | return GroupMessageExt(obj["message"], sourcePeerId) 71 | } 72 | 73 | override fun messageWithoutChatInfo(obj: JsonItem, sourcePeerId: Int): Message { 74 | return GroupMessageExt(obj["object"], sourcePeerId) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackGroupBuilder.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import com.sun.net.httpserver.HttpServer 4 | import iris.vk.VkEventHandler 5 | import iris.vk.VkUpdateProcessor 6 | import iris.vk.VkUpdateProcessorGroupDefault 7 | import java.net.InetSocketAddress 8 | import java.util.concurrent.Executor 9 | import java.util.concurrent.Executors 10 | 11 | /** 12 | * Главный класс для VK Callback API. Создаёт сервер VkCallbackGroup из переданных настроек, готовый к запуску 13 | * 14 | * @created 26.12.2020 15 | * @author [Ivan Ivanov](https://vk.com/irisism) 16 | */ 17 | 18 | @Suppress("MemberVisibilityCanBePrivate") 19 | class VkCallbackGroupBuilder { 20 | var server: VkCallbackRequestServer? = null 21 | var groupbotSource: GroupbotSource? = null 22 | var groupbot: GroupbotSource.Groupbot? = null 23 | var eventReadWriteBuffer: VkCallbackReadWriteBuffer? = null 24 | var updateProcessor: VkUpdateProcessor? = null 25 | var eventHandler: VkEventHandler? = null 26 | var requestHandler: VkCallbackRequestHandler? = null 27 | var path: String = "/callback" 28 | var port: Int = 80 29 | var requestsExecutor: Executor? = null 30 | var addressTester: AddressTester? = AddressTesterDefault() 31 | var expireEventTime: Long = 25_000L 32 | var vkTimeVsLocalTimeDiff: Long = 0L 33 | 34 | companion object { 35 | fun build(initializer: VkCallbackGroupBuilder.() -> Unit): VkCallbackGroup { 36 | return VkCallbackGroupBuilder().apply(initializer).buildGroupCallback() 37 | } 38 | } 39 | 40 | fun buildGroupCallback() : VkCallbackGroup { 41 | val updateProcessor = this.updateProcessor ?: VkUpdateProcessorGroupDefault(this.eventHandler ?: throw IllegalStateException("Event processor is not set")) 42 | val server = server ?: initDefaultServer(port, requestsExecutor?: Executors.newFixedThreadPool(4)) 43 | 44 | 45 | val buffer = eventReadWriteBuffer?: let { 46 | 47 | VkCallbackReadWriteBufferDefault(1000) 48 | } 49 | 50 | val requestHandler = this.requestHandler 51 | if (requestHandler != null) { 52 | server.setHandler(path, requestHandler) 53 | } else { 54 | server.setHandler(path, VkCallbackRequestHandlerDefault( 55 | groupbotSource ?: GroupSourceSimple(groupbot ?: throw IllegalStateException("Neither groupbot nor groupbotSource were not set")) 56 | , buffer 57 | , addressTester 58 | , expireEventTime, vkTimeVsLocalTimeDiff 59 | )) 60 | } 61 | 62 | return VkCallbackGroup(server, buffer, updateProcessor) 63 | } 64 | 65 | private fun initDefaultServer(port: Int, requestsExecutor: Executor): VkCallbackRequestServer { 66 | val server = HttpServer.create() 67 | server.bind(InetSocketAddress(port), 0) 68 | server.executor = requestsExecutor 69 | return VkCallbackRequestServerDefault(server) 70 | } 71 | } -------------------------------------------------------------------------------- /src/iris/vk/api/simple/DocsSimple.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.simple 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.Options 5 | import iris.vk.api.VkApis 6 | import iris.vk.api.common.Docs 7 | 8 | /** 9 | * @created 29.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class DocsSimple(override val api: VkApi) : Docs>(api) { 13 | override fun upload(filePath: String, peerId: Int, type: String?, title: String?, tags: String?, token: String?): JsonItem? { 14 | val uploadServerInfo = getMessagesUploadServer(peerId, type, token) 15 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 16 | return uploadServerInfo 17 | } 18 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("file" to filePath)))?.responseText 19 | ?: return null 20 | val responseFile = api.parser(resText) 21 | return save(responseFile["file"].asString(), title, tags, token) 22 | } 23 | 24 | override fun upload(data: ByteArray, peerId: Int, type: String?, title: String?, tags: String?, token: String?): JsonItem? { 25 | val uploadServerInfo = getMessagesUploadServer(peerId, type, token) 26 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 27 | return uploadServerInfo 28 | } 29 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("data" to data)))?.responseText 30 | ?: return null 31 | val responseFile = api.parser(resText) 32 | return save(responseFile["file"].asString(), title, tags, token) 33 | 34 | } 35 | 36 | override fun uploadWall(filePath: String, groupId: Int, title: String?, tags: String?, token: String?): JsonItem? { 37 | val uploadServerInfo = getWallUploadServer(groupId, token) 38 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 39 | return uploadServerInfo 40 | } 41 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("file" to filePath)))?.responseText 42 | ?: return null 43 | val responseFile = api.parser(resText) 44 | return save(responseFile["file"].asString(), title, tags, token) 45 | } 46 | 47 | override fun uploadWall(data: ByteArray, groupId: Int, title: String?, tags: String?, token: String?): JsonItem? { 48 | val uploadServerInfo = getWallUploadServer(groupId, token) 49 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 50 | return uploadServerInfo 51 | } 52 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("data" to data)))?.responseText 53 | ?: return null 54 | val responseFile = api.parser(resText) 55 | return save(responseFile["file"].asString(), title, tags, token) 56 | } 57 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Photos.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IPhotos 5 | import iris.vk.api.Requester 6 | 7 | /** 8 | * @created 29.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | abstract class Photos(api: Requester) : SectionAbstract(api), IPhotos { 12 | 13 | override fun saveMessagesPhotoByObject(responseImage: Options, token: String?): SingleType { 14 | return request("photos.saveMessagesPhoto", 15 | Options("photo" to responseImage["photo"], 16 | "server" to responseImage["server"], 17 | "hash" to responseImage["hash"] 18 | ), 19 | token 20 | ) 21 | } 22 | 23 | override fun getMessagesUploadServer(peerId: Int, token: String?): SingleType { 24 | return request("photos.getMessagesUploadServer", if (peerId != 0 ) Options("peer_id" to peerId) else null, token) 25 | } 26 | 27 | override fun getUploadServer(albumId: Int, groupId: Int?, token: String?): SingleType { 28 | val params = Options("album_id" to albumId) 29 | if (groupId != null) 30 | params["group_id"] = groupId 31 | return request("photos.getUploadServer", params, token) 32 | } 33 | 34 | override fun saveByObject(responseImage: Options, albumId: Int, groupId: Int?, caption: String?, options: Options?, token: String?): SingleType { 35 | val params = options ?: Options() 36 | params["album_id"] = albumId 37 | if (groupId != null) 38 | params["group_id"] = groupId 39 | if (caption != null) 40 | params["caption"] = caption 41 | params["photos_list"] = responseImage["photos_list"] 42 | params["server"] = responseImage["server"] 43 | params["hash"] = responseImage["hash"] 44 | 45 | return request("photos.save", params, token) 46 | } 47 | 48 | override fun getWallUploadServer(userId: Int?, groupId: Int?, token: String?): SingleType { 49 | val params = Options() 50 | if (userId != null) 51 | params["user_id"] = userId 52 | if (groupId != null) 53 | params["group_id"] = groupId 54 | return request("photos.getWallUploadServer", params, token) 55 | } 56 | 57 | override fun saveWallPhotoByObject(responseImage: Options, userId: Int?, groupId: Int?, token: String?): SingleType { 58 | return request( 59 | "photos.saveWallPhoto", 60 | Options("user_id" to userId, 61 | "group_id" to groupId, 62 | "photo" to responseImage["photo"], 63 | "server" to responseImage["server"], 64 | "hash" to responseImage["hash"] 65 | ), 66 | token 67 | ) 68 | } 69 | 70 | override fun copy(ownerId: Int, photoId: Int, accessKey: Int?, token: String?): SingleType { 71 | val params = Options("owner_id" to ownerId, "photo_id" to photoId) 72 | if (accessKey != null) 73 | params["access_key"] = accessKey 74 | return request("photos.copy", params, token) 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/iris/vk/command/commands_dsl.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.event.Message 4 | 5 | /** 6 | * @created 31.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | fun commands(initializer: DslCommandBuilder.() -> Unit): List { 10 | return DslCommandBuilder().apply(initializer).build() 11 | } 12 | 13 | class DslCommandBuilder { 14 | 15 | private val commands = mutableListOf() 16 | 17 | fun add(pattern: String, command: (message: Message) -> Unit) { 18 | commands += CommandMatcherSimple(pattern, command) 19 | } 20 | 21 | fun add(pattern: String, command: Command) { 22 | commands += CommandMatcherSimple(pattern, command) 23 | } 24 | 25 | fun add(pattern: Regex, command: (message: Message, params: List) -> Unit) { 26 | commands += CommandMatcherRegex(pattern, command) 27 | } 28 | 29 | fun add(pattern: Regex, command: CommandMatcherRegex.CommandRegex) { 30 | commands += CommandMatcherRegex(pattern, command) 31 | } 32 | 33 | fun add(command: CommandMatcherWithHash) { 34 | commands += command 35 | } 36 | 37 | fun addList(commandsList: List) { 38 | commands += commandsList 39 | } 40 | 41 | operator fun plusAssign(command: CommandMatcherWithHash) { 42 | commands += command 43 | } 44 | 45 | operator fun plusAssign(commandsList: List) { 46 | commands += commandsList 47 | } 48 | 49 | infix fun String.runs(command: (message: Message) -> Unit) { 50 | commands += CommandMatcherSimple(this, command) 51 | } 52 | 53 | infix fun String.runs(command: Command) { 54 | commands += CommandMatcherSimple(this, command) 55 | } 56 | 57 | infix fun Collection.runs(command: Command) { 58 | this.forEach { 59 | this@DslCommandBuilder.commands += CommandMatcherSimple(it, command) 60 | } 61 | } 62 | 63 | fun list(vararg commands: String): List { 64 | return commands.asList() 65 | } 66 | 67 | infix fun Collection.runs(command: (message: Message) -> Unit) { 68 | this.forEach { 69 | this@DslCommandBuilder.commands += CommandMatcherSimple(it, command) 70 | } 71 | } 72 | 73 | infix fun RegexBuilder.runs(command: (message: Message, params: List) -> Unit) { 74 | commands += CommandMatcherRegex(Regex(this.pattern), command) 75 | } 76 | 77 | infix fun RegexBuilder.runs(command: CommandMatcherRegex.CommandRegex) { 78 | commands += CommandMatcherRegex(Regex(this.pattern), command, this.hashChars) 79 | } 80 | 81 | fun regex(template: String, hashChars: String? = null) = regex(template, hashChars?.toCharArray()) 82 | 83 | fun regex(template: String, hashChars: CharArray?): RegexBuilder { 84 | return RegexBuilder(template, hashChars) 85 | } 86 | 87 | class RegexBuilder(val pattern: String, val hashChars: CharArray?) 88 | 89 | fun build(): List { 90 | return commands 91 | } 92 | } -------------------------------------------------------------------------------- /src/iris/vk/Options.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST", "unused") 2 | 3 | package iris.vk 4 | 5 | /** 6 | * @created 26.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class Options : HashMap { 10 | 11 | constructor() : super() 12 | 13 | constructor(vararg pairs: Pair): super(pairs.size) { 14 | putAll(pairs) 15 | } 16 | 17 | constructor(m: Map) : super(m) 18 | 19 | fun getString(key: String): String = this[key] as String 20 | fun getStringOrNull(key: String): String? = this[key] as? String 21 | fun getList(key: String): List { 22 | return when (val obj = this[key]) { 23 | is List<*> -> obj as List 24 | else -> (obj as Iterable).toList() 25 | } 26 | } 27 | 28 | fun getIntOrNull(key: String): Int? { 29 | return when (val it = this[key]) { 30 | is Int -> it 31 | is Number -> it.toInt() 32 | else -> null 33 | } 34 | } 35 | fun getInt(key: String): Int { 36 | return when (val it = this[key]) { 37 | is Int -> it 38 | else -> (it as Number).toInt() 39 | } 40 | } 41 | 42 | fun getLongOrNull(key: String): Long? { 43 | return when (val it = this[key]) { 44 | is Long -> it 45 | is Number -> it.toLong() 46 | else -> null 47 | } 48 | } 49 | fun getLong(key: String): Long { 50 | return when (val it = this[key]) { 51 | is Long -> it 52 | else -> (it as Number).toLong() 53 | } 54 | } 55 | 56 | fun getDoubleOrNull(key: String): Double? { 57 | return when (val it = this[key]) { 58 | is Double -> it 59 | is Number -> it.toDouble() 60 | else -> null 61 | } 62 | } 63 | fun getDouble(key: String): Double { 64 | return when (val it = this[key]) { 65 | is Double -> it 66 | else -> (it as Number).toDouble() 67 | } 68 | } 69 | 70 | fun getFloatOrNull(key: String): Float? { 71 | return when (val it = this[key]) { 72 | is Float -> it 73 | is Number -> it.toFloat() 74 | else -> null 75 | } 76 | } 77 | fun getFloat(key: String): Float { 78 | return when (val it = this[key]) { 79 | is Float -> it 80 | else -> (it as Number).toFloat() 81 | } 82 | } 83 | 84 | fun getByteOrNull(key: String): Byte? { 85 | return when (val it = this[key]) { 86 | is Byte -> it 87 | is Number -> it.toByte() 88 | else -> null 89 | } 90 | } 91 | fun getByte(key: String): Byte { 92 | return when (val it = this[key]) { 93 | is Byte -> it 94 | else -> (it as Number).toByte() 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/VkApiFuture.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.json.JsonItem 4 | import iris.json.flow.JsonFlowParser 5 | import iris.vk.Options 6 | import iris.vk.api.* 7 | import iris.vk.api.common.* 8 | import java.util.* 9 | import java.util.concurrent.CompletableFuture 10 | import kotlin.LazyThreadSafetyMode.NONE 11 | 12 | /** 13 | * @created 25.10.2019 14 | * @author [Ivan Ivanov](https://vk.com/irisism) 15 | */ 16 | open class VkApiFuture(val token: String, val version: String = VK_API_VERSION, connection: VkApiConnectionFuture? = null) : Requester { 17 | 18 | companion object { 19 | val defaultConnection = VkApiConnectionFutureHttpClient(VkApiConnectionFutureHttpClient.newClient()) 20 | } 21 | val connection = connection ?: defaultConnection 22 | 23 | open val messages by lazy(NONE) { Messages(this) } 24 | open val groups by lazy(NONE) { Groups(this) } 25 | open val users by lazy(NONE) { Users(this) } 26 | open val photos by lazy(NONE) { PhotosFuture(this) } 27 | open val docs by lazy(NONE) { DocsFuture(this) } 28 | open val utils by lazy(NONE) { Utils(this) } 29 | open val board by lazy(NONE) { Board(this) } 30 | open val wall by lazy(NONE) { Wall(this) } 31 | 32 | ////////////////////////////////////// 33 | 34 | open fun request(req: VkRequestData): VkFuture { 35 | return request(req.method, req.options, req.token) 36 | } 37 | 38 | override fun request(method: String, options: Options?, token: String?): VkFuture { 39 | val token = token ?: this.token 40 | 41 | val sb = StringBuilder() 42 | if (options != null) 43 | VkApis.encodeOptions(options, sb) 44 | 45 | sb.append("access_token=").append(token).append("&v=").append(version) 46 | 47 | val future = VkFuture() 48 | connection.request(Method2UrlCache.getUrl(method), sb.toString()).whenComplete { it, u -> 49 | future.complete(it?.let { parser(it.responseText) }) 50 | } 51 | return future 52 | } 53 | 54 | override fun execute(data: List, token: String?): VkFutureList { 55 | val codes = VkApis.generateExecuteCode(data, token?: this.token) 56 | val futures = codes.map { request(it.method, it.options, it.token) } 57 | return VkExecuteFuture(futures) 58 | } 59 | 60 | fun parser(res: String): JsonItem { 61 | return JsonFlowParser.start(res) 62 | } 63 | 64 | override fun emptyOfListType(): VkFutureList { 65 | return VkFutureList.empty 66 | } 67 | 68 | class VkFuture(val request: VkRequestData? = null) : CompletableFuture() 69 | 70 | open class VkFutureList(val futures: Collection) { 71 | companion object { 72 | val empty = VkFutureList(emptyList()) 73 | } 74 | open fun join(): List { 75 | val data = LinkedList() 76 | for (l in futures) 77 | data.add(l.get()) 78 | return data 79 | } 80 | } 81 | 82 | internal class VkExecuteFuture(futures: Collection) : VkFutureList(futures) { 83 | override fun join(): List { 84 | val data = super.join() 85 | val response = mutableListOf() 86 | for (res in data) { 87 | if (res == null) continue 88 | val data = VkApis.prepareExecuteResponses(res) 89 | response.addAll(data) 90 | } 91 | return response 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/iris/vk/VkEventFilterHandler.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NAME_SHADOWING", "unused") 2 | 3 | package iris.vk 4 | 5 | import iris.vk.event.* 6 | 7 | /** 8 | * @created 20.09.2019 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class VkEventFilterHandler(private val filters: Array, private val handler: VkEventHandler) : VkEventHandler { 12 | 13 | override fun processMessages(messages: List) { 14 | var messages = messages 15 | for (i in filters) { 16 | messages = i.filterMessages(messages) 17 | if (messages.isEmpty()) 18 | return 19 | } 20 | if (messages.isNotEmpty()) 21 | handler.processMessages(messages) 22 | } 23 | 24 | override fun processInvites(invites: List) { 25 | var invites = invites 26 | for (i in filters) { 27 | invites = i.filterInvites(invites) 28 | if (invites.isEmpty()) 29 | return 30 | } 31 | if (invites.isNotEmpty()) 32 | handler.processInvites(invites) 33 | } 34 | 35 | override fun processTitleUpdates(updaters: List) { 36 | var updaters = updaters 37 | for (i in filters) { 38 | updaters = i.filterTitleUpdates(updaters) 39 | if (updaters.isEmpty()) 40 | return 41 | } 42 | if (updaters.isNotEmpty()) 43 | handler.processTitleUpdates(updaters) 44 | } 45 | 46 | override fun processPinUpdates(updaters: List) { 47 | var updaters = updaters 48 | for (i in filters) { 49 | updaters = i.filterPinUpdates(updaters) 50 | if (updaters.isEmpty()) 51 | return 52 | } 53 | handler.processPinUpdates(updaters) 54 | } 55 | 56 | override fun processUnpinUpdates(updates: List) { 57 | var updaters = updates 58 | for (i in filters) { 59 | updaters = i.filterUnpinUpdates(updaters) 60 | if (updaters.isEmpty()) 61 | return 62 | } 63 | handler.processUnpinUpdates(updaters) 64 | } 65 | 66 | override fun processLeaves(leaves: List) { 67 | var leaves = leaves 68 | for (i in filters) { 69 | leaves = i.filterLeaves(leaves) 70 | if (leaves.isEmpty()) 71 | return 72 | } 73 | if (leaves.isNotEmpty()) 74 | handler.processLeaves(leaves) 75 | } 76 | 77 | override fun processEditedMessages(messages: List) { 78 | handler.processEditedMessages(messages) 79 | } 80 | 81 | override fun processCallbacks(callbacks: List) { 82 | var updaters = callbacks 83 | for (i in filters) { 84 | updaters = i.filterCallbacks(updaters) 85 | if (updaters.isEmpty()) 86 | return 87 | } 88 | if (updaters.isNotEmpty()) 89 | handler.processCallbacks(updaters) 90 | } 91 | 92 | override fun processScreenshots(screenshots: List) { 93 | var updaters = screenshots 94 | for (i in filters) { 95 | updaters = i.filterScreenshots(updaters) 96 | if (updaters.isEmpty()) 97 | return 98 | } 99 | if (updaters.isNotEmpty()) 100 | handler.processScreenshots(updaters) 101 | } 102 | 103 | override fun processOthers(others: List) { 104 | var items = others 105 | for (i in filters) { 106 | items = i.filterOthers(items) 107 | if (items.isEmpty()) 108 | return 109 | } 110 | if (items.isNotEmpty()) 111 | handler.processOthers(items) 112 | } 113 | } -------------------------------------------------------------------------------- /src/iris/vk/event/user/UserMessage.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.event.user 2 | 3 | import iris.json.* 4 | import iris.json.flow.JsonFlowParser 5 | import iris.json.plain.IrisJsonArray 6 | import iris.json.plain.IrisJsonObject 7 | import iris.json.proxy.JsonProxyString 8 | import iris.vk.event.Message 9 | import kotlin.LazyThreadSafetyMode.NONE 10 | 11 | /** 12 | * @created 27.09.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | open class UserMessage(fullItemSource: ApiSource, source: JsonItem, sourcePeerId: Int) : UserChatEvent(fullItemSource, source, sourcePeerId), Message { 16 | 17 | override val text by lazy(NONE) { source[6].asStringOrNull()?.replace("
", "\n") } 18 | 19 | override val attachments: List? by lazy(NONE) { 20 | val m = source 21 | if (m[7]["attach1_type"].asStringOrNull() == "wall") { 22 | return@lazy (fullItem?.get("attachments") as JsonArray?)?.getList() 23 | } 24 | 25 | if (m[7].isNotNull()) { 26 | val m7 = m[7] as JsonObject 27 | 28 | val attachmentsFull = m7["attachments"] 29 | val attachments = (if (attachmentsFull.isNull()) { 30 | val attachments = ArrayList>() 31 | for (el in m7) { 32 | val key = el.first 33 | if (!key.startsWith("attach")) continue 34 | val addValue = el.second 35 | val data = key.split("_") 36 | val num = data[0].substring("attach".length).toInt() - 1 37 | if (num + 1 > attachments.size) { 38 | extendCapacity(attachments as ArrayList, num + 1) 39 | attachments[num] = mutableMapOf() 40 | } 41 | val addKey = if (data.size > 1) data[1] else "id" 42 | attachments[num][addKey] = addValue 43 | } 44 | IrisJsonArray(attachments.map { IrisJsonObject(it.toList(), Configuration.globalConfiguration) }) 45 | } else { 46 | JsonFlowParser.start(attachmentsFull.asString()) as JsonArray 47 | }).getList() as Collection 48 | 49 | if (attachments.isNotEmpty()) { 50 | val resAttachments = ArrayList(attachments.size) 51 | for (a in attachments) { 52 | val entries = (a.getEntries() as MutableCollection) 53 | var type: String? = null 54 | if (!entries.removeIf { if (it.first == "type") {type = it.second.asString(); true} else false }) continue 55 | if (type != null) { 56 | resAttachments.add(IrisJsonObject(listOf("type" to JsonProxyString(type), type!! to a), Configuration.globalConfiguration)) 57 | } 58 | } 59 | return@lazy resAttachments 60 | } 61 | } 62 | 63 | null 64 | } 65 | 66 | private fun extendCapacity(array: ArrayList, newCapacity: Int) { 67 | if (newCapacity > array.size) { 68 | for (i in array.size until newCapacity) { 69 | array.add(null) 70 | } 71 | } 72 | } 73 | 74 | override val forwardedMessages: List? by lazy(NONE) { 75 | val m = source 76 | if (m[7]["reply"].isNotNull()) 77 | return@lazy null 78 | if (m[7]["fwd"].isNotNull()) { 79 | return@lazy (fullItem?.get("fwd_messages") as JsonArray?)?.getList() 80 | } 81 | null 82 | } 83 | 84 | override val replyMessage: JsonObject? by lazy { 85 | val m = source 86 | if (m[7]["reply"].isNotNull()) { 87 | val fullItem = fullItem ?: return@lazy null 88 | return@lazy (fullItem["reply_message"] as? JsonObject) 89 | } 90 | null 91 | } 92 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/DocsFuture.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.VkApis 5 | import iris.vk.api.common.Docs 6 | import iris.vk.api.future.VkApiFuture.VkFuture 7 | import iris.vk.api.future.VkApiFuture.VkFutureList 8 | 9 | /** 10 | * @created 29.10.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | class DocsFuture(override val api: VkApiFuture) : Docs(api) { 14 | override fun upload(filePath: String, peerId: Int, type: String?, title: String?, tags: String?, token: String?): VkFuture { 15 | val future = VkFuture() 16 | val t = getMessagesUploadServer(peerId, type, token).thenApply {uploadServerInfo -> 17 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 18 | return@thenApply uploadServerInfo 19 | } 20 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("file" to filePath))).get()?.responseText 21 | ?: return@thenApply null 22 | val responseFile = api.parser(resText) 23 | save(responseFile["file"].asString(), title, tags, token).get() 24 | }.whenComplete { it, err -> 25 | future.complete(it) 26 | } 27 | return future 28 | } 29 | 30 | override fun upload(data: ByteArray, peerId: Int, type: String?, title: String?, tags: String?, token: String?): VkFuture { 31 | val future = VkFuture() 32 | val t = getMessagesUploadServer(peerId, type, token).thenApply {uploadServerInfo -> 33 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 34 | return@thenApply uploadServerInfo 35 | } 36 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("data" to data))).get()?.responseText 37 | ?: return@thenApply null 38 | val responseFile = api.parser(resText) 39 | save(responseFile["file"].asString(), title, tags, token).get() 40 | }.whenComplete { it, err -> 41 | future.complete(it) 42 | } 43 | return future 44 | } 45 | 46 | override fun uploadWall(filePath: String, groupId: Int, title: String?, tags: String?, token: String?): VkFuture { 47 | val future = VkFuture() 48 | val t = getWallUploadServer(groupId, token).thenApply {uploadServerInfo -> 49 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 50 | return@thenApply uploadServerInfo 51 | } 52 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("file" to filePath))).get()?.responseText 53 | ?: return@thenApply null 54 | val responseFile = api.parser(resText) 55 | save(responseFile["file"].asString(), title, tags, token).get() 56 | }.whenComplete { it, err -> 57 | future.complete(it) 58 | } 59 | return future 60 | } 61 | 62 | override fun uploadWall(data: ByteArray, groupId: Int, title: String?, tags: String?, token: String?): VkFuture { 63 | val future = VkFuture() 64 | val t = getWallUploadServer(groupId, token).thenApply {uploadServerInfo -> 65 | if (uploadServerInfo == null || VkApis.isError(uploadServerInfo)) { 66 | return@thenApply uploadServerInfo 67 | } 68 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("file" to Options("data" to data))).get()?.responseText 69 | ?: return@thenApply null 70 | val responseFile = api.parser(resText) 71 | save(responseFile["file"].asString(), title, tags, token).get() 72 | }.whenComplete { it, err -> 73 | future.complete(it) 74 | } 75 | return future 76 | } 77 | } -------------------------------------------------------------------------------- /src/iris/vk/api/VkApis.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.json.Configuration 4 | import iris.json.JsonArray 5 | import iris.json.JsonEncoder 6 | import iris.json.JsonItem 7 | import iris.json.plain.IrisJsonArray 8 | import iris.json.plain.IrisJsonObject 9 | import iris.json.proxy.JsonProxyObject 10 | import iris.vk.Options 11 | import java.net.URLEncoder 12 | import java.nio.charset.StandardCharsets 13 | import kotlin.math.max 14 | 15 | /** 16 | * @created 28.10.2020 17 | * @author [Ivan Ivanov](https://vk.com/irisism) 18 | */ 19 | object VkApis { 20 | 21 | fun chat2PeerId(chatId: Int): Int { 22 | return 2000000000 + chatId 23 | } 24 | 25 | fun peer2ChatId(peerId: Int): Int { 26 | return max(peerId - 2000000000, 0) 27 | } 28 | 29 | fun isChat(peerId: Int): Boolean { 30 | return peerId >= 2000000000 31 | } 32 | 33 | fun isGroup(peerId: Int): Boolean { 34 | return peerId < 0 35 | } 36 | 37 | fun isUser(peerId: Int): Boolean { 38 | return peerId in 1..2000000000 39 | } 40 | 41 | fun group2PeerId(groupId: Int): Int { 42 | return -groupId 43 | } 44 | 45 | fun peer2GroupId(peerId: Int): Int { 46 | return -peerId 47 | } 48 | 49 | fun user2PeerId(id: Int): Int { 50 | return id 51 | } 52 | 53 | fun peerId2User(id: Int): Int { 54 | return id 55 | } 56 | 57 | fun isError(obj: JsonItem?): Boolean { 58 | return obj != null && !obj["error"].isNull() 59 | } 60 | 61 | fun errorString(obj: JsonItem?): String? { 62 | if (obj == null) return null 63 | val error = obj["error"].asMap() 64 | return "${error["error_msg"]} (${error["error_code"]})" 65 | } 66 | 67 | fun errorString(obj: Options?): String? { 68 | if (obj == null) return null 69 | val error = obj["error"] as Options? ?: return null 70 | return "${error["error_msg"]} (${error["error_code"]})" 71 | } 72 | 73 | private val emptyJsonArray = IrisJsonArray(emptyList()) 74 | 75 | fun prepareExecuteResponses(data: JsonItem): List { 76 | if (data["response"].isNull()) return emptyList() 77 | var numError = 0 78 | val result = mutableListOf() 79 | val executeErrors = if (data["execute_errors"].isNotNull()) data["execute_errors"] as JsonArray else emptyJsonArray 80 | for (i in data["response"].iterable()) { 81 | if (i.isPrimitive() && i.asBooleanOrNull() == false) { 82 | val errorInfo = executeErrors[numError] 83 | result.add(IrisJsonObject(listOf("error" to errorInfo), Configuration.globalConfiguration)) 84 | numError++ 85 | } else if (i.isArray()) { 86 | val items = i.asList() 87 | result.add(IrisJsonObject(listOf("response" to JsonProxyObject("count" to items.size, "items" to items)), Configuration.globalConfiguration)) 88 | } else { 89 | result.add(IrisJsonObject(listOf("response" to i), Configuration.globalConfiguration)) 90 | } 91 | } 92 | return result 93 | } 94 | 95 | fun generateExecuteCode(data: List, token: String): List { 96 | val sb = StringBuilder() 97 | val res = mutableListOf() 98 | 99 | for (i in data.indices) { 100 | val item = data[i] 101 | sb.append("API.").append(item.method).append('('); JsonEncoder.encode(item.options, sb); sb.append("),") 102 | if (i != 0 && i % 24 == 0) { 103 | val str = "return [" + sb.substring(0, sb.length - 1) + "];" 104 | res.add(VkRequestData("execute", Options("code" to str), token)) 105 | } 106 | } 107 | 108 | if (sb.isNotEmpty()) { 109 | val str = "return [" + sb.substring(0, sb.length - 1) + "];" 110 | res.add(VkRequestData("execute", Options("code" to str), token)) 111 | } 112 | return res 113 | } 114 | 115 | private fun encode(o: String): String? { 116 | return URLEncoder.encode(o, StandardCharsets.UTF_8) 117 | } 118 | 119 | fun encodeOptions(obj: Options, sb: StringBuilder = StringBuilder()): StringBuilder { 120 | for (o in obj.entries) { 121 | sb.append(encode(o.key)).append('=') 122 | .append(encode(o.value.toString())).append("&") 123 | } 124 | return sb 125 | } 126 | } -------------------------------------------------------------------------------- /src/iris/vk/api/simple/PhotosSimple.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.simple 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.Options 5 | import iris.vk.api.VkApis.isError 6 | import iris.vk.api.common.Photos 7 | 8 | /** 9 | * @created 29.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | open class PhotosSimple(override val api: VkApi) : Photos>(api) { 13 | 14 | override fun uploadWallPhoto(data: ByteArray, userId: Int?, groupId: Int?, type: String, token: String?): JsonItem? { 15 | val uploadServerInfo = getWallUploadServer(userId, groupId, token) 16 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 17 | return uploadServerInfo 18 | } 19 | 20 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type")))?.responseText 21 | ?: return null 22 | val responseImage = api.parser(resText).asMap() 23 | return saveWallPhotoByObject(Options(responseImage), userId, groupId, token) 24 | } 25 | 26 | override fun uploadWallPhoto(photoPath: String, userId: Int?, groupId: Int?, token: String?): JsonItem? { 27 | val uploadServerInfo = getWallUploadServer(userId, groupId, token) 28 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 29 | return uploadServerInfo 30 | } 31 | 32 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath)))?.responseText 33 | ?: return null 34 | val responseImage = api.parser(resText).asMap() 35 | return saveWallPhotoByObject(Options(responseImage), userId, groupId, token) 36 | } 37 | 38 | override fun uploadAlbumPhoto(photoPath: String, albumId: Int, groupId: Int?, caption: String?, options: Options?, token: String?): JsonItem? { 39 | val uploadServerInfo = getUploadServer(albumId, groupId, token) 40 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 41 | return uploadServerInfo 42 | } 43 | 44 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath)))?.responseText 45 | ?: return null 46 | val responseImage = api.parser(resText).asMap() 47 | return saveByObject(Options(responseImage), albumId, groupId, caption, options, token) 48 | 49 | } 50 | 51 | override fun uploadAlbumPhoto(data: ByteArray, albumId: Int, groupId: Int?, type: String, caption: String?, options: Options?, token: String?): JsonItem? { 52 | 53 | val uploadServerInfo = getUploadServer(albumId, groupId, token) 54 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 55 | return uploadServerInfo 56 | } 57 | 58 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type")))?.responseText 59 | ?: return null 60 | val responseImage = api.parser(resText).asMap() 61 | return saveByObject(Options(responseImage), albumId, groupId, caption, options, token) 62 | } 63 | 64 | override fun uploadMessagePhoto(photoPath: String, peerId: Int, token: String?): JsonItem? { 65 | val uploadServerInfo = getMessagesUploadServer(peerId, token) 66 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 67 | return uploadServerInfo 68 | } 69 | 70 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath)))?.responseText 71 | ?: return null 72 | val responseImage = api.parser(resText).asMap() 73 | return saveMessagesPhotoByObject(Options(responseImage), token) 74 | } 75 | 76 | override fun uploadMessagePhoto(data: ByteArray, peerId: Int, type: String, token: String?): JsonItem? { 77 | val uploadServerInfo = getMessagesUploadServer(peerId, token) 78 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 79 | return uploadServerInfo 80 | } 81 | 82 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type")))?.responseText 83 | ?: return null 84 | val responseImage = api.parser(resText).asMap() 85 | return saveMessagesPhotoByObject(Options(responseImage), token) 86 | } 87 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/VkApiPack.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.Options 5 | import iris.vk.api.VkApis 6 | import iris.vk.api.VkRequestData 7 | import java.util.concurrent.ArrayBlockingQueue 8 | import java.util.concurrent.Future 9 | import kotlin.math.min 10 | 11 | /** 12 | * @created 16.09.2019 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | open class VkApiPack(private val sender: Sender): VkApiFuture(sender.vkApi.token) { 16 | 17 | constructor(token: String, strictlySequential: Boolean = false, completeAsync: Boolean = true) : this(Sender(VkApiFuture(token), strictlySequential, completeAsync)) 18 | 19 | override fun request(method: String, options: Options?, token: String?): VkFuture { 20 | return request(VkRequestData(method, options, token?: this.token)) 21 | } 22 | 23 | override fun request(req: VkRequestData): VkFuture { 24 | return sender.execute(req) 25 | } 26 | 27 | override fun execute(data: List, token: String?): VkFutureList { 28 | return VkFutureList(sender.executeAll(data)) 29 | } 30 | 31 | class Sender(val vkApi: VkApiFuture, private val strictlySequential: Boolean = false, private val completeAsync: Boolean = true) { 32 | 33 | private val queue = ArrayBlockingQueue(3000) 34 | private val pillow = Object() 35 | private val ex = emptyArray() 36 | private val thread = object : Thread() { 37 | 38 | init { 39 | isDaemon = true 40 | } 41 | 42 | override fun run() { 43 | while (true) { 44 | val items: Array? = synchronized(pillow) { 45 | if (queue.isEmpty()) { 46 | pillow.wait() 47 | null 48 | } else { 49 | val items = queue.toArray(ex) 50 | queue.clear() 51 | items 52 | } 53 | } 54 | 55 | if (items != null) { 56 | this@Sender.processAll(items) 57 | } 58 | } 59 | } 60 | } 61 | 62 | init { 63 | thread.start() 64 | } 65 | 66 | fun execute(req: VkRequestData): VkFuture { 67 | val fut = VkFuture() 68 | synchronized(pillow) { 69 | queue.put(JobData(req, fut)) 70 | pillow.notify() 71 | } 72 | return fut 73 | } 74 | 75 | fun executeAll(data: List): List { 76 | val futRes = mutableListOf() 77 | synchronized(pillow) { 78 | for (it in data) { 79 | val fut = VkFuture(it) 80 | futRes.add(fut) 81 | queue.put(JobData(it, fut)) 82 | } 83 | pillow.notify() 84 | } 85 | return futRes 86 | } 87 | 88 | private class JobData(val request: VkRequestData, val future: VkFuture) 89 | 90 | private fun processAll(items: Array) { 91 | 92 | val tokenMap = mutableMapOf>() 93 | for (data in items) { 94 | val d = data.request 95 | val token = d.token ?: "" 96 | val list = tokenMap.getOrPut(token) { mutableListOf() } 97 | list += data 98 | } 99 | 100 | val localFutures = mutableListOf>() 101 | for ((token, data) in tokenMap) { 102 | val token = if (token.isEmpty()) vkApi.token else token 103 | val futures = mutableListOf() 104 | val requests = mutableListOf() 105 | for (it in data) { 106 | futures.add(it.future) 107 | requests.add(it.request) 108 | } 109 | val codes = VkApis.generateExecuteCode(requests, token) 110 | for (i in codes.indices) { 111 | val code = codes[i] 112 | val ind = i 113 | 114 | val func = { res: JsonItem?, error: Throwable? -> 115 | if (error != null) { 116 | error.printStackTrace() 117 | for (f in futures) 118 | if (!f.isDone) 119 | f.complete(null) 120 | } 121 | 122 | val offset = ind * 25 123 | if (res == null || VkApis.isError(res)) { 124 | for (i in offset until min(futures.size, offset + 25)) 125 | futures[i].complete(res) 126 | } else { 127 | val responses = VkApis.prepareExecuteResponses(res) 128 | for (i in responses.indices) { 129 | futures[i + offset].complete(responses[i]) 130 | } 131 | 132 | if (futures.size < responses.size + offset) { 133 | for (i in responses.size until futures.size) 134 | futures[i].complete(null) 135 | } else if (offset > 0) { 136 | } 137 | } 138 | } 139 | 140 | val f = vkApi.request(code).let { if (completeAsync) it.whenCompleteAsync(func) else it.whenComplete(func) } 141 | if (strictlySequential) 142 | f.get() 143 | localFutures.add(f) 144 | } 145 | } 146 | for (f in localFutures) 147 | f.get() 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/VkCallbackRequestHandlerDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.json.JsonItem 4 | import iris.json.flow.JsonFlowParser 5 | import iris.vk.callback.VkCallbackRequestHandler.Request 6 | import java.util.logging.Logger 7 | 8 | /** 9 | * @created 26.12.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class VkCallbackRequestHandlerDefault( 13 | private val gbSource: GroupbotSource, 14 | private var eventConsumer: VkCallbackEventConsumer, 15 | private val addressTester: AddressTester? = AddressTesterDefault(), 16 | expireEventTime: Long = 25_000L, 17 | vkTimeVsLocalTimeDiff: Long = 0L 18 | ) : VkCallbackRequestHandler { 19 | 20 | private val expireEventTime = expireEventTime - vkTimeVsLocalTimeDiff 21 | private var expired = 0 22 | private val exp = Any() 23 | 24 | companion object { 25 | var loggingExpired = true 26 | 27 | private val logger = Logger.getLogger("iris.vk") 28 | } 29 | 30 | private inline fun ok(request: Request) { 31 | request.writeResponse("ok", 200) 32 | } 33 | 34 | override fun handle(request: Request) { 35 | logger.finest {"Callback API event from " + request.remoteAddress } 36 | 37 | if (addressTester != null) { 38 | if (!addressTester.isGoodHost(request)) { 39 | logger.info { "Unknown host trying to send Callback API event: " + addressTester.getRealHost(request) } 40 | ok(request) 41 | return 42 | } 43 | } 44 | 45 | var groupbot = if (gbSource.isGetByRequest()) { 46 | val groupbot = gbSource.getGroupbot(request) 47 | if (groupbot == null) { 48 | logger.info { "Groupbot not found. " + request.requestUri } 49 | ok(request) 50 | return 51 | } 52 | groupbot 53 | } else 54 | null // значит информацию о группе возьмём позже из JSON ответа в поле group_id 55 | 56 | val body = request.body() 57 | 58 | try { 59 | if (body.isEmpty()) { 60 | logger.fine {"Body was empty" } 61 | ok(request) 62 | return 63 | } 64 | 65 | val event: JsonItem = JsonFlowParser.start(body) 66 | 67 | val groupId = event["group_id"].asInt() 68 | if (groupbot == null) { 69 | groupbot = gbSource.getGroupbot(groupId) 70 | if (groupbot == null) { 71 | logger.info { "Groupbot not found. " + request.requestUri } 72 | ok(request) 73 | return 74 | } 75 | 76 | } else { // groupbot был получен из информации запроса (URI/query), поэтому нужно дополнительно проверить, совпадают ли данные группы 77 | if (groupId != groupbot.id) { 78 | logger.info { "Group ID from code is not identical with response object: obj=" + groupId + " vs gb=" + groupbot.id } 79 | ok(request) 80 | return 81 | } 82 | } 83 | 84 | val type = event["type"].asString() 85 | if (type == "confirmation") { 86 | val res = groupbot.confirmation 87 | logger.finest {"Test confirmation. Group ID: $groupId" } 88 | request.writeResponse(res, 200) 89 | return 90 | } 91 | 92 | ok(request) 93 | val eventWriter = eventConsumer ?: return 94 | 95 | if (groupbot.secret != null && groupbot.secret != event["secret"].asStringOrNull()) { 96 | logger.info {"Secret is wrong: group id ${groupbot.id}" } 97 | return 98 | } 99 | 100 | var testDate = true 101 | val obj = try { 102 | when (type) { 103 | "message_new" -> { 104 | val obj = event["object"] 105 | obj["message"].let { 106 | when { 107 | it.isNotNull() -> it 108 | obj["text"].isNotNull() -> obj 109 | else -> return 110 | } 111 | } 112 | } 113 | "message_reply" -> event["object"] 114 | "message_edit" -> event["object"] 115 | "message_event" -> { 116 | testDate = false; event["object"] 117 | } 118 | else -> { 119 | logger.info { "Unknown event type $type" } 120 | return 121 | } 122 | } 123 | } catch (e: Exception) { 124 | logger.severe { e.stackTraceToString() } 125 | return 126 | } 127 | val suitsTime = if (testDate) { 128 | val date = obj["date"].asLong() 129 | val curTime = System.currentTimeMillis() 130 | date * 1000 > curTime - expireEventTime 131 | } else 132 | true 133 | 134 | if (suitsTime) { 135 | // отправляем событие 136 | eventWriter.send(event) 137 | 138 | if (loggingExpired) { 139 | synchronized(exp) { 140 | expired = 0 141 | } 142 | } 143 | } else { 144 | if (loggingExpired) { 145 | synchronized(exp) { 146 | expired++ 147 | } 148 | if (expired % 50 == 1) 149 | logger.info { "Expired $expired" } 150 | } 151 | } 152 | } catch (e: Exception) { 153 | logger.severe { e.stackTraceToString() } 154 | ok(request) 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /src/iris/vk/command/VkCommandHandler.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.command 2 | 3 | import iris.vk.VkEventHandlerAdapter 4 | import iris.vk.VkTriggerEventHandler 5 | import iris.vk.event.Message 6 | 7 | /** 8 | * @created 27.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | 12 | open class VkCommandHandler( 13 | private val commandExtractor: CommandExtractor = CommandExtractorChars(null), 14 | private val searchFirst: Boolean = true 15 | ) : VkEventHandlerAdapter(), VkTriggerEventHandler.TriggerMessage { 16 | 17 | constructor(commandExtractor: CommandExtractor = CommandExtractorChars(null), 18 | searchFirst: Boolean = true, commands: Iterable? = null) : this(commandExtractor, searchFirst) { 19 | if (commands != null) 20 | addAllWithHash(commands) 21 | } 22 | 23 | constructor(prefixes: String?, 24 | searchFirst: Boolean = true, commands: Iterable? = null) : this(CommandExtractorChars(prefixes), searchFirst) { 25 | if (commands != null) 26 | addAllWithHash(commands) 27 | } 28 | 29 | constructor(prefixes: Collection, 30 | searchFirst: Boolean = true, commands: Iterable? = null) : this(CommandExtractorPrefixes(prefixes), searchFirst) { 31 | if (commands != null) 32 | addAllWithHash(commands) 33 | } 34 | 35 | private val map = mutableMapOf>() 36 | 37 | operator fun plusAssign(command: CommandMatcher) { 38 | add(command, null) 39 | } 40 | 41 | operator fun plusAssign(command: CommandMatcherWithHash) { 42 | add(command, command.hashChars()) 43 | } 44 | 45 | open fun add(command: CommandMatcher): VkCommandHandler { 46 | return add(command, null) 47 | } 48 | 49 | open fun add(command: CommandMatcherWithHash): VkCommandHandler { 50 | return add(command, command.hashChars()) 51 | } 52 | 53 | operator fun plusAssign(text2Command: Pair) { 54 | val (pattern, command) = text2Command 55 | when(command) { 56 | is CommandMatcherRegex.CommandRegex -> add(CommandMatcherRegex(Regex(pattern), command)) 57 | else -> add(CommandMatcherSimple(pattern, command)) 58 | } 59 | } 60 | 61 | open fun add(pattern: String, command: Command): VkCommandHandler { 62 | return add(CommandMatcherSimple(pattern, command)) 63 | } 64 | 65 | open fun regex(pattern: String, command: CommandMatcherRegex.CommandRegex): VkCommandHandler { 66 | return add(CommandMatcherRegex(Regex(pattern), command)) 67 | } 68 | 69 | open fun regex(pattern: String, command: (vkMessage: Message, parameters: List) -> Unit): VkCommandHandler { 70 | return add(CommandMatcherRegex(pattern, command)) 71 | } 72 | 73 | open fun add(command: CommandMatcher, chars: CharArray?): VkCommandHandler { 74 | if (chars == null) 75 | map.getOrPut(NULL_CHAR) { mutableListOf() }.add(command) 76 | else 77 | for (char in chars) 78 | map.getOrPut(char) { mutableListOf() }.add(command) 79 | return this 80 | } 81 | 82 | fun addAll(commands: Iterable>): VkCommandHandler { 83 | for (it in commands) add(it.first, it.second) 84 | return this 85 | } 86 | 87 | fun addAllWithHash(commands: Iterable): VkCommandHandler { 88 | for (it in commands) add(it, it.hashChars()) 89 | return this 90 | } 91 | 92 | operator fun plusAssign(commands: Collection) { 93 | addAllWithHash(commands) 94 | } 95 | 96 | operator fun plusAssign(commands: Iterable>) { 97 | addAll(commands) 98 | } 99 | 100 | companion object { 101 | private const val NULL_CHAR = '\u0000' 102 | } 103 | 104 | override fun processMessage(message: Message) { 105 | val command = commandExtractor.extractCommand(message)?: return 106 | if (command.isEmpty()) return 107 | val hash = command[0] 108 | var hashItems = map[hash] 109 | if (hashItems != null) { 110 | for (c in hashItems) 111 | if (c.testAndExecute(command, message)) 112 | if (searchFirst) 113 | return 114 | } 115 | hashItems = map[NULL_CHAR] 116 | if (hashItems != null) { 117 | for (c in hashItems) 118 | if (c.testAndExecute(command, message)) 119 | if (searchFirst) 120 | return 121 | } 122 | } 123 | 124 | fun addCommands(vararg commands: CommandMatcher): VkCommandHandler { 125 | for (it in commands) 126 | when (it) { 127 | is CommandMatcherWithHash -> add(it) 128 | else -> add(it) 129 | } 130 | return this 131 | } 132 | 133 | fun addCommands(vararg commands: CommandMatcherWithHash): VkCommandHandler { 134 | for (it in commands) 135 | add(it) 136 | return this 137 | } 138 | 139 | override fun process(messages: List) = processMessages(messages) 140 | } -------------------------------------------------------------------------------- /src/iris/vk/api/common/Groups.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.common 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.IGroups 5 | import iris.vk.api.Requester 6 | 7 | open class Groups(api: Requester) : SectionAbstract(api), IGroups { 8 | 9 | override fun leave(groupId: Int): SingleType { 10 | return request("groups.leave", Options("group_id" to groupId)) 11 | } 12 | 13 | override fun get(userId: Int?, extended: Boolean, filter: String?, fields: String?, offset: Int, count: Int, token: String?): SingleType { 14 | val params = Options() 15 | if (userId != null) params["user_id"] = userId 16 | if (extended) params["extended"] = "1" 17 | if (filter != null) params["filter"] = filter 18 | if (fields != null) params["fields"] = fields 19 | if (offset != 0) params["offset"] = offset 20 | if (count != 0) params["count"] = count 21 | return request("groups.get", params, token) 22 | } 23 | 24 | override fun getById(ids: Collection, groupId: Int, fields: String?, token: String?): SingleType { 25 | val options = Options("group_ids" to ids.joinToString { it.toString() }) 26 | if (groupId != 0) options["group_id"] to groupId 27 | if (fields != null) options["fields"] to fields 28 | return request("groups.getById", options, token) 29 | } 30 | 31 | override fun getLongPollSettings(groupId: Int, token: String?): SingleType { 32 | return request("groups.getLongPollSettings", Options("group_id" to groupId), token) 33 | } 34 | 35 | override fun setLongPollSettings(groupId: Int, options: Options?, token: String?): SingleType { 36 | val options = options?: Options() 37 | options["group_id"] = groupId 38 | return request("groups.setLongPollSettings", options, token) 39 | } 40 | 41 | override fun getLongPollServer(groupId: Int): SingleType { 42 | return request("groups.getLongPollServer", Options("group_id" to groupId)) 43 | } 44 | 45 | override fun getBanned(groupId: Int, offset: Int, count: Int, fields: String?, ownerId: Int, token: String?): SingleType { 46 | val options = Options() 47 | options["group_id"] = groupId 48 | if (offset != 0) options["offset"] = offset 49 | if (count != 0) options["count"] = count 50 | if (fields != null) options["fields"] = fields 51 | if (ownerId != 0) options["owner_id"] = ownerId 52 | return request("groups.getBanned", options, token) 53 | } 54 | 55 | override fun addCallbackServer(groupId: Int, url: String, title: String, secret: String): SingleType { 56 | return request("groups.addCallbackServer", Options("group_id" to groupId, "url" to url, "title" to title, "secret_key" to secret)) 57 | } 58 | 59 | override fun deleteCallbackServer(groupId: Int, serverId: Int): SingleType { 60 | return request("groups.deleteCallbackServer", Options("group_id" to groupId, "server_id" to serverId)) 61 | } 62 | 63 | override fun getCallbackConfirmationCode(groupId: Int): SingleType { 64 | return request("groups.getCallbackConfirmationCode", Options("group_id" to groupId)) 65 | } 66 | 67 | override fun getMembers(groupId: Int, filter: String?, offset: Int, count: Int, token: String?): SingleType { 68 | val options = Options("group_id" to groupId) 69 | if (filter != null) options["filter"] = filter 70 | if (offset != 0) options["offset"] = offset 71 | if (count != 0) options["count"] = count 72 | 73 | return request("groups.getMembers", options, token) 74 | } 75 | 76 | override fun setCallbackSettings(groupId: Int, serverId: Int, options: Options?): SingleType { 77 | val params = Options("group_id" to groupId, "server_id" to serverId) 78 | if (options != null) 79 | params.putAll(options) 80 | return request("groups.setCallbackSettings", params) 81 | 82 | } 83 | 84 | override fun getCallbackSettings(groupId: Int): SingleType { 85 | return request("groups.getCallbackSettings", Options("group_id" to groupId)) 86 | } 87 | 88 | override fun getCallbackServers(groupId: Int, serverIds: Collection?): SingleType { 89 | val options = Options("group_id" to groupId) 90 | if (serverIds != null) 91 | options["server_ids"] = serverIds.joinToString(",") 92 | return request("groups.getCallbackServers", options) 93 | } 94 | 95 | override fun isMember(groupId: Int, usersId: Collection, extended: Boolean, token: String?): SingleType { 96 | val options = Options("user_ids" to usersId.joinToString(","), "group_id" to groupId) 97 | if (extended) options += "extended" to extended 98 | return request("groups.isMember", options, token) 99 | } 100 | 101 | override fun isMember(groupId: Int, userId: Int, extended: Boolean, token: String?): SingleType { 102 | val options = Options("user_id" to userId, "group_id" to groupId) 103 | if (extended) options += "extended" to extended 104 | return request("groups.isMember", options, token) 105 | } 106 | 107 | 108 | } -------------------------------------------------------------------------------- /src/iris/vk/api/simple/VkApiConnectionHttpClient.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.simple 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.simple.VkApiConnection.VkApiConnectResponse 5 | import java.io.File 6 | import java.io.IOException 7 | import java.net.URI 8 | import java.net.URLEncoder 9 | import java.net.http.HttpClient 10 | import java.net.http.HttpRequest 11 | import java.net.http.HttpResponse.BodyHandlers 12 | import java.nio.charset.StandardCharsets 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | import java.time.Duration 16 | 17 | /** 18 | * @created 22.10.2019 19 | * @author [Ivan Ivanov](https://vk.com/irisism) 20 | */ 21 | class VkApiConnectionHttpClient(client: HttpClient? = null) : VkApiConnection { 22 | 23 | private val client: HttpClient = client?: HttpClient.newBuilder() 24 | .version(HttpClient.Version.HTTP_1_1) 25 | .followRedirects(HttpClient.Redirect.NEVER) 26 | .connectTimeout(Duration.ofSeconds(5)) 27 | .build() 28 | 29 | override fun request(url: String, data: Map?): VkApiConnectResponse? { 30 | return request(url, encodeOptions(data)) 31 | } 32 | 33 | override fun request(url: String, data: String?): VkApiConnectResponse? { 34 | 35 | var builder = HttpRequest.newBuilder() 36 | .uri(URI.create(url)); 37 | if (data != null) 38 | builder = builder.POST(HttpRequest.BodyPublishers.ofString(data)) 39 | val request = builder.build() 40 | val tries = 3 41 | for (i in 1..tries) { 42 | try { 43 | val response = client.send(request, BodyHandlers.ofString()) 44 | return VkApiConnectResponse(response.statusCode(), response.body()) 45 | } catch (e: IOException) { 46 | e.printStackTrace() 47 | } 48 | } 49 | return null 50 | } 51 | 52 | override fun requestUpload(url: String, files: Map, data: Map?): VkApiConnectResponse? { 53 | val map = if (data == null) mutableMapOf() else if (data is MutableMap<*, *>) data as MutableMap else HashMap(data) 54 | map.putAll(files) 55 | return requestMultipart(url, map) 56 | } 57 | 58 | fun requestMultipart(url: String, data: Map?, headers: Map? = null): VkApiConnectResponse? { 59 | val map = data?: emptyMap() 60 | val boundary = "-------------573cf973d5228" 61 | val builder = HttpRequest.newBuilder() 62 | .uri(URI.create(url)) 63 | .POST(ofMimeMultipartData(map, boundary)) 64 | builder.header("Content-Type", "multipart/form-data; boundary=\"$boundary\"") 65 | headers?.forEach { k, value -> builder.header(k, value) } 66 | 67 | val request = builder.build() 68 | for (i in 1..3) { 69 | try { 70 | val response = client.send(request, BodyHandlers.ofString()) 71 | return VkApiConnectResponse(response.statusCode(), response.body()) 72 | } catch (e: IOException) { 73 | e.printStackTrace() 74 | } 75 | } 76 | return null 77 | } 78 | 79 | private fun encode(o: String): String? { 80 | return URLEncoder.encode(o, StandardCharsets.UTF_8) 81 | } 82 | 83 | private fun encodeOptions(obj: Map?): String? { 84 | if (obj == null) return "" 85 | val sb = StringBuilder() 86 | for (o in obj.entries) { 87 | sb.append(encode(o.key)).append('=') 88 | .append(encode(o.value.toString())).append("&") 89 | } 90 | return sb.toString() 91 | } 92 | 93 | private fun ofMimeMultipartData(dataItem: Map, boundary: String): HttpRequest.BodyPublisher { 94 | val byteArrays = ArrayList(); 95 | val separator = ("--$boundary\r\nContent-Disposition: form-data; name=").toByteArray(); 96 | 97 | for (entry in dataItem.entries) { 98 | byteArrays.add(separator); 99 | val value = entry.value 100 | if (value is Options) { 101 | if (value["file"] != null) { 102 | val path = if (value["file"] is File) Path.of((value["file"] as File).toURI()) else Path.of(value["file"] as String) 103 | val mimeType = if (value["Content-Type"] != null) value.getString("Content-Type") else Files.probeContentType(path) 104 | val filename = value["filename"] ?: path.fileName 105 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + filename + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()); 106 | byteArrays.add(Files.readAllBytes(path)) 107 | byteArrays.add("\r\n".toByteArray()) 108 | } else if (value["data"] is ByteArray) { 109 | val mimeType = if (value["Content-Type"] != null) value.getString("Content-Type") else "application/octet-stream" 110 | val filename = value.getStringOrNull("filename")?: "Untitled" 111 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + filename + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()); 112 | byteArrays.add(value["data"] as ByteArray) 113 | byteArrays.add("\r\n".toByteArray()) 114 | } else { 115 | throw IllegalArgumentException(value.toString()) 116 | } 117 | } else if (value is File || value is Path) { 118 | val path = if (value is File) Path.of(value.toURI()) else value as Path 119 | val mimeType = Files.probeContentType(path) 120 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + path.fileName + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()); 121 | byteArrays.add(Files.readAllBytes(path)) 122 | byteArrays.add("\r\n".toByteArray()); 123 | } else { 124 | byteArrays.add(("\"" + encode(entry.key) + "\"\r\n\r\n" + entry.value + "\r\n").toByteArray()); 125 | } 126 | } 127 | byteArrays.add(("--$boundary--").toByteArray()); 128 | return HttpRequest.BodyPublishers.ofByteArrays(byteArrays); 129 | } 130 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/PhotosFuture.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.json.plain.JsonPlainParser 4 | import iris.vk.Options 5 | import iris.vk.api.VkApis.isError 6 | import iris.vk.api.common.Photos 7 | import iris.vk.api.future.VkApiFuture.VkFuture 8 | import iris.vk.api.future.VkApiFuture.VkFutureList 9 | 10 | /** 11 | * @created 29.10.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | open class PhotosFuture(override val api: VkApiFuture) : Photos(api) { 15 | 16 | override fun uploadWallPhoto(data: ByteArray, userId: Int?, groupId: Int?, type: String, token: String?): VkFuture { 17 | val future = VkFuture() 18 | val t = getWallUploadServer(userId, groupId, token).thenApply {uploadServerInfo -> 19 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 20 | return@thenApply uploadServerInfo 21 | } 22 | 23 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type"))).get()?.responseText 24 | ?: return@thenApply null 25 | val responseImage = api.parser(resText).asMap() 26 | saveWallPhotoByObject(Options(responseImage), userId, groupId, token).get() 27 | }.whenComplete { it, err -> 28 | future.complete(it) 29 | } 30 | return future 31 | } 32 | 33 | override fun uploadWallPhoto(photoPath: String, userId: Int?, groupId: Int?, token: String?): VkFuture { 34 | val future = VkFuture() 35 | val t = getWallUploadServer(userId, groupId, token).thenApply {uploadServerInfo -> 36 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 37 | return@thenApply uploadServerInfo 38 | } 39 | 40 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath))).get()?.responseText 41 | ?: return@thenApply null 42 | val responseImage = api.parser(resText).asMap() 43 | saveWallPhotoByObject(Options(responseImage), userId, groupId, token).get() 44 | }.whenComplete { it, err -> 45 | future.complete(it) 46 | } 47 | return future 48 | } 49 | 50 | override fun uploadAlbumPhoto(photoPath: String, albumId: Int, groupId: Int?, caption: String?, options: Options?, token: String?): VkFuture { 51 | val future = VkFuture() 52 | val t = getUploadServer(albumId, groupId, token).thenApply {uploadServerInfo -> 53 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 54 | return@thenApply uploadServerInfo 55 | } 56 | 57 | val resText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath))).get()?.responseText 58 | ?: return@thenApply null 59 | val responseImage = api.parser(resText).asMap() 60 | saveByObject(Options(responseImage), albumId, groupId, caption, options, token).get() 61 | }.whenComplete { it, err -> 62 | future.complete(it) 63 | } 64 | return future 65 | } 66 | 67 | override fun uploadAlbumPhoto(data: ByteArray, albumId: Int, groupId: Int?, type: String, caption: String?, options: Options?, token: String?): VkFuture { 68 | val future = VkFuture() 69 | val t = getUploadServer(albumId, groupId, token).thenApply {uploadServerInfo -> 70 | if (uploadServerInfo == null || isError(uploadServerInfo)) { 71 | return@thenApply uploadServerInfo 72 | } 73 | 74 | val response = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type"))).get()?.responseText 75 | ?: return@thenApply null 76 | val responseImage = api.parser(response).asMap() 77 | saveByObject(Options(responseImage), albumId, groupId, caption, options, token).get() 78 | }.whenComplete { it, err -> 79 | future.complete(it) 80 | } 81 | return future 82 | } 83 | 84 | override fun uploadMessagePhoto(photoPath: String, peerId: Int, token: String?): VkFuture { 85 | val future = VkFuture() 86 | val t = getMessagesUploadServer(peerId, token).thenApply { uploadServerInfo -> 87 | if (uploadServerInfo == null || isError(uploadServerInfo)) 88 | return@thenApply uploadServerInfo 89 | val responseText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("file" to photoPath))).get() 90 | val response = responseText?.responseText ?: return@thenApply null 91 | val responseImage = JsonPlainParser.parse(response).asMap() 92 | saveMessagesPhotoByObject(Options(responseImage), token).get() 93 | }.whenComplete {t, e -> 94 | future.complete(t) 95 | } 96 | return future 97 | } 98 | 99 | override fun uploadMessagePhoto(data: ByteArray, peerId: Int, type: String, token: String?): VkFuture { 100 | val future = VkFuture() 101 | val t = getMessagesUploadServer(peerId, token).thenApply { uploadServerInfo -> 102 | if (uploadServerInfo == null || isError(uploadServerInfo)) 103 | return@thenApply uploadServerInfo 104 | val responseText = api.connection.requestUpload(uploadServerInfo["response"]["upload_url"].asString(), mapOf("photo" to Options("data" to data, "Content-Type" to "image/$type", "filename" to "item.$type"))).get() 105 | val response = responseText?.responseText ?: return@thenApply null 106 | val responseImage = JsonPlainParser.parse(response).asMap() 107 | saveMessagesPhotoByObject(Options(responseImage), token).get() 108 | }.whenComplete {t, e -> 109 | future.complete(t) 110 | } 111 | return future 112 | } 113 | } -------------------------------------------------------------------------------- /src/iris/vk/callback/AddressTesterDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.callback 2 | 3 | import iris.vk.callback.VkCallbackRequestHandler.Request 4 | import java.math.BigInteger 5 | import java.net.InetAddress 6 | 7 | 8 | /** 9 | * Проверяет подлинность источника входящего запроса 10 | * 11 | * @param ipSubnets Список доверенных подсетей. По умолчанию `95.142.192.0/21` и `2a00:bdc0::/32` 12 | * @param realIpHeader Если указан, извлекает информацию из заголовка запроса с указанным в `realIpHeader` названием. 13 | * Например, `X-Real-IP`, `CF-Connecting-IP` и подобные 14 | * @created 29.09.2020 15 | * @author [Ivan Ivanov](https://vk.com/irisism) 16 | */ 17 | class AddressTesterDefault( 18 | ipSubnets: Array = arrayOf("95.142.192.0/21", "2a00:bdc0::/32"), 19 | private val realIpHeader: String? = null 20 | ) : AddressTester { 21 | 22 | private val ipSubnets = ipSubnets.map { Subnet.getInstance(it) } 23 | 24 | override fun getRealHost(request: Request): String { 25 | return getRealHostInternal(request) ?: request.remoteAddress.address.hostAddress 26 | } 27 | 28 | override fun isGoodHost(request: Request): Boolean { 29 | val address = (if (realIpHeader == null) { 30 | request.remoteAddress.address 31 | } else { // нужно вытащить реальный IP адрес 32 | val host = getRealHostInternal(request) 33 | if (host == null) 34 | request.remoteAddress.address 35 | else 36 | InetAddress.getByName(host) 37 | }).address.let { BigInteger(it) } 38 | 39 | return ipSubnets.any { it.isInNet(address) } 40 | } 41 | 42 | private fun getRealHostInternal(request: Request): String? { 43 | if (realIpHeader == null) return null 44 | val fwd = request.findHeader(realIpHeader) 45 | return fwd ?: request.remoteAddress.address.hostAddress 46 | } 47 | 48 | /************************************/ 49 | private class Subnet { 50 | private val bytesSubnetCount: Int 51 | private val bigMask: BigInteger 52 | private val bigSubnetMasked: BigInteger 53 | 54 | /** For use via format "192.168.0.0/24" or "2001:db8:85a3:880:0:0:0:0/57" */ 55 | constructor(subnetAddress: InetAddress, bits: Int) { 56 | bytesSubnetCount = subnetAddress.address.size // 4 or 16 57 | bigMask = BigInteger.valueOf(-1).shiftLeft(bytesSubnetCount * 8 - bits) // mask = -1 << 32 - bits 58 | bigSubnetMasked = BigInteger(subnetAddress.address).and(bigMask) 59 | } 60 | 61 | /** For use via format "192.168.0.0/255.255.255.0" or single address */ 62 | constructor(subnetAddress: InetAddress, mask: InetAddress?) { 63 | bytesSubnetCount = subnetAddress.address.size 64 | bigMask = if (null == mask) BigInteger.valueOf(-1) else BigInteger(mask.address) // no mask given case is handled here. 65 | bigSubnetMasked = BigInteger(subnetAddress.address).and(bigMask) 66 | } 67 | 68 | fun isInNet(address: InetAddress): Boolean { 69 | val bytesAddress = address.address 70 | if (bytesSubnetCount != bytesAddress.size) return false 71 | val bigAddress = BigInteger(bytesAddress) 72 | return bigAddress.and(bigMask) == bigSubnetMasked 73 | } 74 | 75 | fun isInNet(bytesAddress: ByteArray): Boolean { 76 | if (bytesSubnetCount != bytesAddress.size) return false 77 | val bigAddress = BigInteger(bytesAddress) 78 | return bigAddress.and(bigMask) == bigSubnetMasked 79 | } 80 | 81 | fun isInNet(bigAddress: BigInteger): Boolean { 82 | return bigAddress.and(bigMask) == bigSubnetMasked 83 | } 84 | 85 | override fun hashCode(): Int { 86 | return bytesSubnetCount 87 | } 88 | 89 | override fun toString(): String { 90 | val buf = StringBuilder() 91 | bigInteger2IpString(buf, bigSubnetMasked, bytesSubnetCount) 92 | buf.append('/') 93 | bigInteger2IpString(buf, bigMask, bytesSubnetCount) 94 | return buf.toString() 95 | } 96 | 97 | override fun equals(other: Any?): Boolean { 98 | if (this === other) return true 99 | if (javaClass != other?.javaClass) return false 100 | other as Subnet 101 | 102 | if (bytesSubnetCount != other.bytesSubnetCount) return false 103 | if (bigMask != other.bigMask) return false 104 | if (bigSubnetMasked != other.bigSubnetMasked) return false 105 | 106 | return true 107 | } 108 | 109 | companion object { 110 | /** 111 | * Subnet factory method. 112 | * @param subnetMask format: "192.168.0.0/24" or "192.168.0.0/255.255.255.0" 113 | * or single address or "2001:db8:85a3:880:0:0:0:0/57" 114 | * @return a new instance 115 | * @throws UnknownHostException thrown if unsupported subnet mask. 116 | */ 117 | fun getInstance(subnetMask: String): Subnet { 118 | val stringArr = subnetMask.split("/").toTypedArray() 119 | return if (2 > stringArr.size) Subnet(InetAddress.getByName(stringArr[0]), null as InetAddress?) else if (stringArr[1].contains(".") || stringArr[1].contains(":")) Subnet(InetAddress.getByName(stringArr[0]), InetAddress.getByName(stringArr[1])) else Subnet(InetAddress.getByName(stringArr[0]), stringArr[1].toInt()) 120 | } 121 | 122 | private fun bigInteger2IpString(buf: StringBuilder, bigInteger: BigInteger, displayBytes: Int) { 123 | val isIPv4 = 4 == displayBytes 124 | val bytes = bigInteger.toByteArray() 125 | val diffLen = displayBytes - bytes.size 126 | val fillByte = if (0 > bytes[0].toInt()) 0xFF.toByte() else 0x00.toByte() 127 | var integer: Int 128 | for (i in 0 until displayBytes) { 129 | if (0 < i && !isIPv4 && i % 2 == 0) buf.append(':') else if (0 < i && isIPv4) buf.append('.') 130 | integer = 0xFF and (if (i < diffLen) fillByte else bytes[i - diffLen]).toInt() 131 | if (!isIPv4 && 0x10 > integer) buf.append('0') 132 | buf.append(if (isIPv4) integer else Integer.toHexString(integer)) 133 | } 134 | } 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/iris/vk/VkKeyboard.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package iris.vk 4 | 5 | import iris.json.JsonEncoder 6 | 7 | /** 8 | * @created 08.02.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | object VkKeyboard { 12 | 13 | const val COLOR_PRIMARY = "primary" 14 | const val COLOR_SECONDARY = "secondary" 15 | const val COLOR_NEGATIVE = "negative" 16 | const val COLOR_POSITIVE = "positive" 17 | 18 | const val CALLBACK_DEFAULT = 0 19 | const val CALLBACK_SILENT = 1 20 | const val CALLBACK_MANUAL_HANDLE = 2 21 | 22 | fun text(label: String, payload: Options, color: String = COLOR_PRIMARY): Options { 23 | return text(label, JsonEncoder.encode(payload), color) 24 | } 25 | 26 | fun text(label: String, payload: String = "", color: String = COLOR_PRIMARY): Options { 27 | return Options( 28 | "action" to Options( 29 | "type" to "text" 30 | , "label" to label 31 | , "payload" to payload 32 | ) 33 | , "color" to color 34 | ) 35 | } 36 | 37 | fun callback(label: String, payload: Options, color: String = COLOR_PRIMARY): Options { 38 | return callback(label, JsonEncoder.encode(payload), color) 39 | } 40 | 41 | fun callback(label: String, payload: String = "", color: String = COLOR_PRIMARY): Options { 42 | return Options( 43 | "action" to Options( 44 | "type" to "callback" 45 | , "label" to label 46 | , "payload" to payload 47 | ) 48 | , "color" to color 49 | ) 50 | } 51 | 52 | fun callbackCommand(label: String, command: String, forId: Int = 0, type: Int = CALLBACK_DEFAULT, color: String = COLOR_PRIMARY): Options { 53 | return callback(label, cbCmd(command, forId, type), color) 54 | } 55 | 56 | fun textCommand(label: String, command: String, forId: Int = 0, color: String = COLOR_PRIMARY): Options { 57 | return text(label, cmd(command, forId), color) 58 | } 59 | 60 | fun cbCmd(command: String, forId: Int = 0, type: Int = CALLBACK_DEFAULT): String { 61 | val std = Options("command" to command) 62 | if (forId != 0) std["for_id"] = forId 63 | if (type != 0) std["type"] = type 64 | return JsonEncoder.encode(std) 65 | } 66 | 67 | fun cmd(command: String, forId: Any? = 0): String { 68 | val std = Options("command" to command) 69 | if (forId != 0) 70 | std["for_id"] = forId 71 | return JsonEncoder.encode(std) 72 | } 73 | 74 | fun location(payload: String = "", color: String = COLOR_PRIMARY): Options { 75 | return Options("action" to Options( 76 | "type" to "location" 77 | , "payload" to payload 78 | ), "color" to color 79 | ) 80 | } 81 | 82 | fun vkpay(hash: String, payload: String = "", color: String = COLOR_PRIMARY): Options { 83 | return Options("action" to Options( 84 | "type" to "vkpay" 85 | , "payload" to payload 86 | , "hash" to hash 87 | ), "color" to color 88 | ) 89 | } 90 | 91 | fun vkapp(appId: Int, label: String, ownerId: Int = 0, hash: String = "", payload: String = "", color: String = COLOR_PRIMARY): Options { 92 | return Options("action" to Options( 93 | "type" to "open_app" 94 | , "app_id" to appId 95 | , "owner_id" to ownerId 96 | , "payload" to payload 97 | , "label" to label 98 | , "hash" to hash 99 | ), "color" to color 100 | ) 101 | } 102 | 103 | fun link(link: String, label: String, payload: String = ""): Options { 104 | return Options("action" to Options( 105 | "type" to "open_link" 106 | , "payload" to payload 107 | , "label" to label 108 | , "link" to link 109 | ) 110 | ) 111 | } 112 | 113 | fun create(buttons: List, inline: Boolean = true, oneTime: Boolean = false): Options { 114 | 115 | val res = Options("buttons" to buttons) 116 | if (inline) res["inline"] = inline 117 | else res["one_time"] = oneTime 118 | return res 119 | } 120 | 121 | fun create(buttons: Array, inline: Boolean = true, oneTime: Boolean = false): Options { 122 | val res = Options("buttons" to buttons) 123 | if (inline) 124 | res["inline"] = inline 125 | else 126 | res["one_time"] = oneTime 127 | return res 128 | } 129 | 130 | fun createJson(buttons: List, inline: Boolean = true, oneTime: Boolean = false): String { 131 | return JsonEncoder.encode(create(buttons, inline, oneTime)) 132 | } 133 | 134 | fun createJson(buttons: Array, inline: Boolean = true, oneTime: Boolean = false): String { 135 | return JsonEncoder.encode(create(buttons, inline, oneTime)) 136 | } 137 | 138 | fun createSingleButtonJson(button: Any, inline: Boolean = true, oneTime: Boolean = false): String { 139 | return JsonEncoder.encode(create(arrayOf(arrayOf(button)), inline, oneTime)) 140 | } 141 | 142 | fun clearKeyboardJson(): String { 143 | return """{"buttons":[], "one_time":true}""" 144 | } 145 | 146 | fun carousel(elements: List): String { 147 | val elementsStd = mutableListOf() 148 | for (e in elements) { 149 | val el = Options() 150 | if (!e.title.isNullOrEmpty()) 151 | el["title"] = e.title 152 | if (!e.description.isNullOrEmpty()) 153 | el["description"] = e.description 154 | if (!e.photo_id.isNullOrEmpty()) 155 | el["photo_id"] = e.photo_id 156 | if (!e.photo_id.isNullOrEmpty()) 157 | el["photo_id"] = e.photo_id 158 | if (!e.buttons.isNullOrEmpty()) 159 | el["buttons"] = e.buttons 160 | if (!e.action.isNullOrEmpty()) 161 | el["action"] = e.action 162 | 163 | elementsStd.add(el) 164 | } 165 | val data = Options( 166 | "type" to "carousel" 167 | , "elements" to elementsStd 168 | ) 169 | return JsonEncoder.encode(data) 170 | } 171 | 172 | class CarouselElement(val title: String? = null, val description: String? = null, val photo_id: String? = null, val buttons: List? = null, val action: Options? = null) 173 | } -------------------------------------------------------------------------------- /src/iris/vk/api/future/VkApiConnectionFutureHttpClient.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api.future 2 | 3 | import iris.vk.Options 4 | import iris.vk.api.simple.VkApiConnection.VkApiConnectResponse 5 | import java.io.File 6 | import java.net.URI 7 | import java.net.URLEncoder 8 | import java.net.http.HttpClient 9 | import java.net.http.HttpRequest 10 | import java.net.http.HttpResponse 11 | import java.nio.charset.StandardCharsets 12 | import java.nio.file.Files 13 | import java.nio.file.Path 14 | import java.time.Duration 15 | import java.util.concurrent.CompletableFuture 16 | 17 | /** 18 | * @created 25.10.2019 19 | * @author [Ivan Ivanov](https://vk.com/irisism) 20 | */ 21 | class VkApiConnectionFutureHttpClient(private val client: HttpClient) : VkApiConnectionFuture { 22 | 23 | override fun request(url: String, data: String?): CompletableFuture { 24 | return request(url, if (data == null) null else HttpRequest.BodyPublishers.ofString(data), HttpResponse.BodyHandlers.ofString()).thenApply { 25 | VkApiConnectResponse(it.statusCode(), it.body()) 26 | } 27 | } 28 | 29 | override fun request(url: String, data: Map?): CompletableFuture { 30 | return request(url, encodeOptions(data)) 31 | } 32 | 33 | fun request(url: String, data: HttpRequest.BodyPublisher?, responseHandler: HttpResponse.BodyHandler, headers: Map? = null): CompletableFuture> { 34 | var builder = HttpRequest.newBuilder() 35 | .uri(URI.create(url)) 36 | if (data != null) 37 | builder = builder.POST(data) 38 | headers?.forEach { t, u -> 39 | builder = builder.header(t, u) 40 | } 41 | 42 | val request = builder.build() 43 | 44 | return client.sendAsync(request, responseHandler) 45 | } 46 | 47 | override fun requestUpload(url: String, files: Map, data: Map?): CompletableFuture { 48 | val map = data?.toMutableMap() ?: mutableMapOf() 49 | map.putAll(files) 50 | 51 | val boundary = "testing" 52 | val request = HttpRequest.newBuilder() 53 | .uri(URI.create(url)) 54 | .POST(ofMimeMultipartData(map, boundary)) 55 | .header("Content-Type", "multipart/form-data; boundary=\"$boundary\"") 56 | .build() 57 | 58 | return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply { 59 | VkApiConnectResponse(it.statusCode(), it.body()) 60 | } 61 | } 62 | 63 | private fun encode(o: String): String? { 64 | return URLEncoder.encode(o, StandardCharsets.UTF_8) 65 | } 66 | 67 | private fun encodeOptions(obj: Map?): String? { 68 | val sb = StringBuilder() 69 | if (obj != null) 70 | for (o in obj.entries) { 71 | sb.append(encode(o.key)).append('=') 72 | .append(encode(o.value.toString())).append("&") 73 | } 74 | return sb.toString() 75 | } 76 | 77 | private fun ofMimeMultipartData(dataItem: Map, boundary: String): HttpRequest.BodyPublisher { 78 | val byteArrays = ArrayList() 79 | val separator = ("--$boundary\r\nContent-Disposition: form-data; name=").toByteArray() 80 | 81 | for (entry in dataItem.entries) { 82 | byteArrays.add(separator) 83 | val value = entry.value 84 | if (value is Options) { 85 | when { 86 | value["file"] != null -> { 87 | val path = if (value["file"] is File) Path.of((value["file"] as File).toURI()) else Path.of(value["file"] as String) 88 | val mimeType = if (value["Content-Type"] != null) value.getString("Content-Type") else Files.probeContentType(path) 89 | val filename = value["filename"] ?: path.fileName 90 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + filename + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()) 91 | byteArrays.add(Files.readAllBytes(path)) 92 | byteArrays.add("\r\n".toByteArray()) 93 | } 94 | value["data"] is ByteArray -> { 95 | val mimeType = if (value["Content-Type"] != null) value.getString("Content-Type") else "application/octet-stream" 96 | val filename = value.getStringOrNull("filename")?: "Untitled" 97 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + filename + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()) 98 | byteArrays.add(value["data"] as ByteArray) 99 | byteArrays.add("\r\n".toByteArray()) 100 | } 101 | else -> { 102 | throw IllegalArgumentException(value.toString()) 103 | } 104 | } 105 | } else if (value is File || value is Path) { 106 | val path = if (value is File) Path.of(value.toURI()) else value as Path 107 | val mimeType = Files.probeContentType(path) 108 | byteArrays.add(("\"" + encode(entry.key) + "\"; filename=\"" + path.fileName + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").toByteArray()) 109 | byteArrays.add(Files.readAllBytes(path)) 110 | byteArrays.add("\r\n".toByteArray()) 111 | } else { 112 | byteArrays.add(("\"" + encode(entry.key) + "\"\r\n\r\n" + entry.value + "\r\n").toByteArray()) 113 | } 114 | } 115 | byteArrays.add(("--$boundary--").toByteArray()) 116 | return HttpRequest.BodyPublishers.ofByteArrays(byteArrays) 117 | } 118 | 119 | companion object { 120 | fun newClient(settings: Map? = null): HttpClient { 121 | val builder = HttpClient.newBuilder() 122 | .version(HttpClient.Version.HTTP_1_1) 123 | .followRedirects(HttpClient.Redirect.NEVER) 124 | if (settings != null) { 125 | builder.connectTimeout(Duration.ofSeconds(settings["connectTimeout"]?.toLong() ?: 5_000L)) 126 | } 127 | return builder.build() 128 | } 129 | 130 | fun wrapData(data: String?): HttpRequest.BodyPublisher? { 131 | return if (data == null) null else HttpRequest.BodyPublishers.ofString(data) 132 | } 133 | 134 | fun wrapData(data: ByteArray?): HttpRequest.BodyPublisher? { 135 | return if (data == null) null else HttpRequest.BodyPublishers.ofByteArray(data) 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /src/iris/vk/VkPollingUser.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | import iris.vk.api.LongPollSettings 6 | import iris.vk.api.VK_BOT_ERROR_WRONG_TOKEN 7 | import iris.vk.api.VkApis 8 | import iris.vk.api.simple.VkApi 9 | import java.util.logging.Logger 10 | 11 | /** 12 | * @created 08.09.2019 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | 16 | open class VkPollingUser(protected val vkApi: VkApi, protected val updateProcessor: VkUpdateProcessor) : Runnable { 17 | 18 | constructor(api: VkApi, eventHandler: VkEventHandler) : this(api, VkUpdateProcessorUserDefault(api, eventHandler)) 19 | 20 | constructor(token: String, eventHandler: VkEventHandler) : this(VkApi(token), eventHandler) 21 | 22 | private var isWorking = true 23 | 24 | companion object { 25 | val logger = Logger.getLogger("iris.vk")!! 26 | } 27 | 28 | private var thread: Thread? = null 29 | 30 | open fun startPolling() { 31 | thread = Thread(this) 32 | thread!!.start() 33 | } 34 | 35 | open fun join() { 36 | thread?.join() 37 | } 38 | 39 | override fun run() { 40 | isWorking = true 41 | val thisThread = thread ?: Thread.currentThread() 42 | while (!thisThread.isInterrupted && isWorking) { 43 | try { 44 | runInternal() 45 | } catch (e: Throwable) { 46 | errorCaught(e) 47 | } 48 | } 49 | } 50 | 51 | protected fun errorCaught(e: Throwable) { 52 | e.printStackTrace() 53 | } 54 | 55 | private fun runInternal() { 56 | var longPoll = this.getLongPollServer() 57 | if (longPoll == null) { 58 | logger.warning("FAIL AUTH") 59 | return 60 | } 61 | if (longPoll["response"].isNull()) { 62 | if (VkApis.isError(longPoll)) 63 | logger.warning("VK Error: ${VkApis.errorString(longPoll)}") 64 | else 65 | logger.warning("No start response!\n${longPoll.obj()}") 66 | return 67 | } 68 | 69 | logger.fine("VkCallbackRequestServer received. Starting listening") 70 | 71 | var lastTs = longPoll["response"]["ts"].asString() 72 | val accessMode = (2 + 8).toString() 73 | var longPollSettings = getLongPollSettings(longPoll["response"]["server"].asString(), longPoll["response"]["key"].asString(), accessMode) 74 | 75 | val thisThread = Thread.currentThread() 76 | loop@ while (!thisThread.isInterrupted && this.isWorking) { 77 | val updates = getUpdates(longPollSettings, lastTs) 78 | 79 | if (updates == null) { 80 | longPoll = getLongPollServer() 81 | 82 | if (longPoll == null) { 83 | logger.warning("FAIL AUTH") 84 | return 85 | } 86 | 87 | if (longPoll["response"].isNull()) { 88 | logger.warning("NO RESPONSE") 89 | return 90 | } 91 | val response = longPoll["response"] 92 | longPollSettings = getLongPollSettings(response["server"].asString(), response["key"].asString(), accessMode) 93 | lastTs = response["ts"].asString() 94 | continue 95 | } 96 | 97 | if (updates["updates"].isNull()) { 98 | if (!updates["failed"].isNull()) { 99 | when (updates["failed"].asInt()) { 100 | 2 -> { 101 | // истёк срок ссылки 102 | logger.info("Long poll connection expired. Rebuilding") 103 | longPoll = getLongPollServer() 104 | 105 | if (longPoll == null) { 106 | logger.warning("FAIL AUTH") 107 | return 108 | } 109 | 110 | if (longPoll["response"].isNull()) { 111 | logger.warning("NO RESPONSE") 112 | return 113 | } 114 | 115 | val response = longPoll["response"] 116 | longPollSettings = getLongPollSettings(response["server"].asString(), response["key"].asString(), accessMode) 117 | lastTs = longPoll["response"]["ts"].asString() 118 | continue@loop 119 | } 1 -> { // обновляем TS 120 | lastTs = longPoll!!["response"]["ts"].asString() 121 | continue@loop 122 | } 3 -> { // обновляем TS 123 | logger.info { updates["error"].asString() + ". Try to rebuild" } 124 | longPoll = getLongPollServer() 125 | 126 | if (longPoll == null) { 127 | logger.warning("FAIL AUTH") 128 | return 129 | } 130 | 131 | if (longPoll["response"].isNull()) { 132 | logger.warning("NO RESPONSE") 133 | return 134 | } 135 | 136 | val response = longPoll["response"] 137 | longPollSettings = getLongPollSettings(response["server"].asString(), response["key"].asString(), accessMode) 138 | lastTs = response["ts"].asString() 139 | continue@loop 140 | } else -> { 141 | logger.warning("Как мы здесь оказались???") 142 | return 143 | } 144 | } 145 | 146 | } else if (!updates["error"].isNull()) { 147 | if (updates["error"]["error_code"].asInt() == VK_BOT_ERROR_WRONG_TOKEN 148 | || updates["error"]["error_msg"].asString() == "User authorization failed: access_token has expired." 149 | ) { 150 | logger.warning("Нет токена?") 151 | return 152 | } else { 153 | return 154 | } 155 | } else { 156 | logger.warning("YOU ARE HERE. SEEMS SOMETHING WRONG") 157 | return 158 | } 159 | } 160 | lastTs = updates["ts"].asString() 161 | updateProcessor.processUpdates((updates["updates"] as JsonArray).getList()) 162 | } 163 | } 164 | 165 | protected open fun getLongPollSettings(server: String, key: String, accessMode: String): LongPollSettings { 166 | return LongPollSettings("https://$server", key, accessMode) 167 | } 168 | 169 | protected open fun getLongPollServer(): JsonItem? { 170 | return vkApi.messages.getLongPollServer() 171 | } 172 | 173 | protected open fun getUpdates(lpSettings: LongPollSettings, ts: String): JsonItem? { 174 | val response = vkApi.connection.request(lpSettings.getUpdatesLink(ts)) ?: return null 175 | if (response.code != 200) return null 176 | return vkApi.parser(response.responseText) 177 | } 178 | 179 | fun stop() { 180 | this.isWorking = false 181 | thread?.interrupt() 182 | } 183 | 184 | } 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/iris/vk/VkUpdateProcessorGroupDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonItem 4 | import iris.vk.event.* 5 | import iris.vk.event.group.* 6 | import java.util.* 7 | 8 | class VkUpdateProcessorGroupDefault( 9 | private val handler: VkEventHandler, 10 | private val eventProducerFactory: VkEventProducerFactory = GroupEventProducerDefault(), 11 | 12 | ) : VkUpdateProcessor { 13 | 14 | open class GroupEventProducerDefault : VkEventProducer, VkEventProducerFactory { 15 | 16 | override fun producer(): VkEventProducer { 17 | return this 18 | } 19 | 20 | override fun message(obj: JsonItem, sourcePeerId: Int): Message { 21 | return GroupMessage(obj, sourcePeerId) 22 | } 23 | 24 | override fun messageWithoutChatInfo(obj: JsonItem, sourcePeerId: Int): Message { 25 | return GroupMessageWithoutChatInfo(obj["object"], sourcePeerId) 26 | } 27 | 28 | override fun invite(obj: JsonItem, sourcePeerId: Int): ChatEvent { 29 | return GroupChatEvent(obj, sourcePeerId) 30 | } 31 | 32 | override fun leave(obj: JsonItem, sourcePeerId: Int): ChatEvent { 33 | return GroupChatEvent(obj, sourcePeerId) 34 | } 35 | 36 | override fun titleUpdate(obj: JsonItem, sourcePeerId: Int): TitleUpdate { 37 | return GroupTitleUpdate(obj, sourcePeerId) 38 | } 39 | 40 | override fun pin(obj: JsonItem, sourcePeerId: Int): PinUpdate { 41 | return GroupPinUpdate(obj, sourcePeerId) 42 | } 43 | 44 | override fun unpin(obj: JsonItem, sourcePeerId: Int): PinUpdate { 45 | return GroupPinUpdate(obj, sourcePeerId) 46 | } 47 | 48 | override fun screenshot(obj: JsonItem, sourcePeerId: Int): ChatEvent { 49 | return GroupChatEvent(obj, sourcePeerId) 50 | } 51 | 52 | override fun callback(obj: JsonItem, sourcePeerId: Int): CallbackEvent { 53 | return GroupCallbackEvent(obj, sourcePeerId) 54 | } 55 | 56 | override fun otherEvent(obj: JsonItem, sourcePeerId: Int): OtherEvent { 57 | return OtherEvent(obj, sourcePeerId) 58 | } 59 | } 60 | 61 | override fun processUpdates(updates: List) { 62 | var checkMessages: MutableList? = null 63 | var checkEditMessages: MutableList? = null 64 | var checkInvites: MutableList? = null 65 | var checkLeave: MutableList? = null 66 | var titleUpdaters: MutableList? = null 67 | var pinUpdaters: MutableList? = null 68 | var unpinUpdaters: MutableList? = null 69 | var checkCallbacks: MutableList? = null 70 | var screenshots: LinkedList? = null 71 | var others: LinkedList? = null 72 | 73 | val producer = eventProducerFactory.producer() 74 | 75 | for (update in updates) { 76 | val sourcePeerId = update["group_id"].asInt() 77 | when (val type = update["type"].asString()) { 78 | "message_new" -> { // это сообщение 79 | val obj = update["object"] 80 | val message = obj["message"].let { if (it.isNull()) obj else it } 81 | if (message["action"]["type"].isNotNull()) { 82 | when(val subType = message["action"]["type"].asString()) { 83 | "chat_invite_user" -> { 84 | if (checkInvites == null) checkInvites = mutableListOf() 85 | checkInvites!! += producer.invite(obj, sourcePeerId) 86 | } 87 | "chat_title_update" -> { 88 | if (titleUpdaters == null) titleUpdaters = mutableListOf() 89 | titleUpdaters!! += producer.titleUpdate(obj, sourcePeerId) 90 | } 91 | "chat_invite_user_by_link" -> { 92 | if (checkInvites == null) checkInvites = mutableListOf() 93 | checkInvites!! += producer.invite(obj, sourcePeerId) 94 | } 95 | "chat_kick_user" -> { 96 | if (checkLeave == null) checkLeave = mutableListOf() 97 | checkLeave!! += producer.leave(obj, sourcePeerId) 98 | } 99 | "chat_pin_message" -> { 100 | if (pinUpdaters == null) pinUpdaters = mutableListOf() 101 | pinUpdaters!! += producer.pin(obj, sourcePeerId) 102 | } 103 | 104 | "chat_unpin_message" -> { 105 | if (unpinUpdaters == null) unpinUpdaters = mutableListOf() 106 | unpinUpdaters!! += producer.unpin(obj, sourcePeerId) 107 | } 108 | "chat_screenshot" -> { 109 | if (screenshots == null) screenshots = mutableListOf() 110 | screenshots!! += producer.screenshot(obj, sourcePeerId) 111 | } 112 | else -> { 113 | VkPollingUser.logger.info("Unknown message_new type : $subType") 114 | if (others == null) others = mutableListOf() 115 | others!! += producer.otherEvent(obj, sourcePeerId) 116 | /*if (checkMessages == null) checkMessages = mutableListOf() 117 | checkMessages!! += producer.message(obj, sourcePeerId)*/ 118 | } 119 | } 120 | } else { 121 | if (checkMessages == null) checkMessages = mutableListOf() 122 | checkMessages!! += producer.message(obj, sourcePeerId) 123 | } 124 | } 125 | "message_reply" -> { 126 | if (checkMessages == null) checkMessages = mutableListOf() 127 | checkMessages!! += producer.messageWithoutChatInfo(update, sourcePeerId) 128 | } 129 | "message_edit" -> { 130 | if (checkEditMessages == null) checkEditMessages = mutableListOf() 131 | checkEditMessages!! += producer.messageWithoutChatInfo(update, sourcePeerId) 132 | } 133 | "message_event" -> { 134 | if (checkCallbacks == null) checkCallbacks = mutableListOf() 135 | checkCallbacks!! += producer.callback(update, sourcePeerId) 136 | } 137 | else -> { 138 | VkPollingUser.logger.info("Unknown type of event: $type") 139 | if (others == null) others = mutableListOf() 140 | others!! += producer.otherEvent(update["object"], sourcePeerId) 141 | } 142 | } 143 | } 144 | if (checkMessages != null) 145 | handler.processMessages(checkMessages) 146 | if (checkEditMessages != null) 147 | handler.processEditedMessages(checkEditMessages) 148 | if (checkInvites != null) 149 | handler.processInvites(checkInvites) 150 | if (titleUpdaters != null) 151 | handler.processTitleUpdates(titleUpdaters) 152 | if (pinUpdaters != null) 153 | handler.processPinUpdates(pinUpdaters) 154 | if (unpinUpdaters != null) 155 | handler.processUnpinUpdates(unpinUpdaters) 156 | if (checkLeave != null) 157 | handler.processLeaves(checkLeave) 158 | if (checkCallbacks != null) 159 | handler.processCallbacks(checkCallbacks) 160 | if (screenshots != null) 161 | handler.processScreenshots(screenshots) 162 | if (others != null) 163 | handler.processOthers(others) 164 | } 165 | 166 | private inline fun mutableListOf() = LinkedList() 167 | } -------------------------------------------------------------------------------- /src/iris/vk/VkUpdateProcessorUserDefault.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | import iris.vk.api.VkApis 6 | import iris.vk.api.simple.VkApi 7 | import iris.vk.event.* 8 | import iris.vk.event.user.UserChatEvent 9 | import iris.vk.event.user.UserMessage 10 | import iris.vk.event.user.UserPinUpdate 11 | import iris.vk.event.user.UserTitleUpdate 12 | import java.util.* 13 | 14 | class VkUpdateProcessorUserDefault(private val eventHandler: VkEventHandler, private val eventProducer: VkEventProducerFactory) : VkUpdateProcessor { 15 | 16 | constructor(api: VkApi, eventHandler: VkEventHandler) : this(eventHandler, UserEventProducerDefault(api)) 17 | 18 | private class UserEventProducerDefault(private val api: VkApi) : VkEventProducerFactory { 19 | override fun producer(): VkEventProducer = SubUserEventProducer(api) 20 | } 21 | 22 | private class SubUserEventProducer(api: VkApi) : VkEventProducer { 23 | 24 | private val apiSource = ApiSource(api) 25 | 26 | private class ApiSource(private val api: VkApi) : UserChatEvent.ApiSource { 27 | 28 | val messages = mutableListOf() 29 | 30 | private val map: Map by lazy(LazyThreadSafetyMode.NONE) { 31 | val ids = messages.map { it[1].asInt() } 32 | val result = api.messages.getById(ids)?: return@lazy emptyMap() 33 | if (VkApis.isError(result)) 34 | return@lazy emptyMap() 35 | val items = result["response"]["items"] as JsonArray 36 | items.associateBy { it["id"].asInt() } 37 | } 38 | 39 | private fun getDirect(id: Int): JsonItem? { 40 | val result = api.messages.getById(listOf(id))?: return null 41 | if (VkApis.isError(result)) 42 | return null 43 | val items = result["response"]["items"] as JsonArray 44 | return items.firstOrNull() 45 | } 46 | 47 | override fun getFullEvent(messageId: Int): JsonItem? { 48 | return map[messageId] ?: getDirect(messageId) 49 | } 50 | 51 | operator fun plusAssign(item: JsonItem) { 52 | messages += item 53 | } 54 | } 55 | 56 | override fun message(obj: JsonItem, sourcePeerId: Int): Message { 57 | apiSource += obj[1] 58 | return UserMessage(apiSource, obj, sourcePeerId) 59 | } 60 | 61 | override fun messageWithoutChatInfo(obj: JsonItem, sourcePeerId: Int): Message { 62 | apiSource += obj[1] 63 | return UserMessage(apiSource, obj, sourcePeerId) 64 | } 65 | 66 | override fun invite(obj: JsonItem, sourcePeerId: Int): ChatEvent { 67 | apiSource += obj 68 | return UserChatEvent(apiSource, obj, sourcePeerId) 69 | } 70 | 71 | override fun leave(obj: JsonItem, sourcePeerId: Int): ChatEvent { 72 | apiSource += obj 73 | return UserChatEvent(apiSource, obj, sourcePeerId) 74 | } 75 | 76 | override fun titleUpdate(obj: JsonItem, sourcePeerId: Int): TitleUpdate { 77 | apiSource += obj 78 | return UserTitleUpdate(apiSource, obj, sourcePeerId) 79 | } 80 | 81 | override fun pin(obj: JsonItem, sourcePeerId: Int): PinUpdate { 82 | apiSource += obj 83 | return UserPinUpdate(apiSource, obj, sourcePeerId) 84 | } 85 | 86 | override fun unpin(obj: JsonItem, sourcePeerId: Int): PinUpdate { 87 | apiSource += obj 88 | return UserPinUpdate(apiSource, obj, sourcePeerId) 89 | } 90 | 91 | override fun screenshot(obj: JsonItem, sourcePeerId: Int): ChatEvent { 92 | apiSource += obj 93 | return UserChatEvent(apiSource, obj, sourcePeerId) 94 | } 95 | 96 | override fun callback(obj: JsonItem, sourcePeerId: Int): CallbackEvent { 97 | throw IllegalArgumentException("???") 98 | } 99 | 100 | override fun otherEvent(obj: JsonItem, sourcePeerId: Int): OtherEvent { 101 | return OtherEvent(obj, sourcePeerId) 102 | } 103 | } 104 | 105 | override fun processUpdates(updates: List) { 106 | val sourcePeerId = 0 107 | var messages: MutableList? = null 108 | //var editMessages: LinkedList? = null 109 | var invites: MutableList? = null 110 | var leaves: MutableList? = null 111 | var titleUpdaters: MutableList? = null 112 | var pinUpdaters: MutableList? = null 113 | var unpinUpdaters: MutableList? = null 114 | var screenshots: MutableList? = null 115 | var others: MutableList? = null 116 | val producer = eventProducer.producer() 117 | for (update in updates) { 118 | if (!update.isArray()) continue 119 | update as JsonArray 120 | if (update[0].asLong() == 4L) { // это сообщение 121 | val sourceAct = update[7]["source_act"].asStringOrNull() 122 | if (sourceAct != null) { 123 | when (sourceAct) { 124 | "chat_invite_user" -> { 125 | if (invites == null) invites = mutableListOf() 126 | invites!! += producer.invite(update, sourcePeerId) 127 | } 128 | "chat_title_update" -> { 129 | if (titleUpdaters == null) titleUpdaters = mutableListOf() 130 | titleUpdaters!! += producer.titleUpdate(update, sourcePeerId) 131 | } 132 | "chat_invite_user_by_link" -> { 133 | if (invites == null) invites = mutableListOf() 134 | invites!! += producer.invite(update, sourcePeerId) 135 | } 136 | "chat_kick_user" -> { 137 | if (leaves == null) leaves = mutableListOf() 138 | leaves!! += producer.leave(update, sourcePeerId) 139 | } 140 | "chat_pin_message" -> { 141 | if (pinUpdaters == null) pinUpdaters = mutableListOf() 142 | pinUpdaters!! += producer.pin(update, sourcePeerId) 143 | } 144 | 145 | "chat_unpin_message" -> { 146 | if (unpinUpdaters == null) unpinUpdaters = mutableListOf() 147 | unpinUpdaters!! += producer.unpin(update, sourcePeerId) 148 | } 149 | 150 | "chat_screenshot" -> { 151 | if (screenshots == null) screenshots = mutableListOf() 152 | screenshots!! += producer.screenshot(update, sourcePeerId) 153 | } 154 | else -> { 155 | if (others == null) others = mutableListOf() 156 | others!! += producer.otherEvent(update, sourcePeerId) 157 | } 158 | } 159 | } else { 160 | if (messages == null) messages = mutableListOf() 161 | messages!! += producer.message(update, sourcePeerId) 162 | } 163 | } 164 | } 165 | if (messages != null) 166 | eventHandler.processMessages(messages) 167 | if (invites != null) 168 | this.eventHandler.processInvites(invites) 169 | if (titleUpdaters != null) 170 | eventHandler.processTitleUpdates(titleUpdaters) 171 | if (pinUpdaters != null) 172 | this.eventHandler.processPinUpdates(pinUpdaters) 173 | if (unpinUpdaters != null) 174 | eventHandler.processUnpinUpdates(unpinUpdaters) 175 | if (leaves != null) 176 | this.eventHandler.processLeaves(leaves) 177 | if (screenshots != null) 178 | eventHandler.processScreenshots(screenshots) 179 | } 180 | 181 | private inline fun mutableListOf() = LinkedList() 182 | 183 | private fun processEditMessages(messages: List) { 184 | this.eventHandler.processEditedMessages(messages) 185 | } 186 | } -------------------------------------------------------------------------------- /src/iris/vk/api/IMessages.kt: -------------------------------------------------------------------------------- 1 | package iris.vk.api 2 | 3 | import iris.vk.Options 4 | 5 | /** 6 | * @created 28.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface IMessages { 10 | 11 | fun addChatUser(userId: Int, chatId: Int, visibleMessagesCount: Int, token: String? = null): SingleType 12 | 13 | fun allowMessagesFromGroup(groupId: Int, key: String, token: String? = null): SingleType 14 | 15 | fun createChat(ids: List, title: String? = null, name: String? = null, token: String? = null): SingleType 16 | 17 | fun delete(messageIds: Collection, isSpam: Boolean = false, deleteForAll: Boolean = false, groupId: Int = 0, token: String? = null): SingleType 18 | 19 | fun deleteChatPhoto(chatId: Int, groupId: Int = 0, token: String? = null): SingleType 20 | 21 | fun deleteConversation(peerId: Int, groupId: Int = 0, token: String? = null): SingleType 22 | 23 | fun deleteConversationChat(chatId: Int, groupId: Int = 0, token: String? = null): SingleType 24 | 25 | fun deleteConversationUser(id: Int, groupId: Int = 0, token: String? = null): SingleType 26 | 27 | fun deleteConversationGroup(id: Int, groupId: Int = 0, token: String? = null): SingleType 28 | 29 | fun denyMessagesFromGroup(groupId: Int, token: String? = null): SingleType 30 | 31 | fun sendMulti(data: List, token: String? = null): ListType 32 | 33 | fun edit(peerId: Int, message: String?, messageId: Int, options: Options? = null, token: String? = null): SingleType 34 | 35 | fun editChat(chatId: Int, title: String, token: String? = null): SingleType 36 | 37 | fun getByConversationMessageId(peerId: Int, conversationMessageIds: Collection, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 38 | 39 | fun getByConversationMessageId_ChatId(chatId: Int, conversationMessageIds: Collection, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 40 | 41 | fun getById(messageIds: List, previewLength: Int = 0, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 42 | 43 | fun getChat(chatId: Int, fields: String? = null, nameCase: String? = null, token: String? = null): SingleType 44 | 45 | fun getChats(chatIds: Collection, fields: String? = null, nameCase: String? = null, token: String? = null): SingleType 46 | 47 | fun getChatPreview(peerId: Int, fields: String? = null, token: String? = null): SingleType 48 | 49 | fun getChatPreview(link: String, fields: String? = null, token: String? = null): SingleType 50 | 51 | fun getConversationMembers(peerId: Int, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 52 | 53 | fun getConversations(offset: Int = 0, count: Int = 0, filter: String? = null, extended: Boolean = false, startMessageId: Int = 0, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 54 | 55 | fun getConversationsById(peerIds: Collection, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 56 | 57 | fun getHistory(peerId: Int, offset: Int = 0, count: Int = 100, startMessageId: Int = 0, rev: Boolean = false, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 58 | 59 | fun getHistoryAttachments(peerId: Int, startFrom: Int = 0, count: Int = 0, fields: String? = null, mediaType: String? = null, groupId: Int = 0, options: Options? = null, token: String? = null): SingleType 60 | 61 | fun getImportantMessages(count: Int = 0, offset: Int = 0, startMessageId: Int = 0, previewLength: Int = 0, fields: String? = null, extended: Boolean = false, groupId: Int = 0, token: String? = null): SingleType 62 | 63 | fun getInviteLink(peerId: Int, reset: Boolean, groupId: Int = 0, token: String? = null): SingleType 64 | 65 | fun getInviteLinkChat(chatId: Int, reset: Boolean, groupId: Int = 0, token: String? = null): SingleType 66 | 67 | fun getLastActivity(userId: Int, token: String? = null): SingleType 68 | 69 | fun getLongPollHistory(ts: Int, pts: Int, groupId: Int = 0, options: Options? = null, token: String? = null): SingleType 70 | 71 | fun getLongPollServer(needPts: Boolean = false, groupId: Int = 0, lpVersion: Int = 0, token: String? = null): SingleType 72 | 73 | fun isMessagesFromGroupAllowed(groupId: Int, userId: Int = 0, token: String? = null): SingleType 74 | 75 | fun joinChatByInviteLink(link: String, token: String? = null): SingleType 76 | 77 | fun markAsAnsweredConversation(peerId: Int, answered: Boolean = true, groupId: Int = 0, token: String? = null): SingleType 78 | 79 | fun markAsImportant(messageIds: Collection, important: Boolean = true, token: String? = null): SingleType 80 | 81 | fun markAsImportantConversation(peerId: Int, important: Boolean = true, groupId: Int = 0, token: String? = null): SingleType 82 | 83 | fun markAsRead(peerId: Int, token: String? = null): SingleType 84 | 85 | fun markAsRead(messageIds: Collection, peerId: Int = 0, startMessageId: Int = 0, groupId: Int = 0, markConversationAsRead: Boolean = true, token: String? = null): SingleType 86 | 87 | fun pin(peerId: Int, messageId: Int, conversationMessageId: Int = 0, token: String? = null): SingleType 88 | 89 | fun pinByCmid(peerId: Int, conversationMessageId: Int, token: String? = null): SingleType 90 | 91 | fun removeChatUser(chatId: Int, memberId: Int, token: String? = null): SingleType 92 | 93 | fun removeChatUserList(chatId: Int, memberIds: Collection, token: String? = null): ListType 94 | 95 | fun removeChatUserData(data: List, token: String? = null): ListType 96 | 97 | fun restore(messageId: Int, groupId: Int = 0, token: String? = null): SingleType 98 | 99 | fun search(q: String, peerId: Int = 0, offset: Int = 0, count: Int = 0, date: Int = 0, previewLength: Int = 0, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 100 | 101 | fun searchConversations(q: String, count: Int = 0, extended: Boolean = false, fields: String? = null, groupId: Int = 0, token: String? = null): SingleType 102 | 103 | fun send(peerId: Int, message: String? = null, attachment: String? = null, replyTo: Int = 0, forwardMessages: String? = null, dontParseLinks: Boolean = false, disableMentions: Boolean = false, options: Options? = null, token: String? = null): SingleType 104 | 105 | fun send(peerIds: Collection, message: String? = null, attachment: String? = null, replyTo: Int = 0, forwardMessages: String? = null, dontParseLinks: Boolean = false, disableMentions: Boolean = false, options: Options? = null, token: String? = null): SingleType 106 | 107 | fun send(domain: String, message: String? = null, attachment: String? = null, replyTo: Int = 0, forwardMessages: String? = null, dontParseLinks: Boolean = false, disableMentions: Boolean = false, options: Options? = null, token: String? = null): SingleType 108 | 109 | fun sendUser(userId: Int, message: String? = null, attachment: String? = null, replyTo: Int = 0, forwardMessages: String? = null, dontParseLinks: Boolean = false, disableMentions: Boolean = false, options: Options? = null, token: String? = null): SingleType 110 | 111 | fun sendChat(chatId: Int, message: String? = null, attachment: String? = null, replyTo: Int = 0, forwardMessages: String? = null, dontParseLinks: Boolean = false, disableMentions: Boolean = false, options: Options? = null, token: String? = null): SingleType 112 | 113 | fun setChatPhoto(file: String, token: String? = null): SingleType 114 | 115 | fun sendMessageEventAnswer(eventId: String, userId: Int, peerId: Int, eventData: Options?, token: String?): SingleType 116 | 117 | fun unpin(peerId: Int, groupId: Int = 0, token: String? = null): SingleType 118 | 119 | 120 | 121 | 122 | 123 | 124 | fun getChatMembers(chatId: Int, fields: String? = null, token: String? = null): SingleType 125 | 126 | //fun getUpdates(lpSettings: LongPollSettings, ts: String): SingleType 127 | 128 | 129 | } -------------------------------------------------------------------------------- /src/iris/vk/VkTriggerEventHandler.kt: -------------------------------------------------------------------------------- 1 | package iris.vk 2 | 3 | import iris.vk.event.* 4 | 5 | /** 6 | * @created 01.11.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class VkTriggerEventHandler() : VkEventHandler { 10 | 11 | constructor(initializer: VkTriggerEventHandler.() -> Unit) : this() { 12 | apply(initializer) 13 | } 14 | 15 | interface TriggerMessage { fun process(messages: List) } 16 | interface TriggerEditMessage { fun process(messages: List) } 17 | interface TriggerInvite { fun process(messages: List) } 18 | interface TriggerLeave { fun process(messages: List) } 19 | interface TriggerTitleUpdate { fun process(messages: List) } 20 | interface TriggerPinUpdate { fun process(messages: List) } 21 | interface TriggerUnpinUpdate { fun process(messages: List) } 22 | interface TriggerCallback { fun process(messages: List) } 23 | interface TriggerScreenshot { fun process(messages: List) } 24 | interface TriggerOther { fun process(messages: List) } 25 | 26 | class TriggerMessageLambda(private val processor: (messages: List) -> Unit) : TriggerMessage { 27 | override fun process(messages: List) { 28 | processor(messages) 29 | } 30 | } 31 | 32 | class TriggerMessageEditLambda(private val processor: (messages: List) -> Unit) : TriggerEditMessage { 33 | override fun process(messages: List) { 34 | processor(messages) 35 | } 36 | } 37 | 38 | class TriggerInviteLambda(private val processor: (messages: List) -> Unit) : TriggerInvite { 39 | override fun process(messages: List) { 40 | processor(messages) 41 | } 42 | } 43 | 44 | class TriggerLeaveLambda(private val processor: (messages: List) -> Unit) : TriggerLeave { 45 | override fun process(messages: List) { 46 | processor(messages) 47 | } 48 | } 49 | 50 | class TriggerTitleUpdateLambda(private val processor: (messages: List) -> Unit) : TriggerTitleUpdate { 51 | override fun process(messages: List) { 52 | processor(messages) 53 | } 54 | } 55 | 56 | class TriggerPinUpdateLambda(private val processor: (messages: List) -> Unit) : TriggerPinUpdate { 57 | override fun process(messages: List) { 58 | processor(messages) 59 | } 60 | } 61 | 62 | class TriggerUnpinUpdateLambda(private val processor: (messages: List) -> Unit) : TriggerUnpinUpdate { 63 | override fun process(messages: List) { 64 | processor(messages) 65 | } 66 | } 67 | 68 | class TriggerCallbackLambda(private val processor: (messages: List) -> Unit) : TriggerCallback { 69 | override fun process(messages: List) { 70 | processor(messages) 71 | } 72 | } 73 | 74 | class TriggerScreenshotLambda(private val processor: (messages: List) -> Unit) : TriggerScreenshot { 75 | override fun process(messages: List) { 76 | processor(messages) 77 | } 78 | } 79 | 80 | class TriggerOtherLambda(private val processor: (messages: List) -> Unit) : TriggerOther { 81 | override fun process(messages: List) { 82 | processor(messages) 83 | } 84 | } 85 | 86 | private var messages: MutableList? = null 87 | private var editMessages: MutableList? = null 88 | private var invites: MutableList? = null 89 | private var leaves: MutableList? = null 90 | private var titles: MutableList? = null 91 | private var pins: MutableList? = null 92 | private var unpins: MutableList? = null 93 | private var callbacks: MutableList? = null 94 | private var screenshots: MutableList? = null 95 | private var others: MutableList? = null 96 | 97 | operator fun plusAssign(trigger: TriggerMessage) { 98 | if (messages == null) messages = mutableListOf() 99 | messages!! += trigger 100 | } 101 | 102 | operator fun plusAssign(trigger: TriggerEditMessage) { 103 | if (editMessages == null) editMessages = mutableListOf() 104 | editMessages!! += trigger 105 | } 106 | 107 | operator fun plusAssign(trigger: TriggerInvite) { 108 | if (invites == null) invites = mutableListOf() 109 | invites!! += trigger 110 | } 111 | 112 | operator fun plusAssign(trigger: TriggerLeave) { 113 | if (leaves == null) leaves = mutableListOf() 114 | leaves!! += trigger 115 | } 116 | 117 | operator fun plusAssign(trigger: TriggerTitleUpdate) { 118 | if (titles == null) titles = mutableListOf() 119 | titles!! += trigger 120 | } 121 | 122 | operator fun plusAssign(trigger: TriggerPinUpdate) { 123 | if (pins == null) pins = mutableListOf() 124 | pins!! += trigger 125 | } 126 | 127 | operator fun plusAssign(trigger: TriggerUnpinUpdate) { 128 | if (unpins == null) unpins = mutableListOf() 129 | unpins!! += trigger 130 | } 131 | 132 | operator fun plusAssign(trigger: TriggerCallback) { 133 | if (callbacks == null) callbacks = mutableListOf() 134 | callbacks!! += trigger 135 | } 136 | 137 | operator fun plusAssign(trigger: TriggerScreenshot) { 138 | if (screenshots == null) screenshots = mutableListOf() 139 | screenshots!! += trigger 140 | } 141 | 142 | operator fun plusAssign(trigger: TriggerOther) { 143 | if (others == null) others = mutableListOf() 144 | others!! += trigger 145 | } 146 | 147 | fun onMessage(processor: (messages: List) -> Unit) = plusAssign(TriggerMessageLambda(processor)) 148 | fun onMessage(trigger: TriggerMessage) = plusAssign(trigger) 149 | fun onMessageEdit(processor: (messages: List) -> Unit) = plusAssign(TriggerMessageEditLambda(processor)) 150 | fun onInvite(processor: (messages: List) -> Unit) = plusAssign(TriggerInviteLambda(processor)) 151 | fun onLeave(processor: (messages: List) -> Unit) = plusAssign(TriggerLeaveLambda(processor)) 152 | fun onTitleUpdate(processor: (messages: List) -> Unit) = plusAssign(TriggerTitleUpdateLambda(processor)) 153 | fun onPinUpdate(processor: (messages: List) -> Unit) = plusAssign(TriggerPinUpdateLambda(processor)) 154 | fun onUnpinUpdate(processor: (messages: List) -> Unit) = plusAssign(TriggerUnpinUpdateLambda(processor)) 155 | fun onCallback(processor: (messages: List) -> Unit) = plusAssign(TriggerCallbackLambda(processor)) 156 | fun onScreenshot(processor: (messages: List) -> Unit) = plusAssign(TriggerScreenshotLambda(processor)) 157 | fun onOther(processor: (messages: List) -> Unit) = plusAssign(TriggerOtherLambda(processor)) 158 | 159 | override fun processMessages(messages: List) { 160 | this.messages?.forEach { it.process(messages) } 161 | } 162 | 163 | override fun processEditedMessages(messages: List) { 164 | this.editMessages?.forEach { it.process(messages) } 165 | } 166 | 167 | override fun processInvites(invites: List) { 168 | this.invites?.forEach { it.process(invites) } 169 | } 170 | 171 | override fun processLeaves(leaves: List) { 172 | this.leaves?.forEach { it.process(leaves) } 173 | } 174 | 175 | override fun processTitleUpdates(updaters: List) { 176 | this.titles?.forEach { it.process(updaters) } 177 | } 178 | 179 | override fun processPinUpdates(updaters: List) { 180 | this.pins?.forEach { it.process(updaters) } 181 | } 182 | 183 | override fun processUnpinUpdates(updates: List) { 184 | this.unpins?.forEach { it.process(updates) } 185 | } 186 | 187 | override fun processCallbacks(callbacks: List) { 188 | this.callbacks?.forEach { it.process(callbacks) } 189 | } 190 | 191 | override fun processScreenshots(screenshots: List) { 192 | this.screenshots?.forEach { it.process(screenshots) } 193 | } 194 | 195 | override fun processOthers(others: List) { 196 | this.others?.forEach { it.process(others) } 197 | } 198 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iris VK API 2 | 3 | Ещё одна библиотека по работе с VK API на **Kotlin** 💖 4 | 5 | Гибкая система получения данных от VK. После обновления VK API вам не придётся ждать 6 | обновления в ваших прежних используемых библиотеках. Все данные будут доступны сразу после обновлений самого VK API. 7 | 8 | ## Как скачать и установить? 9 | 10 | ##### Прямая ссылка: 11 | 12 | - Вы можете скачать [подготовленные релизы](https://github.com/iris2iris/iris-vk-api/releases), чтобы скачать JAR файл напрямую. 13 | - Также вам необходимо скачать зависимость — JAR файл [Iris JSON Parser](https://github.com/iris2iris/iris-vk-api/releases/download/v0.3/iris-json-parser.jar) 14 | 15 | ## Как это использовать 16 | 17 | ### Простой VkApi 18 | 19 | ```kotlin 20 | val vk = VkApi(token) 21 | val res = vk.messages.send(userToId, "Привет. Это сообщение с Kotlin") 22 | println(res?.obj()) 23 | ``` 24 | 25 | ### VkApi методом Future 26 | 27 | ```kotlin 28 | val vk = VkApiFuture(token) 29 | vk.messages.send(userToId, "Привет. Это сообщение с Kotlin").thenAccept { 30 | println("Это сообщение появится вторым") 31 | println(it?.obj()) 32 | } 33 | println("Это сообщение появится первым, т.к. метод Future неблокирующий") 34 | 35 | // А можно сделать последовательное исполнение 36 | val future = vk.messages.send(userToId, "Привет. Это сообщение с Kotlin") 37 | val result = future.get() // дожидаемся ответа 38 | println(result?.obj()) // выводим результат 39 | ``` 40 | 41 | ### VkApi, упаковывающий запросы в execute 42 | ```kotlin 43 | val vk = VkApiPack(token) 44 | val futuresList = vk.messages.sendMulti(listOf( 45 | Options("peer_id" to userToId, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553"), 46 | Options("peer_id" to 2, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553"), 47 | ) 48 | ) 49 | println("Прошёл сюда без задержек") 50 | val secondFutures = vk.execute(listOf( 51 | VkRequestData("messages.send", Options("peer_id" to userToId, "random_id" to (0..2_000_000).random(), "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553")) 52 | , VkRequestData("messages.edit", Options("peer_id" to userToId, "conversation_message_id" to 1, "message" to "Привет. Это сообщение с Kotlin\nОно почти работает!", "attachment" to "photo-181070115_457239553")) 53 | )) 54 | 55 | println("И сюда тоже без задержек. Но вот ниже нужно подождать\n") 56 | println("Первый пакет:") 57 | for (it in futuresList.futures) 58 | println(it.get()?.obj()) 59 | 60 | println() 61 | println("Второй пакет скорее всего без задержек:") 62 | for (it in secondFutures.futures) 63 | println(it.get()?.obj()) 64 | println() 65 | println("Завершились") 66 | ``` 67 | 68 | ### VkPollingGroup — слушатель событий методом Long Poll 69 | 70 | ```kotlin 71 | // Создаём класс для отправки сообщений 72 | val vk = VkApiPack(token) 73 | 74 | // Определяем простой обработчик событий 75 | val simpleMessageHandler = object : VkEventHandlerAdapter() { 76 | 77 | override fun processMessage(message: Message) { 78 | // message содержит информацию о полученном JsonItem (message.source) и вспомогательную информацию, которую 79 | // добавит сам программист по мере продвижения события (message.options) 80 | 81 | // message.text — это метод, подготавливает текст для дальнейшей работы 82 | val text = message.text 83 | if (text.equals("пинг", true)) 84 | vk.messages.send(message.peerId, "ПОНГ") 85 | } 86 | } 87 | 88 | // Передаём в параметрах слушателя событий токен и созданный обработчик событий 89 | val listener = VkPollingGroup(token, simpleMessageHandler) 90 | listener.startPolling() // Можно запустить неблокирующего слушателя 91 | listener.join() // Даст дождаться завершения работы слушателя 92 | ``` 93 | 94 | ### VkPollingUser — слушатель событий пользовательского Long Poll 95 | Всё то же самое, что и у `VkPollingGroup`, только вместо этого класса используется `VkPollingUser` 96 | ```kotlin 97 | //... 98 | val listener = VkPollingUser(token, simpleMessageHandler) 99 | //... 100 | ``` 101 | 102 | ### VkCallbackGroup — слушатель событий методом VK Callback API 103 | 104 | ```kotlin 105 | val api = VkApiPack(token) 106 | 107 | val cbEngine = VkCallbackServer( 108 | gbSource = groupSource, 109 | path = "/kotlin/callback", 110 | addressTester = VkAddressTesterDefault(), 111 | vkTimeVsLocalTimeDiff = api.utils.getServerTime().get()!!["response"].asLong()*1000L - System.currentTimeMillis() 112 | ) 113 | 114 | val messageHandler = object : VkEventHandlerAdapter() { 115 | override fun processMessage(message: Message) { 116 | println("Событие получено. Group ID: ${message.sourcePeerId} текст: ${message.text}") 117 | if (message.text == "пинг") 118 | api.messages.send(message.peerId, "ПОНГ!") 119 | } 120 | } 121 | 122 | val listener = VkCallbackGroup(cbEngine, VkCallbackGroup.defaultUpdateProcessor(messageHandler)) 123 | listener.run() 124 | ``` 125 | Также смотрите более развёрнутый пример использования `VkPollingCallback` [iris.vk.test/group_cb_multibot.kt](https://github.com/iris2iris/iris-vk-api/blob/master/test/iris/vk/test/group_cb_multibot.kt) 126 | 127 | ### VkCommandHandler 128 | 129 | Возможность добавлять обработчики каждой текстовой команды отдельным обработчиком 130 | ```kotlin 131 | val commandsHandler = VkCommandHandler() 132 | 133 | commandsHandler += CommandMatcherSimple("пинг") { 134 | vk.messages.send(it.peerId, "ПОНГ!") 135 | } 136 | 137 | commandsHandler += CommandMatcherRegex("рандом (\\d+) (\\d+)") { vkMessage, params -> 138 | 139 | var first = params[1].toInt() 140 | var second = params[2].toInt() 141 | if (second < first) 142 | first = second.also { second = first } 143 | 144 | vk.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 145 | } 146 | 147 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 148 | val listener = VkPollingGroup(token, commandsHandler) 149 | listener.run() 150 | ``` 151 | 152 | ##### Настройка карты команд с помощью DSL 153 | ```kotlin 154 | val commandsHandler = VkCommandHandler() 155 | 156 | // Конфигурирование команд в стиле DSL 157 | commandsHandler += commands { 158 | "пинг" runs { 159 | api.messages.send(it.peerId, "ПОНГ!") 160 | } 161 | 162 | "мой ид" runs { 163 | api.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 164 | } 165 | 166 | regex("""рандом (\d+) (\d+)""") runs { vkMessage, params -> 167 | 168 | var first = params[1].toInt() 169 | var second = params[2].toInt() 170 | if (second < first) 171 | first = second.also { second = first } 172 | 173 | api.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 174 | } 175 | } 176 | 177 | // Передаём в параметрах слушателя событий токен и созданный обработчик команд 178 | val listener = VkPollingGroup(token, commandsHandler) 179 | listener.run() 180 | ``` 181 | 182 | ### Настройка обработки событий методом onXxx 183 | ```kotlin 184 | // Определяем обработчик триггеров 185 | val triggerHandler = VkTriggerEventHandler() 186 | 187 | // можно настраивать лямбдами 188 | triggerHandler.onMessage { 189 | for (message in it) 190 | println("Получено сообщение от ${message.peerId}: ${message.text}") 191 | } 192 | 193 | triggerHandler.onMessageEdit { 194 | for (message in it) 195 | println("Сообщение исправлено ${message.id}: ${message.text}") 196 | } 197 | 198 | // можно настраивать классами триггеров 199 | triggerHandler += VkCommandHandler( 200 | commands = listOf( 201 | CommandMatcherSimple("пинг") { 202 | vk.messages.send(it.peerId, "ПОНГ!") 203 | }, 204 | 205 | CommandMatcherSimple("мой ид") { 206 | vk.messages.send(it.peerId, "Ваш ID равен: ${it.fromId}") 207 | }, 208 | 209 | CommandMatcherRegex("""рандом (\d+) (\d+)""") { vkMessage, params -> 210 | 211 | var first = params[1].toInt() 212 | var second = params[2].toInt() 213 | if (second < first) 214 | first = second.also { second = first } 215 | 216 | vk.messages.send(vkMessage.peerId, "🎲 Случайное значение в диапазоне [$first..$second] выпало на ${(first..second).random()}") 217 | } 218 | ) 219 | ) 220 | ``` 221 | 222 | Все приведённые выше примеры доступны в пакете [iris.vk.test](https://github.com/iris2iris/iris-vk-api/blob/master/test/iris/vk/test) 223 | 224 | ## Дополнительная информация 225 | 226 | **[Iris VK API](https://github.com/iris2iris/iris-vk-api)** использует библиотеку **[Iris JSON Parser](https://github.com/iris2iris/iris-json-parser-kotlin)** для обработки ответов от сервера VK. Загляните ознакомиться =) 227 | 228 | ### Благодарности 229 | Спасибо @markelovstyle за код-ревью, замечания и предложения 230 | 231 | ⭐ **Не забывайте поставить звёзды, если этот инструмент оказался вам полезен** 232 | 233 | --------------------------------------------------------------------------------