diff --git a/examples/pom.xml b/examples/pom.xml index f550c80..9fe8e48 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -33,6 +33,18 @@ wechaty ${wechaty.version} + + io.github.wechaty + wechaty-puppet-hostie + ${wechaty.version} + + + + io.github.wechaty + java-wechaty-plugin-contrib + 1.0.0-SNAPSHOT + + org.apache.logging.log4j log4j-api @@ -62,11 +74,7 @@ 1.7.30 - - io.github.wechaty - java-wechaty-plugin-contrib - 1.0.0-SNAPSHOT - + org.apache.commons commons-lang3 diff --git a/examples/src/main/java/io/github/wechaty/example/Main.java b/examples/src/main/java/io/github/wechaty/example/Main.java index 4a0f34e..d096cd7 100644 --- a/examples/src/main/java/io/github/wechaty/example/Main.java +++ b/examples/src/main/java/io/github/wechaty/example/Main.java @@ -2,6 +2,7 @@ import io.github.wechaty.Wechaty; +import io.github.wechaty.user.Contact; import io.github.wechaty.user.Room; import io.github.wechaty.utils.QrcodeUtils; import okhttp3.OkHttpClient; @@ -16,37 +17,24 @@ public class Main { public static void main(String[] args){ Wechaty bot = Wechaty.instance("your_token") - .onScan((qrcode, statusScanStatus, data) -> System.out.println(QrcodeUtils.getQr(qrcode))) - .onLogin(user -> System.out.println(user)) + .onScan((qrcode, statusScanStatus, data) -> { + System.out.println(QrcodeUtils.getQr(qrcode)); + System.out.println("Online Image: https://wechaty.github.io/qrcode/" + qrcode); + }) + .onLogin(user -> System.out.println(user.name() + "login")) .onMessage(message -> { Room room = message.room(); String text = message.text(); - if (StringUtils.equals(text, "#ding")) { + Contact from = message.from(); + if (StringUtils.equals(text, "ding")) { if (room != null) { room.say("dong"); } + else { + // say something to from contact + from.say("hello:" + from.name()); + } } }).start(true); - -// } - -// Room room = bot.room(); -// -// RoomQueryFilter roomQueryFilter = new RoomQueryFilter(); -// -// roomQueryFilter.setTopic("ChatOps - Donut"); -// -// Future> all = room.findAll(roomQueryFilter); -// -// List rooms = all.get(); -// -// Room room1 = rooms.get(0); -// -// FileBox fileBox = FileBox.fromFile("dong.jpg", "dong.jpg"); -// -// room1.say(fileBox).get(); - } - - } diff --git a/wechaty-puppet-hostie/pom.xml b/wechaty-puppet-hostie/pom.xml index 9742318..59be0cd 100644 --- a/wechaty-puppet-hostie/pom.xml +++ b/wechaty-puppet-hostie/pom.xml @@ -62,7 +62,6 @@ org.apache.commons commons-lang3 - diff --git a/wechaty-puppet-hostie/src/main/kotlin/io/github/wechaty/grpc/GrpcPuppet.kt b/wechaty-puppet-hostie/src/main/kotlin/io/github/wechaty/grpc/GrpcPuppet.kt index 7e61a7a..c6132dd 100644 --- a/wechaty-puppet-hostie/src/main/kotlin/io/github/wechaty/grpc/GrpcPuppet.kt +++ b/wechaty-puppet-hostie/src/main/kotlin/io/github/wechaty/grpc/GrpcPuppet.kt @@ -389,7 +389,6 @@ class GrpcPuppet(puppetOptions: PuppetOptions) : Puppet(puppetOptions) { } override fun contactRawPayload(contractId: String): Future { - val request = Contact.ContactPayloadRequest.newBuilder() .setId(contractId) .build() diff --git a/wechaty-puppet/aaaa.memory-card.json b/wechaty-puppet/aaaa.memory-card.json new file mode 100644 index 0000000..d0d386b --- /dev/null +++ b/wechaty-puppet/aaaa.memory-card.json @@ -0,0 +1 @@ +{"a":"b","\rparent\nmap":{"a":1,"b":2},"\rparent\nlist":["1","2","3"]} \ No newline at end of file diff --git a/wechaty-puppet/default.memory-card.json b/wechaty-puppet/default.memory-card.json new file mode 100644 index 0000000..7f228f9 --- /dev/null +++ b/wechaty-puppet/default.memory-card.json @@ -0,0 +1 @@ +{"a":"b"} \ No newline at end of file diff --git a/wechaty-puppet/parent.memory-card.json b/wechaty-puppet/parent.memory-card.json new file mode 100644 index 0000000..d0d386b --- /dev/null +++ b/wechaty-puppet/parent.memory-card.json @@ -0,0 +1 @@ +{"a":"b","\rparent\nmap":{"a":1,"b":2},"\rparent\nlist":["1","2","3"]} \ No newline at end of file diff --git a/wechaty-puppet/pom.xml b/wechaty-puppet/pom.xml index b68cd47..da7421e 100644 --- a/wechaty-puppet/pom.xml +++ b/wechaty-puppet/pom.xml @@ -21,7 +21,11 @@ org.jetbrains.kotlin kotlin-stdlib - + + org.jetbrains.kotlinx + kotlinx-coroutines-core + 1.3.7 + com.github.ben-manes.caffeine @@ -40,15 +44,11 @@ commons-lang3 - - - - - - - - - + + com.amazonaws + aws-java-sdk-s3 + 1.11.636 + @@ -105,7 +105,22 @@ okhttp - + + + com.huaweicloud + esdk-obs-java + 3.19.7 + + + com.aliyun.oss + aliyun-sdk-oss + 3.10.2 + + + junit + junit + test + diff --git a/wechaty-puppet/src/main/kotlin/Puppet.kt b/wechaty-puppet/src/main/kotlin/Puppet.kt index a91cdc1..fe12f5a 100644 --- a/wechaty-puppet/src/main/kotlin/Puppet.kt +++ b/wechaty-puppet/src/main/kotlin/Puppet.kt @@ -14,9 +14,11 @@ import io.github.wechaty.io.github.wechaty.watchdag.WatchDog import io.github.wechaty.io.github.wechaty.watchdag.WatchdogFood import io.github.wechaty.io.github.wechaty.watchdag.WatchdogListener import io.github.wechaty.listener.* +import io.github.wechaty.memorycard.MemoryCard import io.github.wechaty.schemas.* import io.github.wechaty.utils.FutureUtils import io.github.wechaty.utils.JsonUtils +import okhttp3.internal.toImmutableList import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory @@ -42,7 +44,7 @@ abstract class Puppet : EventEmitter { private val HEARTBEAT_COUNTER = AtomicLong() private val HOSTIE_KEEPALIVE_TIMEOUT = 15 * 1000L private val DEFAULT_WATCHDOG_TIMEOUT = 60L -// private var memory: MemoryCard + private var memory: MemoryCard private val executorService = Executors.newSingleThreadScheduledExecutor() @@ -61,15 +63,20 @@ abstract class Puppet : EventEmitter { /** * */ - constructor(puppetOptions: PuppetOptions) { count.addAndGet(1) this.puppetOptions = puppetOptions -// this.memory = MemoryCard() -// this.memory.load() - + // for test + this.memory = MemoryCard() + try { + this.memory.load() + log.debug("Puppet, constructor() memory.load() done") + } + catch (e: Exception) { + log.warn("Puppet, constructor() memory.load() rejection: {}", e) + } val timeOut = puppetOptions.timeout ?: DEFAULT_WATCHDOG_TIMEOUT watchDog = WatchDog(1000 * timeOut, "puppet") @@ -441,13 +448,13 @@ abstract class Puppet : EventEmitter { if (StringUtils.isNotBlank(query.id)) { stream = stream?.filter { - StringUtils.equals(query.alias, it.alias) + StringUtils.equals(query.id, it.id) } } if (StringUtils.isNotBlank(query.weixin)) { stream = stream?.filter { - StringUtils.equals(query.alias, it.alias) + StringUtils.equals(query.weixin, it.weixin) } } @@ -700,8 +707,46 @@ abstract class Puppet : EventEmitter { } - protected fun messageQueryFilterFactory(query: MessageQueryFilter) { - TODO() + protected fun messageQueryFilterFactory(query: MessageQueryFilter): MessagePayloadFilterFunction { + val clz = query::class.java + val fields = clz.fields + val list = fields.map { + it.name to it.get(query) + } + + var filterFunctionList = ArrayList() + + list.forEach { pair -> + if (StringUtils.isNotEmpty(pair.second.toString())) { + val filterFunction = if (StringUtils.equals(pair.first.toString(), "textReg")) { + { + payload: MessagePayload -> Boolean + val clazz = payload::class.java + val field = clazz.getField(pair.first) + val realValue = field.get(payload).toString() + Regex(pair.second.toString()).matches(realValue) + } + } + else { + { + payload: MessagePayload -> Boolean + val clazz = payload::class.java + val field = clazz.getField(pair.first) + val realValue = field.get(payload).toString() + StringUtils.equals(realValue, pair.second.toString()) + } + } + filterFunctionList.add(filterFunction) + } + } + val allFilterFunction: MessagePayloadFilterFunction = { + payload: MessagePayload -> + filterFunctionList.all { + func -> func(payload) + } + } + return allFilterFunction + } fun messageForward(conversationId: String, messageId: String): Future { @@ -819,37 +864,36 @@ abstract class Puppet : EventEmitter { abstract fun roomMemberList(roomId: String): Future> fun roomMemberSearch(roomId: String, query: RoomMemberQueryFilter): Future> { - TODO() - } - - fun roomReach(query: RoomQueryFilter?): Future> { - TODO() - } + return CompletableFuture.supplyAsync { + val roomMemberIdList = roomMemberList(roomId).get() - fun roomValidate(roomId: String): Future { - return CompletableFuture.completedFuture(true) - } + val contactQuery = if (query.name != null || query.contactAlias != null) { + val contactQueryFilter = ContactQueryFilter() + contactQueryFilter.name = query.name + contactQueryFilter + } + else { + val contactQueryFilter = ContactQueryFilter() + contactQueryFilter.alias = query.contactAlias + contactQueryFilter + } - fun roomPayloadCache(roomId: String): RoomPayload? { - return cacheRoomPayload.getIfPresent(roomId) - } + var idList = ArrayList(contactSearch(contactQuery, roomMemberIdList).get()) + val memberPayloadList = roomMemberIdList.mapNotNull { x -> roomMemberPayload(roomId, x).get() } - fun roomPayload(roomId: String): Future { - return CompletableFuture.supplyAsync { - return@supplyAsync cacheRoomPayload.get(roomId) { t: String -> - val get = roomRawPayload(t).get() - return@get roomRawPayloadParser(get).get() + if (query.roomAlias != null) { + val result = memberPayloadList.filter { payload -> + payload.roomAlias === query.roomAlias + }.map { payload -> + payload.id + } + idList.addAll(result) } + return@supplyAsync idList; } } - fun roomPayloadDirty(roomId: String): Future { - cacheRoomPayload.invalidate(roomId) - return CompletableFuture.completedFuture(null) - } - - fun roomSearch(query: RoomQueryFilter): Future> { return CompletableFuture.supplyAsync { val allRoomList = roomList().get() @@ -879,6 +923,30 @@ abstract class Puppet : EventEmitter { } } + fun roomValidate(roomId: String): Future { + return CompletableFuture.completedFuture(true) + } + + fun roomPayloadCache(roomId: String): RoomPayload? { + return cacheRoomPayload.getIfPresent(roomId) + } + + + fun roomPayload(roomId: String): Future { + return CompletableFuture.supplyAsync { + return@supplyAsync cacheRoomPayload.get(roomId) { t: String -> + val get = roomRawPayload(t).get() + return@get roomRawPayloadParser(get).get() + } + } + } + + fun roomPayloadDirty(roomId: String): Future { + cacheRoomPayload.invalidate(roomId) + return CompletableFuture.completedFuture(null) + } + + /** * Concat roomId & contactId to one string */ @@ -912,13 +980,15 @@ abstract class Puppet : EventEmitter { } } -// fun setMemory(memoryCard: MemoryCard){ -// this.memory = memoryCard -// } + fun setMemory(memoryCard: MemoryCard) { + log.debug("Puppet, setMemory()") -// fun getEventBus(): EventBus { -// return eb -// } + this.memory = memoryCard + } + + override fun toString(): String { + return "Puppet#${count}<${this.puppetOptions?.name}>(${this.memory.getName()})" + } companion object { private val log = LoggerFactory.getLogger(Puppet::class.java) diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/StateEnum.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/StateEnum.kt index fb54b6f..798027a 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/StateEnum.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/StateEnum.kt @@ -1,7 +1,7 @@ package io.github.wechaty -enum class StateEnum { +import io.github.wechaty.eventEmitter.Event +enum class StateEnum: Event { PENDING,ON,OFF; - -} \ No newline at end of file +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/eventEmitter/EventEmitter.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/eventEmitter/EventEmitter.kt index 9e60edf..27b532c 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/eventEmitter/EventEmitter.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/eventEmitter/EventEmitter.kt @@ -84,7 +84,6 @@ open class EventEmitter : EventEmitterInterface { } map.put(event, wrapListener) - } override fun removeAllListeners(event:Event): Boolean { diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/MemoryCard.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/MemoryCard.kt index e6864fd..f6753a3 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/MemoryCard.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/MemoryCard.kt @@ -1,89 +1,407 @@ -//package io.github.wechaty.memorycard -// -//import org.slf4j.LoggerFactory -//import java.util.concurrent.Future -// -//class MemoryCard{ -// -// private var name:String? = null -// protected var parent:MemoryCard? = null -// protected var payload:MemoryCardPayload ? = null -// protected var storage:StorageBackend? = null -// protected val multiplexNameList = mutableListOf() -// -// private var options:MemoryCardOptions -// -// constructor(name: String?=null,options:MemoryCardOptions? = null){ -// -// val _optiones:MemoryCardOptions = options ?: MemoryCardOptions() -// -// if(name != null){ -// if(options != null) { -// _optiones.name = name -// } -// } -// this.options = _optiones -// this.name = _optiones.name -// -// (_optiones.multiplex != null).let { -// this.parent = _optiones.multiplex!!.parent -// this.payload = this.parent!!.payload -// this.multiplexNameList.addAll(parent!!.multiplexNameList) -// this.multiplexNameList.add(_optiones.multiplex!!.name) -// this.storage = null -// } -// -// (_optiones.multiplex == null).let { -// this.payload = null -// this.multiplexNameList.clear() -// } -// } -// -// private fun getStore():StorageBackend?{ -// log.debug("getStorage() for storage type: %s'",this.options) -// -// return StorageBackend.getStorage( -// this.options.name!!, -// this.options.storageOptions -// ) -// } -// -// fun load(){ -// this.payload = this.storage!!.load() -// } -// -// fun save(){ -// this.storage!!.save(this.payload!!) -// } -// -// companion object{ -// private val log = LoggerFactory.getLogger(MemoryCard::class.java) -// -// fun multiplex(memory:MemoryCard,name:String):MemoryCard{ -// return MemoryCard() -// } -// } -// -// -//} -// -//class MemoryCardPayload{ -// val map:Map = HashMap() -//} -// -//data class Multiplex( -// val parent:MemoryCard, -// val name:String -//) -// -//class MemoryCardOptions{ -// -// var name:String? = null -// var storageOptions:StorageBackendOptions? = null -// var multiplex:Multiplex? = null -// -//} -// -// -// -// +package io.github.wechaty.memorycard + +import io.github.wechaty.utils.JsonUtils +import org.slf4j.LoggerFactory + +const val NAMESPACE_MULTIPLEX_SEPRATOR = "\r" +const val NAMESPACE_KEY_SEPRATOR = "\n" + +val NAMESPACE_MULTIPLEX_SEPRATOR_REGEX = Regex(NAMESPACE_MULTIPLEX_SEPRATOR) +val NAMESPACE_KEY_SEPRATOR_REGEX = Regex(NAMESPACE_KEY_SEPRATOR) + +// 名字可以由options传入也可以直接传入 +class MemoryCard { + + private var name:String? = null + protected var parent:MemoryCard? = null + protected var payload:MemoryCardPayload ? = null + protected var storage:StorageBackend? = null + protected val multiplexNameList = mutableListOf() + + // 是否是multiplex,用哪个存储后端,memorycard名字是什么 + private var options:MemoryCardOptions + + // name和options里面的name有可能同时为空 + constructor(name: String? = null, options: MemoryCardOptions? = null) { + val _options: MemoryCardOptions = options ?: MemoryCardOptions() + log.info("MemoryCard, constructor({})", JsonUtils.write(_options)) + + if(name != null) { + _options.name = name + } + + else if (_options.name != null) { + this.name = _options.name + } + else { + // 如果没有给名字就用一个default作为名字 + this.name = "default" + _options.name = "default" + } + this.options = _options + + if (_options.multiplex != null) { + this.parent = _options.multiplex!!.parent + this.payload = this.parent!!.payload + + this.multiplexNameList.addAll(parent!!.multiplexNameList) + this.multiplexNameList.add(_options.multiplex!!.name) + + this.storage = null + } + else { + this.payload = null + this.multiplexNameList.clear() + this.storage = getStore() + } + } + + private fun getStore(): StorageBackend? { + + log.debug("getStorage() for storage type: {}", + if (this.options != null && this.options.storageOptions != null && this.options.storageOptions!!.type != null) + this.options + else + "N/A" + ) + + if (this.options == null) { + return null + } + + // 默认得到一个file的后端 + return StorageBackend.getStorage( + this.options.name!!, + this.options.storageOptions + ) + } + + suspend fun loadAsync() { + log.info("MemoryCard, load() from storage: {}", this.storage ?: "N/A") + if (this.isMultiplex()) { + log.info("MemoryCard, load() should not be called on a multiplex MemoryCard. NOOP") + return + } + if (this.payload != null) { + throw Exception("memory had already loaded before.") + } + + if (this.storage != null) { + this.payload = this.storage!!.load() + } + else { + log.info("MemoryCard, load() no storagebackend") + this.payload = MemoryCardPayload() + } + } + + fun load() { + log.info("MemoryCard, load() from storage: {}", this.storage ?: "N/A") + if (this.isMultiplex()) { + log.info("MemoryCard, load() should not be called on a multiplex MemoryCard. NOOP") + return + } + if (this.payload != null) { + throw Exception("memory had already loaded before.") + } + + if (this.storage != null) { + this.payload = this.storage!!.load() + } + else { + log.info("MemoryCard, load() no storagebackend") + this.payload = MemoryCardPayload() + } + } + + fun save() { + if (this.isMultiplex()) { + if (this.parent == null) { + throw Exception("multiplex memory no parent") + } + this.parent!!.save() + } + + log.info("MemoryCard, <{}>{} save() to {}",this.name ?: "", this.multiplexPath(), this.storage ?: "N/A") + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + if (this.storage == null) { + log.info("MemoryCard, save() no storage, NOOP") + return + } + + this.storage!!.save(this.payload!!) + } + + fun destory() { + log.info("MemoryCard, destroy() storage: {}", this.storage ?: "N/A") + if (this.isMultiplex()) { + throw Exception("can not destroy on a multiplexed memory") + } + + // this.clear() + + if (this.storage != null) { + this.storage!!.destory() + this.storage = null + } + this.payload = null + } + + + fun size(): Int { + log.info("MemoryCard, <{}> size", this.multiplexPath()) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + val count: Int + if (this.isMultiplex()) { + count = this.payload!!.map.keys + .filter { key -> this.isMultiplexKey(key) } + .size + } + else { + count = this.payload!!.map.size + } + + return count + } + + + fun isMultiplex (): Boolean { + return this.multiplexNameList.size > 0 + } + + protected fun multiplexPath(): String { + return this.multiplexNameList.joinToString("/") + } + + protected fun multiplexNamespace(): String { + if (!this.isMultiplex()) { + throw Exception("not a multiplex memory") + } + + val namespace = NAMESPACE_MULTIPLEX_SEPRATOR + + this.multiplexNameList.joinToString(NAMESPACE_MULTIPLEX_SEPRATOR) + return namespace + } + + protected fun isMultiplexKey(key: String): Boolean { + + if (NAMESPACE_MULTIPLEX_SEPRATOR_REGEX.containsMatchIn(key) + && NAMESPACE_KEY_SEPRATOR_REGEX.containsMatchIn(key)) { + + val namespace = this.multiplexNamespace() + return key.startsWith(namespace) + } + return false + } + + protected fun resolveKey (name: String): String { + + if (this.isMultiplex()) { + val namespace = this.multiplexNamespace() + return namespace + NAMESPACE_KEY_SEPRATOR + name + } + else { + return name + } + } + + fun get(name: String): Any? { + log.info("MemoryCard, <{}> get({})", this.multiplexPath(), name) + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + val key = this.resolveKey(name) + return this.payload!!.map.get(key) + } + /** + * 功能描述: + * + * @Param: + * @Return: + * @Author: a1725 + * @Date: 2020/7/18 23:54 + */ + fun set(name: String, data: T) { + log.info("MemoryCard, <{}> set({}, {})", this.multiplexPath(), name, data) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + val key = this.resolveKey(name) + this.payload!!.map[key] = data as Any + } + + fun clear() { + log.info("MemoryCard, <{}> clear()", this.multiplexPath()) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + if (this.isMultiplex()) { + val keys = this.payload!!.map.keys + for (key in keys) { + this.payload!!.map.remove(key) + } + } + else { + this.payload = MemoryCardPayload() + } + } + + fun delete(name: String) { + log.info("MemoryCard, <{}> delete({})", this.multiplexPath(), name) + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + val key = this.resolveKey(name) + this.payload!!.map.remove(key) + } + + fun entries(): MutableSet> { + log.info("MemoryCard, <{}> *entries()", this.multiplexPath()) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + return this.payload!!.map.entries + } + + fun has(key: String): Boolean { + log.info("MemoryCard, <{}> has ({})", this.multiplexPath(), key) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + val absoluteKey = this.resolveKey(key) + return this.payload!!.map.containsKey(absoluteKey) + } + + fun keys(): MutableSet { + log.info("MemoryCard, <{}> keys()", this.multiplexPath()) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + var result = mutableSetOf() + for (key in this.payload!!.map.keys) { + if (this.isMultiplex()) { + if (this.isMultiplexKey(key)) { + val namespace = this.multiplexNamespace() + val mpKey = key.substring(namespace.length + 1) + result.add(mpKey) + } + continue + } + result.add(key) + } + return result + } + + + fun values(): MutableCollection { + log.info("MemoryCard, <{}> values()", this.multiplexPath()) + + if (this.payload == null) { + throw Exception("no payload, please call load() first.") + } + + return this.payload!!.map.values + } + + + + override fun toString(): String { + var mpString = "" + if (this.multiplexNameList.size > 0) { + mpString = this.multiplexNameList + .map { mpName -> "multiplex(${mpName})" } + .joinToString("") + } + var name = "" + if (this.options != null && this.options.name != null) { + name = this.options.name.toString() + } + + return "MemoryCard<${name}>${mpString}" + } + + fun getVersion(): String { + return VERSION + } + + fun getName(): String? { + return this.name + } + // 会将当前的类作为parent, 后面那个为namespace + fun multiplex(nameSpace: String): MemoryCard { + log.info("MemoryCard, multiplex({})", nameSpace) + return multiplex(this, nameSpace) + } + + protected fun multiplex(parent: MemoryCard, nameSpace: String): MemoryCard { + log.info("MemoryCard, multiplex({}, {})", parent, nameSpace) + parent.options.name = parent.name + parent.options.multiplex = Multiplex(name = nameSpace, parent = parent) + val mpMemory = MemoryCard(options = parent.options) + return mpMemory + } + + companion object { + private val log = LoggerFactory.getLogger(MemoryCard::class.java) + val VERSION = "0.0.0" + + fun fromJSON(text: String): MemoryCard { + log.info("MemoryCard, fromJSON(...)") + var jsonObj: MemoryCardJsonObject + jsonObj = JsonUtils.readValue(text) + val card = MemoryCard(jsonObj.options.name, jsonObj.options) + card.payload = jsonObj.payload + return MemoryCard() + } + + fun fromJSON(obj: MemoryCardJsonObject): MemoryCard { + log.info("MemoryCard, fromJSON(...)") + val card = MemoryCard(obj.options.name, obj.options) + card.payload = obj.payload + + return card + } + } +} + +class MemoryCardPayload{ + var map = mutableMapOf() +} + +data class Multiplex( + val parent: MemoryCard, + val name: String +) + +data class MemoryCardOptions( + var name: String? = null, + var storageOptions:StorageBackendOptions? = null, + var multiplex:Multiplex? = null +) + +data class MemoryCardJsonObject( + val payload: MemoryCardPayload, + val options: MemoryCardOptions +) + + + + + diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageFile.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageFile.kt deleted file mode 100644 index f4647a8..0000000 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageFile.kt +++ /dev/null @@ -1,58 +0,0 @@ -//package io.github.wechaty.memorycard -// -//import io.github.wechaty.utils.JsonUtils -//import org.apache.commons.io.FileUtils -//import org.apache.commons.io.FilenameUtils -//import org.apache.commons.lang3.StringUtils -//import java.io.File -// -//class StorageFile(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { -// -// private var absFileName:String -// -// init { -// options.type = "file" -// options = options as StorageFileOptions -// val file = File(name) -// if(file.isAbsolute){ -// this.absFileName = name -// }else{ -// this.absFileName = FilenameUtils.concat(System.getProperty("user.dir"),name) -// } -// -// if(!StringUtils.endsWith(this.absFileName,".memory-card.json")){ -// this.absFileName += ".memory-card.json" -// } -// -// } -// -// override fun save(payload: MemoryCardPayload) { -// val text = JsonUtils.write(payload) -// val file = File(absFileName) -// FileUtils.write(file,text,"UTF-8") -// -// } -// -// override fun load(): MemoryCardPayload { -// val file = File(absFileName) -// if(!file.exists()){ -// return MemoryCardPayload() -// } -// val text = FileUtils.readFileToString(file, "UTF-8") -// return JsonUtils.readValue(text); -// -// } -// -// override fun destory() { -// TODO("Not yet implemented") -// } -// -//} -// -// -// -//fun main(){ -// -// StorageFile("test",StorageFileOptions()) -// -//} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageOptions.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageOptions.kt index f2d32a8..8cb4043 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageOptions.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageOptions.kt @@ -1,30 +1,42 @@ -//package io.github.wechaty.memorycard -// -//sealed class StorageBackendOptions{ -// var type:String?=null -//} -// -//data class StorageS3Options( -// val accessKeyId:String, -// val secretAccessKey:String, -// val region:String, -// val bucket:String -//):StorageBackendOptions() -// -// -//data class StorageObsOptions( -// val accessKeyId:String, -// val secretAccessKey:String, -// val server:String, -// val bucket:String -//):StorageBackendOptions() -// -//class StorageNopOptions: StorageBackendOptions() { -// var placeholder: String? = null -//} -// -//typealias StorageFileOptions = StorageNopOptions -// -//val BACKEND_DICT = mapOf( -// "file" to StorageFile::class -//) +package io.github.wechaty.memorycard + +import io.github.wechaty.memorycard.backend.* + + +sealed class StorageBackendOptions { + var type: String? = null +} + +class StorageNopOptions: StorageBackendOptions() + +typealias StorageFileOptions = StorageNopOptions + +data class StorageS3Options( + val accessKeyId:String, + val secretAccessKey:String, + val region:String, + val bucket:String +): StorageBackendOptions() + + +data class StorageObsOptions( + val accessKeyId:String, + val secretAccessKey:String, + val server:String, + val bucket:String +): StorageBackendOptions() + +data class StorageOSSOptions( + val accessKeyId: String, + val secretAccessKey: String, + val endPoint: String, + val bucket: String +): StorageBackendOptions() + +val BACKEND_DICT = mapOf( + "file" to StorageFile::class, + "s3" to StorageS3::class, + "nop" to StorageNop::class, + "obs" to StorageObs::class, + "oss" to StorageOSS::class +) diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StrorageBackend.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StrorageBackend.kt index 6879ab5..ed1020c 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StrorageBackend.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StrorageBackend.kt @@ -1,39 +1,39 @@ -//package io.github.wechaty.memorycard -// -//import org.slf4j.LoggerFactory -//import kotlin.reflect.full.createInstance -//import kotlin.reflect.full.primaryConstructor -// -//abstract class StorageBackend(name: String, option: StorageBackendOptions) { -// -// init { -// log.debug("constructor({}, { type: {} })",name,option) -// } -// -// abstract fun save(payload: MemoryCardPayload) -// abstract fun load():MemoryCardPayload -// abstract fun destory() -// -// companion object{ -// private val log = LoggerFactory.getLogger(StorageBackend::class.java) -// -// fun getStorage(name: String,options: StorageBackendOptions?):StorageBackend{ -// -// var _options = options -// -// if(options == null) { -// _options = StorageFileOptions() -// _options.type = "file" -// } -// -// if(_options?.type == null || _options.type !in BACKEND_DICT.keys){ -// throw Exception("backed unknown : ${_options?.type}") -// } -// -// -// } -// -// } -// -//} -// +package io.github.wechaty.memorycard + +import io.github.wechaty.memorycard.backend.StorageFile +import io.github.wechaty.utils.JsonUtils +import org.slf4j.LoggerFactory + +abstract class StorageBackend(name: String, option: StorageBackendOptions) { + + init { + log.debug("constructor({}, { type: {} })", name, option) + } + + abstract fun save(payload: MemoryCardPayload) + abstract fun load():MemoryCardPayload + abstract fun destory() + + companion object{ + private val log = LoggerFactory.getLogger(StorageBackend::class.java) + + fun getStorage(name: String, options: StorageBackendOptions?): StorageBackend { + log.info("getStorage', name: {}, options: {}", name, options?.let { JsonUtils.write(it) }) + + var _options = options + + // 如果没有传option参数,默认用file后端 + if(options == null) { + _options = StorageFileOptions() + _options.type = "file" + } + + if(_options?.type == null || _options.type!! !in BACKEND_DICT.keys) { + throw Exception("backed unknown : ${_options?.type}") + } + return StorageFile(name, _options) + } + } + +} + diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageFile.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageFile.kt new file mode 100644 index 0000000..003ffb3 --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageFile.kt @@ -0,0 +1,108 @@ +package io.github.wechaty.memorycard.backend + +import io.github.wechaty.memorycard.MemoryCardPayload +import io.github.wechaty.memorycard.StorageBackend +import io.github.wechaty.memorycard.StorageBackendOptions +import io.github.wechaty.memorycard.StorageFileOptions +import io.github.wechaty.utils.JsonUtils +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FilenameUtils +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import java.io.File +import java.io.IOException +import java.lang.Exception + +class StorageFile(val name: String, var options: StorageBackendOptions) : StorageBackend(name, options) { + + private var absFileName: String + + init { + + log.info("StorageFile, constructor(%s, ...)", name) + + options.type = "file" + options = options as StorageFileOptions + + val file = File(name) + + if(file.isAbsolute) { + this.absFileName = name + } + else { + this.absFileName = FilenameUtils.concat(System.getProperty("user.dir"),name) + } + + if(!StringUtils.endsWith(this.absFileName,".memory-card.json")) { + this.absFileName += ".memory-card.json" + } + + } + + override fun load(): MemoryCardPayload { + log.info("StorageFile, load() from %s", this.absFileName) + + val file = File(absFileName) + if(!file.exists()){ + return MemoryCardPayload() + } + + var text = "" + try { + text = FileUtils.readFileToString(file, "UTF-8") + } + catch (e: IOException) { + log.error("load() from file %s error %s", this.absFileName, e.toString()) + } + + val payload = MemoryCardPayload() + try { + payload.map = JsonUtils.readValue(text) + } + catch (e: Exception) { + log.error("MemoryCard, load() exception: %s", e) + } + return payload + } + + override fun save(payload: MemoryCardPayload) { + log.info("StorageFile, save() to %s", this.absFileName) + + val text = JsonUtils.write(payload.map) + val file = File(absFileName) + try { + FileUtils.write(file,text,"UTF-8") + } + catch (e: IOException) { + log.error("MemoryCard, save() exception: %s", e) + } + } + + override fun destory() { + log.info("StorageFile, destoay()") + + val file = File(absFileName) + if (file.exists()) { + val deleteQuietly = FileUtils.deleteQuietly(file) + if (deleteQuietly) { + log.info("destory() ${this.absFileName} success") + } + else { + log.warn("destory() ${this.absFileName} failed") + } + } + } + + override fun toString(): String { + return "${this.name}<${this.absFileName}>" + } + companion object { + private val log = LoggerFactory.getLogger(StorageFile::class.java) + } +} + + + +fun main() { + val storageFile = StorageFile("test", StorageFileOptions()) +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageNop.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageNop.kt new file mode 100644 index 0000000..db1b20c --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageNop.kt @@ -0,0 +1,41 @@ +package io.github.wechaty.memorycard.backend + +import io.github.wechaty.memorycard.* +import org.slf4j.LoggerFactory + +class StorageNop(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { + + init { + log.info("StorageNop, constructor(%s, ...)", name) + options.type = "nop" + options = options as StorageNopOptions + } + + override fun load(): MemoryCardPayload { + log.info("StorageNop, load()") + return MemoryCardPayload() + } + + override fun save(payload: MemoryCardPayload) { + log.info("StorageNop, save()") + } + + override fun destory() { + log.info("StorageNop, destroy()") + } + + override fun toString(): String { + return "${this.name} " + } + + companion object { + private val log = LoggerFactory.getLogger(StorageNop::class.java) + } +} + + + +fun main() { + val storageNop = StorageNop("test", StorageNopOptions()) + println(storageNop) +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageOSS.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageOSS.kt new file mode 100644 index 0000000..fa2cc7c --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageOSS.kt @@ -0,0 +1,112 @@ +package io.github.wechaty.memorycard.backend + +import com.aliyun.oss.OSSClient +import com.aliyun.oss.OSSClientBuilder +import com.aliyun.oss.model.PutObjectRequest +import io.github.wechaty.memorycard.* +import io.github.wechaty.utils.JsonUtils +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +// 使用阿里的云存储服务 +class StorageOSS(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { + + private var oss: OSSClient + + init { + log.info("StorageOSS, constructor()") + options.type = "oss" + options = options as StorageOSSOptions + val _options = options as StorageOSSOptions + this.oss = OSSClientBuilder().build(_options.endPoint,_options.accessKeyId, _options.secretAccessKey) as OSSClient + } + + override fun save(payload: MemoryCardPayload) { + log.info("StorageOSS, save()") + this.putObject(payload) + } + + override fun load(): MemoryCardPayload { + log.info("StorageOSS, load()") + val card = this.getObject() + log.info("press", card) + return card + } + + override fun destory() { + log.info("StorageOSS, destroy()") + this.deleteObject() + } + + private fun putObject(payload: MemoryCardPayload) { + val options = this.options as StorageOSSOptions + + val putObjectRequest = PutObjectRequest(options.bucket, this.name, ByteArrayInputStream(JsonUtils.write(payload.map).toByteArray())) + try { + this.oss.putObject(putObjectRequest) + } + catch (e: Exception) { + log.error("上传${this.name}错误") + } + } + + private fun getObject(): MemoryCardPayload { + val options = this.options as StorageOSSOptions + val ossObject = try { + this.oss.getObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("获取文件${this.name}失败") + null + } + if (ossObject == null) { + return MemoryCardPayload() + } + val input = ossObject.objectContent + val byte = ByteArray(1024) + val bos = ByteArrayOutputStream() + var len = 0; + while (true) { + len = input.read(byte) + if (len != -1) { + bos.write(byte, 0, len) + } + else { + break + } + } + input.close() + ossObject.close() + val card = MemoryCardPayload() + card.map = JsonUtils.readValue(String(bos.toByteArray())) + return card + } + + private fun deleteObject() { + val options = this.options as StorageOSSOptions + try { + this.oss.deleteObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("删除${this.name}错误") + } + } + + fun shutdown() { + log.info("StorageOSS, shutdown()") + this.oss.shutdown() + } + + override fun toString(): String { + return "${this.name}<${this.name}>" + } + + companion object { + private val log = LoggerFactory.getLogger(StorageObs::class.java) + } +} + +fun main() { + +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageObs.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageObs.kt new file mode 100644 index 0000000..71153b7 --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageObs.kt @@ -0,0 +1,131 @@ +package io.github.wechaty.memorycard.backend + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.regions.Regions +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import com.obs.services.ObsClient +import io.github.wechaty.memorycard.* +import io.github.wechaty.utils.JsonUtils +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FilenameUtils +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File + +// 使用华为的云存储服务 +class StorageObs(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { + + private var obs: ObsClient + + init { + log.info("StorageObs, constructor()") + options.type = "obs" + options = options as StorageObsOptions + var _options = options as StorageObsOptions + this.obs = ObsClient(_options.accessKeyId, _options.secretAccessKey, _options.server) + } + + override fun save(payload: MemoryCardPayload) { + log.info("StorageObs, save()") + this.putObject(payload) + } + + override fun load(): MemoryCardPayload { + log.info("StorageObs, load()") + val card = this.getObject() + log.info("press", card) + return card + } + + override fun destory() { + log.info("StorageObs, destroy()") + this.deleteObject() + } + + private fun putObject(payload: MemoryCardPayload) { + val options = this.options as StorageObsOptions + + val putObject = this.obs.putObject(options.bucket, this.name, ByteArrayInputStream(JsonUtils.write(payload.map).toByteArray())) + if (putObject.statusCode >= 300) { + throw Exception("obs putObject error") + } + } + + private fun getObject(): MemoryCardPayload { + val options = this.options as StorageObsOptions + + val obsObject = try { + this.obs.getObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("获取${name}错误") + null + } + + if (obsObject == null) { + return MemoryCardPayload() + } + val input = obsObject.objectContent + var byte = ByteArray(1024) + val bos = ByteArrayOutputStream() + var len = 0; + while (true) { + len = input.read(byte) + if (len != -1) { + bos.write(byte, 0, len) + } + else { + break + } + } + + input.close() + var card = MemoryCardPayload() + card.map = JsonUtils.readValue(String(bos.toByteArray())) + return card + } + + private fun deleteObject() { + val options = this.options as StorageObsOptions + val deleteObject = try { + this.obs.deleteObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("删除${name}错误") + null + } + if (deleteObject == null) { + throw Exception("obs deleteObject error") + } + if (deleteObject.statusCode >= 300) { + throw Exception("obs deleteObject error") + } + } + override fun toString(): String { + return "${this.name}<${this.name}>" + } + + fun shutdown() { + log.info("StorageObs, shutdown()") + this.obs.close() + } + + companion object { + private val log = LoggerFactory.getLogger(StorageObs::class.java) + } +} + + + +fun main(){ + val storageObsOptions = StorageObsOptions("aaa", "aaa", + "aaa", "aaa") + + val storageObs = StorageObs("notexist", storageObsOptions) + val load = storageObs.load() + +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageS3.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageS3.kt new file mode 100644 index 0000000..aa7522e --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/backend/StorageS3.kt @@ -0,0 +1,103 @@ +package io.github.wechaty.memorycard.backend + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import io.github.wechaty.memorycard.* +import io.github.wechaty.utils.JsonUtils +import org.slf4j.LoggerFactory + +class StorageS3(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { + + private var s3: AmazonS3 + + init { + log.info("StorageS3, constructor()") + options.type = "s3" + options = options as StorageS3Options + val _options = options as StorageS3Options + + val basicAWSCredentials = BasicAWSCredentials(_options.accessKeyId, _options.secretAccessKey) + this.s3 = AmazonS3ClientBuilder.standard().withCredentials(AWSStaticCredentialsProvider(basicAWSCredentials)) + .withRegion(_options.region).build() + } + + override fun save(payload: MemoryCardPayload) { + log.info("StorageS3, save()") + val options = this.options as StorageS3Options + try { + this.s3.putObject(JsonUtils.write(payload.map), options.bucket, this.name) + } + catch (e: Exception) { + log.error("上传文件:${this.name}错误") + } + } + + override fun load(): MemoryCardPayload { + log.info("StorageS3, load()") + + val options = this.options as StorageS3Options + val result = try { + this.s3.getObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("获取文件:${this.name}错误") + null + } + + if (result == null || result.objectContent == null) { + return MemoryCardPayload() + } + val objectContent = result.objectContent + + var payloadMap = StringBuffer() + var readBuf = ByteArray(1024) + var readLen = 0 + while (true) { + readLen = objectContent.read(readBuf) + if (readLen > 0) { + payloadMap.append(String(readBuf, 0, readLen)) + } + else { + break + } + } + + var payload = MemoryCardPayload() + payload.map = JsonUtils.readValue(payloadMap.toString()) + return payload + } + + override fun destory() { + log.info("StorageS3, destory()") + val options = this.options as StorageS3Options + try { + this.s3.deleteObject(options.bucket, this.name) + } + catch (e: Exception) { + log.error("删除${this.name}错误") + } + } + + override fun toString(): String { + return "${this.name}<${this.name}>" + } + + fun shutdown() { + log.info("StorageS3, shutdown()") + this.s3.shutdown() + } + + companion object { + private val log = LoggerFactory.getLogger(StorageS3::class.java) + } + +} + +fun main(){ + +// val storageS3 = StorageS3("test", StorageS3Options("1", "1", "2", "3")) +// val load = storageS3.load() + +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Message.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Message.kt index 7b5e02d..d1cd7cc 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Message.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Message.kt @@ -61,6 +61,7 @@ class MessageQueryFilter { override fun toString(): String { return "MessageQueryFilter(fromId=$fromId, id=$id, roomId=$roomId, text=$text, toId=$toId, type=$type, textReg=$textReg)" } - - } + +typealias MessagePayloadFilterFunction = (payload: MessagePayload) -> Boolean +typealias MessagePayloadFilterFactory = (query: MessageQueryFilter) -> MessagePayloadFilterFunction diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateListener.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateListener.kt new file mode 100644 index 0000000..2c57339 --- /dev/null +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateListener.kt @@ -0,0 +1,8 @@ +package io.github.wechaty.io.github.wechaty.status + +import io.github.wechaty.StateEnum + +@FunctionalInterface +interface StateSwitchListener { + fun handler(state: StateEnum) +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateSwitch.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateSwitch.kt index f3aa3f8..acf78de 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateSwitch.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateSwitch.kt @@ -1,17 +1,19 @@ -package io.github.wechaty.io.github.wechaty.status +package io.github.wechaty.status import io.github.wechaty.StateEnum import io.github.wechaty.eventEmitter.EventEmitter +import io.github.wechaty.eventEmitter.Listener import io.github.wechaty.io.github.wechaty.schemas.EventEnum +import io.github.wechaty.io.github.wechaty.status.StateSwitchListener +import kotlinx.coroutines.* import org.slf4j.LoggerFactory -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Future +import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.atomic.AtomicInteger +import javax.swing.plaf.nimbus.State -val COUNTER=AtomicInteger() - -class StateSwitch:EventEmitter(){ - +var COUNTER = AtomicInteger() +val resolver: () -> Unit = {} +class StateSwitch(var name :String = "#${COUNTER.addAndGet(1)}"): EventEmitter(){ @Volatile private var onoff:Boolean = false @@ -19,42 +21,187 @@ class StateSwitch:EventEmitter(){ @Volatile private var pending:Boolean = false + private lateinit var onPromise : suspend () -> Unit + private lateinit var offPromise : suspend () -> Unit - private lateinit var onPromise : Future - private lateinit var offPromise : Future + private lateinit var onResolver : () -> Unit + private lateinit var offResolver : () -> Unit - private lateinit var onResolver : Function - private lateinit var offResolver : Function +// private var name :String = "#${COUNTER.addAndGet(1)}" + private val VERSION: String = "0.0.0" + private lateinit var onQueue: ArrayBlockingQueue<() -> Unit> + private lateinit var offQueue: ArrayBlockingQueue<() -> Unit> + init { + log.info("StateSwitch, constructor({})", this.name) - private val name :String = "#${COUNTER.addAndGet(1)}" + this.onoff = false + this.pending = false + onQueue = ArrayBlockingQueue(1) + offQueue = ArrayBlockingQueue(1) + this.offQueue.put(resolver) - init { - offPromise = CompletableFuture.completedFuture(null); - onPromise = CompletableFuture.runAsync{ - TODO() - } } - fun on(state: StateEnum):StateEnum{ - val on = on() - log.debug("statusSwitch $name on ${state.name} <- $on") + // 传入on的状态只能是on或者说pending + fun on(state: StateEnum): StateEnum { + log.debug("statusSwitch $name on ${state.name} <- ${this.on()}") + + if (state == StateEnum.OFF) { + throw Exception("the parameter state shouldn't be OFF") + } onoff = true pending = (state == StateEnum.PENDING) - emit(EventEnum.ON,state.name) - TODO() + emit(EventEnum.ON, state.name) + + if (this.offQueue.isEmpty()) { + this.offQueue.put(resolver) + } + + if (state == StateEnum.ON && this.onQueue.isEmpty()) { + this.onQueue.put(resolver) + } + return this.on() + + } + // get the current on state + fun on(): StateEnum { + val on = + if (this.onoff == true) + if (this.pending == true) + StateEnum.PENDING + else + StateEnum.ON + else + StateEnum.OFF + log.info("StateSwitch, <{}> on() is {}", this.name, on) + return on + } + + fun off(state: StateEnum): StateEnum { + log.info("StateSwitch, <{}> off({}) <- ({})", this.name, state, this.off()) + if (state == StateEnum.ON) { + throw Exception("the parameter state shouldn't be ON") + } + + this.onoff = false + this.pending = (state == StateEnum.PENDING) + this.emit(StateEnum.OFF, state) + + if (this.onQueue.isEmpty()) { + this.onQueue.put(resolver) + } + if (state == StateEnum.OFF && this.offQueue.isEmpty()) { + this.offQueue.put(resolver) + } + return this.off() } - fun on():StateEnum{ - TODO() + /** + * + * @return 返回当前off的状态 + */ + fun off(): StateEnum { + val off = + if (!this.onoff) + if (this.pending) + StateEnum.PENDING + else + StateEnum.OFF + else + StateEnum.ON + log.info("StateSwitch, <{}> off() is {}", this.name, off) + return off } - companion object{ - private val log = LoggerFactory.getLogger(StateSwitch::class.java) + /** + * @param state: 准备变为的状态 + * @param cross: 是否变换状态,默认可以 + * 好像可以去掉runblocking + */ + fun ready(state: StateEnum = StateEnum.ON, cross: Boolean = true) = runBlocking { + log.info("StateSwitch, <{}> ready({}, {})", name, state, cross) + + // 如果准备变换的状态为on + if (state == StateEnum.ON) { + // 如果当前状态为off,并且不允许变换状态 + if (onoff == false && cross == false) { + throw Exception("ready(on) but the state is off. call ready(on, true) to force crossWait") + } + // 当前状态为on + // 或者说当前状态为off, 但是允许变换状态 + // his.onPromise + CoroutineScope(Dispatchers.Default).launch { + onQueue.take() + } + } + // 如果准备变为off + else if (state == StateEnum.OFF) { + // 但是当前状态为 on, 并且不允许变换状态 + if (onoff == true && cross == false) { + throw Exception("ready(off) but the state is on. call ready(off, true) to force crossWait") + } + // 当前状态为off,或者说当前状态为on, 但是允许变换状态 + // 执行状态改变时执行的函数 + CoroutineScope(Dispatchers.Default).launch { + offQueue.take() + } + } + // 错误状态 + else { + throw Exception("should not go here. ${state} should be of type 'never'") + } + log.info("StateSwitch, <{}> ready({}, {}) resolved.", name, state, cross) + } + + fun addEventListener(type: StateEnum, listener: StateSwitchListener) { + super.addListener(type, object : Listener { + override fun handler(vararg any: Any) { + listener.handler(any[0] as StateEnum) + } + + }) } + fun removeEventListener(type: StateEnum, listener: StateSwitchListener) { + super.removeListener(type, object : Listener { + override fun handler(vararg any: Any) { + // 有问题 + listener.handler(any[0] as StateEnum) + } + }) + } + fun version(): String { + return this.VERSION + } + companion object { + private val log = LoggerFactory.getLogger(StateSwitch::class.java) + } } + +fun main() { + + val stateSwitch = StateSwitch() + println("刚开始创建时:" + stateSwitch.on()) + + stateSwitch.on(StateEnum.PENDING) + println("调用on(pending):" + stateSwitch.on()) + stateSwitch.ready(StateEnum.ON) + println("调用ready(on)之后:" + stateSwitch.on()) + stateSwitch.on(StateEnum.ON) + println("调用on(on):" + stateSwitch.on()) + // ====================================== +// stateSwitch.off(StateEnum.PENDING) +// println(stateSwitch.off()) +// +// stateSwitch.ready(StateEnum.OFF) +// println(stateSwitch.on()) +// +// stateSwitch.off(StateEnum.OFF) +// println(stateSwitch.on()) + +} diff --git a/wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonUtils.kt b/wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonUtils.kt index 70bd984..a775dc0 100644 --- a/wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonUtils.kt +++ b/wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonUtils.kt @@ -15,11 +15,10 @@ object JsonUtils { return mapper.readValue(json) } - fun write(input:Any):String{ + fun write(input:Any):String { return mapper.writeValueAsString(input) } - } diff --git a/wechaty-puppet/src/test/java/MemoryCardTest.java b/wechaty-puppet/src/test/java/MemoryCardTest.java new file mode 100644 index 0000000..7959595 --- /dev/null +++ b/wechaty-puppet/src/test/java/MemoryCardTest.java @@ -0,0 +1,20 @@ +import io.github.wechaty.memorycard.MemoryCard; +import org.junit.Test; + +import java.util.Map; +import java.util.concurrent.ExecutionException; + +public class MemoryCardTest { + + @Test + public void testLoad() throws ExecutionException, InterruptedException { + MemoryCard card = new MemoryCard("default", null); + card.load(); + Map map = (Map) card.get("person"); + if (map != null) { + map.forEach((key, value) -> { + System.out.println(key + ":" + value); + }); + } + } +} diff --git a/wechaty-puppet/test.memory-card.json b/wechaty-puppet/test.memory-card.json new file mode 100644 index 0000000..9d908d9 --- /dev/null +++ b/wechaty-puppet/test.memory-card.json @@ -0,0 +1 @@ +{"\rparent\nson":"b","list":[1,2,3]} \ No newline at end of file diff --git a/wechaty/pom.xml b/wechaty/pom.xml index 10aa138..3f46585 100644 --- a/wechaty/pom.xml +++ b/wechaty/pom.xml @@ -16,13 +16,17 @@ - io.github.wechaty wechaty-puppet ${project.version} - + + + io.github.wechaty + wechaty-puppet-hostie + ${project.version} + io.github.wechaty wechaty-puppet-hostie @@ -92,8 +96,6 @@ okhttp - - org.hamcrest hamcrest-core @@ -113,6 +115,7 @@ junit test + org.reflections reflections diff --git a/wechaty/src/main/kotlin/io/github/wechaty/Wechaty.kt b/wechaty/src/main/kotlin/io/github/wechaty/Wechaty.kt index 10c9997..ba9daa0 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/Wechaty.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/Wechaty.kt @@ -4,17 +4,21 @@ package io.github.wechaty; import io.github.wechaty.eventEmitter.Event import io.github.wechaty.eventEmitter.EventEmitter import io.github.wechaty.eventEmitter.Listener +import io.github.wechaty.filebox.FileBox import io.github.wechaty.io.github.wechaty.schemas.EventEnum import io.github.wechaty.listener.* -//import io.github.wechaty.memorycard.MemoryCard +import io.github.wechaty.memorycard.MemoryCard import io.github.wechaty.schemas.* +import io.github.wechaty.status.StateSwitch import io.github.wechaty.user.* import io.github.wechaty.user.manager.* import org.slf4j.LoggerFactory import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future import java.util.concurrent.locks.ReentrantLock - +val PUPPET_MEMORY_NAME = "puppet" class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : EventEmitter() { private val LOCK = ReentrantLock() @@ -26,12 +30,12 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : private val globalPluginList: MutableList = mutableListOf() @Volatile - private var readyState = StateEnum.OFF + private var readyState = StateSwitch("Wechaty") @Volatile - private var status = StateEnum.OFF + private var status = StateSwitch("WechatyReady") -// private var memory:MemoryCard? = null + private var memory: MemoryCard? = null val tagManager: TagManager = TagManager(this) val contactManager = ContactManager(this) @@ -39,18 +43,31 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : val roomManager = RoomManager(this) val roomInvitationManager = RoomInvitationManager(this) val imageManager = ImageManager(this) - val friendshipManager = FriendshipManager(this) + val friendShipManager = FriendshipManager(this) init { -// this.memory = wechatyOptions.memory + if (wechatyOptions.memory == null) { + this.memory = MemoryCard(wechatyOptions.name) + } + else { + this.memory = wechatyOptions.memory + } installGlobalPlugin() } fun start(await: Boolean = false):Wechaty { + if (this.status.on() == StateEnum.ON) { + this.status.ready(StateEnum.ON) + return this + } + + this.readyState.off(StateEnum.OFF) + this.status.on(StateEnum.PENDING) initPuppet() puppet.start().get() - status = StateEnum.ON +// status = StateEnum.ON + status.on(StateEnum.ON) emit(EventEnum.START, "") if (await) { @@ -69,7 +86,16 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : } fun stop() { +// if (this.status.off() == StateEnum.OFF) { +// this.status.ready(StateEnum.OFF) +// return +// } +// +// this.readyState.off(StateEnum.OFF) +// this.status.off(StateEnum.PENDING) puppet.stop() + +// this.status.off(StateEnum.OFF) } fun logout(){ @@ -99,12 +125,22 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : return on(EventEnum.LOGIN,listener) } + fun onLogout(listener: LogoutListener): Wechaty { + return on(EventEnum.LOGOUT, listener) + } fun onScan(listener: ScanListener):Wechaty{ return on(EventEnum.SCAN,listener); } + fun onReady(listener: ReadyListener): Wechaty { + return on(EventEnum.READY, listener) + } + fun onFriendship(listener: FriendshipListener): Wechaty { + return on(EventEnum.FRIENDSHIP, listener) + } + fun onRoomJoin(listener: RoomJoinListener):Wechaty { - return on(EventEnum.ROOM_JOIN,listener) + return on(EventEnum.ROOM_JOIN, listener) } fun onRoomLeave(listener: RoomLeaveListener):Wechaty { @@ -115,10 +151,16 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : return on(EventEnum.ROOM_TOPIC,listener) } + fun onRoomInvite(listener: RoomInviteListener): Wechaty { + return on(EventEnum.ROOM_INVITE, listener) + } fun onMessage(listener: MessageListener):Wechaty{ return on(EventEnum.MESSAGE,listener) } + fun onError(listener: ErrorListener): Wechaty { + return on(EventEnum.ERROR, listener) + } fun use(vararg plugins: WechatyPlugin):Wechaty{ plugins.forEach { it(this) @@ -140,11 +182,36 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : }) return this } + private fun on(event: Event,listener: ReadyListener):Wechaty{ + super.on(event, object : Listener { + override fun handler(vararg any: Any) { + listener.handler() + } + }) + return this + } + + private fun on(event: Event, listener: LogoutListener): Wechaty { + super.on(event, object : Listener { + override fun handler(vararg any: Any) { + listener.handler(any[0] as String, any[1] as String) + } + }) + return this + } private fun on(event: Event, listener: DongListener):Wechaty { return this } + private fun on(event: Event, listener: ErrorListener): Wechaty { + super.on(event, object : Listener { + override fun handler(vararg any: Any) { + listener.handler(any[0] as String) + } + }) + return this + } private fun on(event: Event, listener: ScanListener):Wechaty{ super.on(event, object : Listener { override fun handler(vararg any: Any) { @@ -162,7 +229,24 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : }) return this } + private fun on(event: Event, listener: FriendshipListener): Wechaty { + super.on(event, object : Listener { + override fun handler(vararg any: Any) { + listener.handler(any[0] as Friendship) + } + }) + return this + } + private fun on(eventName: Event, listener: RoomInviteListener): Wechaty { + super.on(eventName, object : Listener { + override fun handler(vararg any: Any) { + // roomInvitationId + listener.handler(any[0] as RoomInvitation) + } + }) + return this + } private fun on(eventName: Event, listener: RoomJoinListener):Wechaty { super.on(eventName, object : Listener { override fun handler(vararg any: Any) { @@ -192,6 +276,11 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : private fun initPuppet() { this.puppet = PuppetManager.resolveInstance(wechatyOptions).get() + if (this.memory == null) { + throw Exception("no memory") + } + val puppetMemory = this.memory!!.multiplex(PUPPET_MEMORY_NAME) + this.puppet.setMemory(puppetMemory) initPuppetEventBridge(puppet) } @@ -247,7 +336,7 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : EventEnum.FRIENDSHIP -> { puppet.on(it, object : PuppetFriendshipListener { override fun handler(payload: EventFriendshipPayload) { - val friendship = friendshipManager.load(payload.friendshipId) + val friendship = friendShipManager.load(payload.friendshipId) friendship.ready() emit(EventEnum.FRIENDSHIP, friendship) } @@ -268,7 +357,7 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : override fun handler(payload: EventLogoutPayload) { val contact = contactManager.loadSelf(payload.contactId) contact.ready() - emit(EventEnum.LOGOUT, contact, payload.data) + emit(EventEnum.LOGOUT, contact.id, payload.data) } }) } @@ -290,7 +379,8 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : puppet.on(it, object : PuppetReadyListener { override fun handler(payload: EventReadyPayload) { emit(EventEnum.READY); - readyState = StateEnum.ON +// readyState = StateEnum.ON + readyState.on(StateEnum.ON) } }) } @@ -331,7 +421,7 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : puppet.on(it, object : PuppetRoomLeaveListener { override fun handler(payload: EventRoomLeavePayload) { val room = roomManager.load(payload.roomId) - room.sync() + room.sync().get() val leaverList = payload.removeeIdList.map { id -> val contact = contactManager.loadSelf(id) @@ -353,12 +443,11 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : puppet.on(it, object : PuppetRoomTopicListener { override fun handler(payload: EventRoomTopicPayload) { val room = roomManager.load(payload.roomId) - room.sync() + room.sync().get() val changer = contactManager.loadSelf(payload.changerId) changer.ready() val date = Date(payload.timestamp * 1000) - emit(EventEnum.ROOM_TOPIC, room, payload.newTopic, payload.oldTopic, changer, date) room.emit(EventEnum.TOPIC, payload.newTopic, payload.oldTopic, changer, date) } @@ -417,6 +506,26 @@ class Wechaty private constructor(private var wechatyOptions: WechatyOptions) : } }, "StartMain-shutdown-hook")) } + +// override fun toString(): String { +// if (this.wechatyOptions == null) { +// return "default" +// } +// val first = if (this.wechatyOptions != null && this.puppet != null) { +// this.wechatyOptions.puppet +// } +// else { +// "puppet" +// } +// +// val second = if (this.memory != null) { +// this.memory!!.getName() +// } +// else { +// "default" +// } +// return "Wechaty#<${first}><${second}>" +// } } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/WechatyListener.kt b/wechaty/src/main/kotlin/io/github/wechaty/WechatyListener.kt index 8da5e0b..a76da29 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/WechatyListener.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/WechatyListener.kt @@ -1,10 +1,7 @@ package io.github.wechaty import io.github.wechaty.schemas.ScanStatus -import io.github.wechaty.user.Contact -import io.github.wechaty.user.ContactSelf -import io.github.wechaty.user.Message -import io.github.wechaty.user.Room +import io.github.wechaty.user.* import java.util.* @FunctionalInterface @@ -19,7 +16,7 @@ interface ErrorListener { @FunctionalInterface interface FriendshipListener { - fun handler(friendshipId: String) + fun handler(friendship: Friendship) } @FunctionalInterface @@ -66,7 +63,7 @@ interface RoomTopicListener { @FunctionalInterface interface RoomInviteListener { - fun handler(roomInvitationId: String) + fun handler(roomInvitation : RoomInvitation) } @FunctionalInterface @@ -77,6 +74,5 @@ interface ReadyListener { @FunctionalInterface interface MessageListener { fun handler(message: Message) - } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/WechatyOptions.kt b/wechaty/src/main/kotlin/io/github/wechaty/WechatyOptions.kt index d930bf7..5d337f1 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/WechatyOptions.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/WechatyOptions.kt @@ -1,11 +1,11 @@ package io.github.wechaty -//import io.github.wechaty.memorycard.MemoryCard +import io.github.wechaty.memorycard.MemoryCard import io.github.wechaty.schemas.PuppetOptions class WechatyOptions { -// var memory:MemoryCard? = null + var memory: MemoryCard? = null var name:String = "Wechaty" @@ -17,5 +17,5 @@ class WechatyOptions { var ioToken:String? = null } - typealias WechatyPlugin = (Wechaty) -> Unit +typealias WechatyPlugin = (Wechaty) -> Unit diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Contact.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Contact.kt index 8edd437..56c6b36 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Contact.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Contact.kt @@ -94,10 +94,41 @@ open class Contact(wechaty: Wechaty,val id:String) : Sayable, Accessory(wechaty) return (payload != null && StringUtils.isNotEmpty(payload!!.name)) } - fun name():String{ + fun name():String { return payload?.name ?: "" } + fun getName():String? { + return payload?.name + } + + fun address(): String { + return this.payload?.address ?: "" + } + + fun getAddress(): String? { + return this.payload?.address + } + + fun signature(): String { + return this.payload?.signature ?: "" + } + fun getSignature(): String? { + return this.payload?.signature + } + + fun star(): Boolean { + return this.payload?.star ?: false + } + + fun weixin(): String { + return this.payload?.weixin ?: "" + } + + fun getWeixin(): String? { + return this.payload?.weixin + } + fun setAlias(newAlias:String){ if(payload == null){ throw Exception("no payload") @@ -113,8 +144,8 @@ open class Contact(wechaty: Wechaty,val id:String) : Sayable, Accessory(wechaty) } - fun getAlias():String?{ - return payload?.alias ?:null + fun getAlias():String? { + return payload?.alias } fun stranger():Boolean?{ @@ -132,28 +163,54 @@ open class Contact(wechaty: Wechaty,val id:String) : Sayable, Accessory(wechaty) fun type():ContactType{ return payload?.type ?: throw Exception("no payload") } + fun getType(): ContactType { + return payload?.type ?: throw Exception("no payload") + } fun gender():ContactGender{ return payload?.gender ?: ContactGender.Unknown } + fun getGender():ContactGender{ + return payload?.gender ?: throw Exception("no payload") + } fun province():String?{ return payload?.province } + fun getProvince():String?{ + return payload?.province + } fun city():String?{ return payload?.city } + fun getCity():String?{ + return payload?.city + } + open fun avatar(): FileBox { try { return wechaty.getPuppet().getContactAvatar(this.id).get() } catch (e: Exception) { log.error("error",e) - TODO() + return qrCodeForChatie() + } + } + fun getAvatar(): FileBox { + try { + return wechaty.getPuppet().getContactAvatar(this.id).get() + } catch (e: Exception) { + log.error("error",e) + return qrCodeForChatie() } } + fun qrCodeForChatie (): FileBox { + val CHATIE_OFFICIAL_ACCOUNT_QRCODE = "http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5" + return FileBox.fromQRCode(CHATIE_OFFICIAL_ACCOUNT_QRCODE) + } + fun tags():List{ val tagIdList = wechaty.getPuppet().tagContactList(this.id).get() return try { @@ -165,8 +222,19 @@ open class Contact(wechaty: Wechaty,val id:String) : Sayable, Accessory(wechaty) listOf() } } + fun getTags():List { + val tagIdList = wechaty.getPuppet().tagContactList(this.id).get() + return try { + tagIdList.map { + wechaty.tagManager.load(it) + } + } catch (e: Exception) { + log.error("error",e) + listOf() + } + } - fun self():Boolean{ + fun self():Boolean { val userId = puppet.selfId() if(StringUtils.isEmpty(userId)){ return false diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/ContactSelf.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/ContactSelf.kt index bfb5d22..e1c859f 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/ContactSelf.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/ContactSelf.kt @@ -2,6 +2,8 @@ package io.github.wechaty.user import io.github.wechaty.Wechaty import io.github.wechaty.filebox.FileBox +import io.github.wechaty.utils.QrcodeUtils.Companion.guardQrCodeValue +import org.slf4j.LoggerFactory import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -11,6 +13,10 @@ class ContactSelf(wechaty: Wechaty, id: String) : Contact(wechaty, id) { puppet.setContactAvatar(super.id, fileBox) } + fun getAvatar(fileBox: FileBox) { + puppet.setContactAvatar(super.id, fileBox) + } + fun setName(name:String){ puppet.contactSelfName(name).get() sync() @@ -26,6 +32,91 @@ class ContactSelf(wechaty: Wechaty, id: String) : Contact(wechaty, id) { puppet.contactSelfSignature(signature).get() sync() } + } + fun setSignature(signature:String) { + + var puppetId:String? = puppet.selfId() + + let{ + puppetId != null + }.run { + puppet.contactSelfSignature(signature).get() + sync() + } + } + + fun qrcode(): Future { + log.info("Contact, qrcode()") + + val puppetId: String = try { + this.puppet.selfId().toString() + } + catch (e: Exception) { + throw Exception("Can not get qrcode, user might be either not logged in or already logged out") + } + + if (this.id !== puppetId) { + throw Exception("only can get qrcode for the login userself") + } + + return CompletableFuture.supplyAsync { + val qrcodeValue = this.puppet.contactSelfQRCode().get() + guardQrCodeValue(qrcodeValue) + } + } + fun getQrcode(): Future { + log.info("Contact, qrcode()") + + val puppetId: String = try { + this.puppet.selfId().toString() + } + catch (e: Exception) { + throw Exception("Can not get qrcode, user might be either not logged in or already logged out") + } + + if (this.id !== puppetId) { + throw Exception("only can get qrcode for the login userself") + } + + return CompletableFuture.supplyAsync { + val qrcodeValue = this.puppet.contactSelfQRCode().get() + guardQrCodeValue(qrcodeValue) + } + } + + fun name(name: String?): String? { + if (name == null) { + return super.name() + } + val puppetId = try { + this.puppet.selfId() + } catch (e: Exception) { + throw Exception("Can not get qrcode, user might be either not logged in or already logged out") + } + if (this.id !== puppetId) { + throw Exception("only can get qrcode for the login userself") + } + this.puppet.contactSelfName(name) + return null + } + + fun getName(name: String?): String? { + if (name == null) { + return super.name() + } + val puppetId = try { + this.puppet.selfId() + } catch (e: Exception) { + throw Exception("Can not get qrcode, user might be either not logged in or already logged out") + } + if (this.id !== puppetId) { + throw Exception("only can get qrcode for the login userself") + } + this.puppet.contactSelfName(name) + return null + } + companion object { + private val log = LoggerFactory.getLogger(ContactSelf::class.java) } } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Favorite.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Favorite.kt index 56f4897..de7d7a3 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Favorite.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Favorite.kt @@ -1,10 +1,10 @@ package io.github.wechaty.user import io.github.wechaty.Accessory +import io.github.wechaty.Puppet import io.github.wechaty.Wechaty +import io.github.wechaty.schemas.ContactPayload class Favorite(wechaty: Wechaty):Accessory(wechaty){ - - -} \ No newline at end of file +} diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Friendship.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Friendship.kt index 8afcb75..cfbf567 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Friendship.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Friendship.kt @@ -14,6 +14,22 @@ class Friendship (wechaty: Wechaty,val id:String):Accessory(wechaty){ private var payload:FriendshipPayload? = null + fun search(queryFilter: FriendshipSearchCondition):Contact?{ + val contactId = wechaty.getPuppet().friendshipSearch(queryFilter).get(); + if(StringUtils.isEmpty(contactId)){ + return null + } + val contact = wechaty.contactManager.load(contactId!!) + contact.ready() + return contact + } + + // 这个应该是静态方法吧 + fun add(contact: Contact, hello:String){ + log.debug("add contact: {} hello: {}",contact,hello) + wechaty.getPuppet().friendshipAdd(contact.id!!,hello).get() + } + fun isReady():Boolean{ return payload != null } @@ -24,7 +40,6 @@ class Friendship (wechaty: Wechaty,val id:String):Accessory(wechaty){ } this.payload = wechaty.getPuppet().friendshipPayload(id!!).get() contact().ready() - } fun contact():Contact{ @@ -55,17 +70,20 @@ class Friendship (wechaty: Wechaty,val id:String):Accessory(wechaty){ return this.payload?.hello ?: ""; } - fun type():FriendshipType{ + fun type(): FriendshipType { return this.payload?.type ?:FriendshipType.Unknown } + fun getType(): FriendshipType { + return this.payload?.type ?: throw Exception("ne payload") + } + fun toJson():String{ if(payload==null){ throw Exception("ne payload") } return JsonUtils.write(payload!!); } - companion object{ private val log = LoggerFactory.getLogger(Friendship::class.java) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Message.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Message.kt index 7dd15c9..f1b30ee 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Message.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Message.kt @@ -7,9 +7,11 @@ import io.github.wechaty.schemas.MessagePayload import io.github.wechaty.schemas.MessageType import io.github.wechaty.schemas.RoomMemberQueryFilter import io.github.wechaty.type.Sayable +import io.grpc.netty.shaded.io.netty.util.concurrent.CompleteFuture import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory +import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import java.util.regex.Pattern @@ -116,6 +118,9 @@ open class Message(wechaty: Wechaty,val id: String) : Sayable, Accessory(wechaty } return this.payload?.type ?: MessageType.Unknown } + fun getType(): MessageType{ + return this.payload?.type ?: throw Exception("no payload") + } fun self():Boolean{ val selfId = puppet.selfId() @@ -124,6 +129,45 @@ open class Message(wechaty: Wechaty,val id: String) : Sayable, Accessory(wechaty return StringUtils.equals(selfId,from?.id) } + fun date(): Date { + val payload = wechaty.getPuppet().messagePayload(this.id).get() + return Date(payload.timestamp!! * 1000) + } + + fun getDate(): Date { + val payload = wechaty.getPuppet().messagePayload(this.id).get() + return Date(payload.timestamp!! * 1000) + } + + fun age(): Long { + val ageMilliseconds = Date().time - this.date().time + val ageSeconds = Math.floor(ageMilliseconds / 1000.0).toLong() + return ageSeconds + } + + fun getAge(): Long { + val ageMilliseconds = Date().time - this.date().time + val ageSeconds = Math.floor(ageMilliseconds / 1000.0).toLong() + return ageSeconds + } + + fun forward(to: Any): Future { + log.debug("Message, forward({})", to) + when(to) { + is Room -> { + this.puppet.messageForward(to.id, this.id).get() + } + is Contact -> { + this.puppet.messageForward(to.id, this.id).get() + } + else -> { + throw Exception("unkown forward type") + } + } + return CompletableFuture.completedFuture(null) + } + + fun mentionList():List{ val room = this.room() @@ -171,7 +215,6 @@ open class Message(wechaty: Wechaty,val id: String) : Sayable, Accessory(wechaty return text() } - fun ready():Future{ return CompletableFuture.supplyAsync { diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/MiniProgram.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/MiniProgram.kt index c5217cb..8243a20 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/MiniProgram.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/MiniProgram.kt @@ -35,11 +35,10 @@ class MiniProgram(var payload: MiniProgramPayload) { companion object{ - fun create():MiniProgram{ + fun create(): MiniProgram { val payload = MiniProgramPayload() return MiniProgram(payload); - } } -} \ No newline at end of file +} diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Room.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Room.kt index 9ed5058..35d94c5 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Room.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Room.kt @@ -15,7 +15,10 @@ import io.github.wechaty.io.github.wechaty.schemas.EventEnum import io.github.wechaty.schemas.RoomMemberQueryFilter import io.github.wechaty.schemas.RoomPayload import io.github.wechaty.type.Sayable +import io.github.wechaty.user.manager.RoomManager +import io.github.wechaty.utils.JsonUtils import io.github.wechaty.utils.QrcodeUtils +import io.grpc.netty.shaded.io.netty.util.concurrent.CompleteFuture import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory @@ -57,9 +60,7 @@ class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { message.ready().get() return@supplyAsync message } - return@supplyAsync null - } } @@ -349,13 +350,41 @@ class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { } } + fun getAnnounce(): Future { + return CompletableFuture.supplyAsync { + puppet.getRoomAnnounce(id).get() + } + } + + fun setAnnounce(text: String): Future { + return puppet.setRoomAnnounce(id, text) + } + fun qrCode(): Future { return CompletableFuture.supplyAsync { val qrCodeValue = puppet.roomQRCode(id).get() return@supplyAsync QrcodeUtils.guardQrCodeValue(qrCodeValue) } } + fun getQrCode(): Future { + return CompletableFuture.supplyAsync { + val qrCodeValue = puppet.roomQRCode(id).get() + return@supplyAsync QrcodeUtils.guardQrCodeValue(qrCodeValue) + } + } + fun member(query: RoomMemberQueryFilter?): Contact? { + val memberList = memberAll(query) + + if (memberList == null || memberList.size == 0) { + return null + } + if (memberList.size > 1) { + log.warn("Room, member({}) get {} contacts, use the first one by default", query?.let { JsonUtils.write(it) }, memberList.size) + } + + return memberList[0]; + } fun memberAll(query: RoomMemberQueryFilter?): List { if (query == null) { @@ -364,11 +393,10 @@ class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { val contactIdList = wechaty.getPuppet().roomMemberSearch(this.id, query).get() val contactList = contactIdList.map { - wechaty.contactManager.load(id) + id -> wechaty.contactManager.load(id) } return contactList - } fun memberList(): List { @@ -380,19 +408,15 @@ class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { } val contactList = memberIdList.map { - wechaty.contactManager.load(id) + id -> wechaty.contactManager.load(id) } return contactList } fun alias(contact: Contact): String? { - val roomMemberPayload = wechaty.getPuppet().roomMemberPayload(this.id, contact.id).get() - return roomMemberPayload?.roomAlias - - } fun has(contact: Contact): Boolean { @@ -422,6 +446,11 @@ class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { return puppet.roomAvatar(this.id).get() } + fun getAvatar(): FileBox { + log.debug("getAvatar:{}", getAvatar()) + return puppet.roomAvatar(this.id).get() + } + companion object { private val log = LoggerFactory.getLogger(Room::class.java) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/RoomInvitation.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/RoomInvitation.kt index 779ecb7..5ef4ec9 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/RoomInvitation.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/RoomInvitation.kt @@ -52,25 +52,41 @@ class RoomInvitation(wechaty: Wechaty,val id:String) : Accessory(wechaty){ return Date(payload.timestamp!! * 1000) } + fun getDate(): Date { + val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() + return Date(payload.timestamp!! * 1000) + } fun age():Long{ val recvDate = this.date() return System.currentTimeMillis() - recvDate.time; } + fun getAge():Long{ + val recvDate = this.date() + return System.currentTimeMillis() - recvDate.time; + } fun inviter():Contact{ val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() return wechaty.contactManager.load(payload.inviterId!!) } + fun getInviter():Contact{ + val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() + return wechaty.contactManager.load(payload.inviterId!!) + } + fun topic():String { val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() return payload.topic ?:"" } + fun getTopic():String { + val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() + return payload.topic ?:"" + } + companion object{ private val log = LoggerFactory.getLogger(RoomInvitation::class.java) } - - } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/Tag.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/Tag.kt index bd8af36..29bda65 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/Tag.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/Tag.kt @@ -14,7 +14,6 @@ class Tag(wechaty:Wechaty,val id:String):Accessory(wechaty){ wechaty.getPuppet().tagContactRemove(this.id!!,from.id!!).get() } - companion object{ private val log = LoggerFactory.getLogger(Tag::class.java) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/UrlLink.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/UrlLink.kt index 1024566..ff7d99d 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/UrlLink.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/UrlLink.kt @@ -69,7 +69,6 @@ class UrlLink(val payload:UrlLinkPayload) { return UrlLink(payload) -// } } @@ -79,7 +78,13 @@ class UrlLink(val payload:UrlLinkPayload) { fun main(){ val create = UrlLink.create("https://xilidou.com") - +// val bilibili = UrlLink.create("https://www.bilibili.com/") +// val image = UrlLink.create("https://img.xilidou.com/img/java-wechaty.png") println(create) - +// println(bilibili) +// print(image) + val urlLink = UrlLinkPayload("Nihao", "https://www.bilibili.com/") + urlLink.thumbnailUrl = "https://xilidou.com/images/avatar.jpg" + urlLink.description = "犀利豆的博客" + print(urlLink) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/ContactManager.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/ContactManager.kt index b6ccb8c..33e2046 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/ContactManager.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/ContactManager.kt @@ -30,9 +30,6 @@ class ContactManager(wechaty: Wechaty):Accessory(wechaty) { } - - - fun find(queryFilter: ContactQueryFilter):Contact?{ val findAll = findAll(queryFilter) @@ -72,7 +69,7 @@ class ContactManager(wechaty: Wechaty):Accessory(wechaty) { } companion object { - private val log = LoggerFactory.getLogger(Contact::class.java) + private val log = LoggerFactory.getLogger(ContactManager::class.java) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/FriendShipManager.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/FriendShipManager.kt new file mode 100644 index 0000000..4a01f8c --- /dev/null +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/FriendShipManager.kt @@ -0,0 +1,49 @@ +package io.github.wechaty.user.manager + +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import io.github.wechaty.Accessory +import io.github.wechaty.Wechaty +import io.github.wechaty.schemas.* +import io.github.wechaty.user.Contact +import io.github.wechaty.user.ContactSelf +import io.github.wechaty.user.Friendship +import io.github.wechaty.user.Tag +import org.apache.commons.collections4.CollectionUtils +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import java.util.* + +class FriendShipManager(wechaty: Wechaty):Accessory(wechaty) { + + private val friendshipCache: Cache = Caffeine.newBuilder().build() + + fun load(id:String): Friendship { + return friendshipCache.get(id) { + Friendship(wechaty, id) + }!! + } + + // 查找发送请求的好友 + fun search(queryFilter: FriendshipSearchCondition): Contact? { + val friendshipId = wechaty.getPuppet().friendshipSearch(queryFilter).get(); + if(StringUtils.isEmpty(friendshipId)){ + return null + } + val contact = wechaty.contactManager.load(friendshipId!!) + contact.ready() + return contact + } + + // 添加好友 + fun add(contact: Contact, hello:String) { + log.debug("add contact: {} hello: {}", contact, hello) + wechaty.getPuppet().friendshipAdd(contact.id, hello).get() + } + + companion object { + private val log = LoggerFactory.getLogger(FriendShipManager::class.java) + } + + +} diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/MessageManager.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/MessageManager.kt index e9dc3c9..f4208ee 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/MessageManager.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/MessageManager.kt @@ -56,7 +56,7 @@ class MessageManager (wechaty: Wechaty):Accessory(wechaty){ } companion object { - private val log = LoggerFactory.getLogger(Contact::class.java) + private val log = LoggerFactory.getLogger(MessageManager::class.java) } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomInvitationManager.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomInvitationManager.kt index a2b514d..eabed38 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomInvitationManager.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomInvitationManager.kt @@ -9,13 +9,12 @@ import org.slf4j.LoggerFactory class RoomInvitationManager (wechaty: Wechaty):Accessory(wechaty){ - fun load(id:String):RoomInvitation{ + fun load(id:String): RoomInvitation { return RoomInvitation(wechaty,id) } - companion object { - private val log = LoggerFactory.getLogger(Contact::class.java) + private val log = LoggerFactory.getLogger(RoomInvitationManager::class.java) } } diff --git a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/TagManager.kt b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/TagManager.kt index 02435da..1ec3700 100644 --- a/wechaty/src/main/kotlin/io/github/wechaty/user/manager/TagManager.kt +++ b/wechaty/src/main/kotlin/io/github/wechaty/user/manager/TagManager.kt @@ -18,7 +18,7 @@ class TagManager(wechaty: Wechaty):Accessory(wechaty){ }!! } - fun get(id:String):Tag{ + fun get(id:String): Tag{ return load(id) } @@ -26,6 +26,13 @@ class TagManager(wechaty: Wechaty):Accessory(wechaty){ wechaty.getPuppet().tagContactDelete(tag.id) } + fun tags():List{ + val tagIdList = wechaty.getPuppet().tagContactList().get() + return tagIdList.map { + wechaty.tagManager.load(it) + } + } + companion object{ private val log = LoggerFactory.getLogger(TagManager::class.java) } diff --git a/wechaty/src/main/resources/log4j2.xml b/wechaty/src/main/resources/log4j2.xml index 0e80944..b7f6060 100644 --- a/wechaty/src/main/resources/log4j2.xml +++ b/wechaty/src/main/resources/log4j2.xml @@ -11,7 +11,6 @@ -