Message.scala
package wechaty.user
import java.util.Date
import com.typesafe.scalalogging.LazyLogging
import wechaty.Wechaty.PuppetResolver
import wechaty.helper.ImplicitHelper._
import wechaty.puppet.schemas.Message.MessageType
import wechaty.puppet.schemas.Puppet._
import wechaty.puppet.schemas.Puppet
import wechaty.puppet.{ResourceBox, schemas}
import scala.concurrent.Future
/**
* wrap MessagePayload
* @author <a href="mailto:jcai@ganshane.com">Jun Tsai</a>
* @since 2020-06-02
*/
class Message(messageId:String)(implicit resolver: PuppetResolver) extends LazyLogging{
private val MENTION_MEMBER_PATTERN= ("@([^\u2005^\u0020^$]+)")
lazy val payload: schemas.Message.MessagePayload = {
resolver
.puppet
.messagePayload(messageId)
}
private def sayId: String ={
if(!Puppet.isBlank(payload.roomId)) payload.roomId
else if(!Puppet.isBlank(payload.fromId)) payload.fromId
else throw new IllegalStateException("roomid and fromid both is null")
}
override def toString: String = {
if(payload != null) payload.text
else "Message"
}
def talker:Contact ={
this.from
}
def conversation : Conversation = {
room match{
case Some(r) => r
case _ => this.from
}
}
private def assertPayload(): Unit ={
if (this.payload == null) {
throw new Error("no payload")
}
}
def from: Contact ={
assertPayload()
val fromId = this.payload.fromId
if (Puppet.isBlank(fromId)) null
else new Contact(fromId)
}
def to : Contact ={
assertPayload()
val toId = this.payload.toId
if (Puppet.isBlank(toId)) null
else new Contact(toId)
}
def room:Option[Room] ={
assertPayload()
val roomId = this.payload.roomId
if (isBlank(roomId)) None
else Room.load(roomId)
}
def text : String ={
assertPayload()
this.payload.text
}
def toRecalled: Message= {
if (this.`type` != MessageType.Recalled) {
throw new Error("Can not call toRecalled() on message which is not recalled type.")
}
val originalMessageId = this.text
if (originalMessageId == null) {
throw new Error("Can not find recalled message")
}
new Message(originalMessageId)
}
def say(text:String): Future[Message] = {
resolver.puppet.messageSendText(sayId,text)
}
def say(contact: Contact): Message = {
resolver.puppet.messageSendContact(sayId,contact.id)
}
def say(resourceBox:ResourceBox): Message ={
resolver.puppet.messageSendFile(sayId,resourceBox)
}
def say(urlLink: UrlLink) :Message = {
resolver.puppet.messageSendUrl(sayId,urlLink.payload)
}
def say(mp:MiniProgram) :Message = {
resolver.puppet.messageSendMiniProgram(
sayId,
mp.payload,
)
}
def recall (): Boolean = {
resolver.puppet.messageRecall(messageId)
}
def `type`: MessageType.Type={
assertPayload()
this.payload.`type`
}
def self (): Boolean = {
val userIdOpt = resolver.puppet.selfIdOpt()
userIdOpt match{
case Some(userId) =>
val from = this.from
from != null && from.id == userId
case _ =>
false
}
}
def mentionList: Array[Contact]= {
val room = this.room
if (this.`type` != MessageType.Text || room == null) {
return Array()
}
/**
* Use mention list if mention list is available
* otherwise, process the message and get the mention list
*/
if (this.payload != null && this.payload.mentionIdList != null && this.payload.mentionIdList.length > 0) {
this.payload.mentionIdList.map(new Contact(_))
} else {
val reg = MENTION_MEMBER_PATTERN.r
val it = reg.findAllMatchIn(text)
it.map(_.group(1)).map(new Contact(_)).toArray
}
}
def mentionText (): String= {
val text = this.text
val room = this.room
val list = this.mentionList
if (room.isEmpty || list == null || list.length == 0) {
return text
}
val toAliasName = (member: Contact) => {
room.get.alias(member).getOrElse(member.name)
}
logger.debug(s"message text:$text mentionsList:$list")
val mentionNameList = list.map(toAliasName)
val textWithoutMention = mentionNameList.foldLeft(text)((prev, cur) => {
val escapedCur = cur
val regex = "@\\Q"+escapedCur+"\\E(\\u2005|\\u0020|$)"
prev.replaceFirst(regex,"")
})
textWithoutMention.trim()
}
def mentionSelf (): Boolean = {
resolver.puppet.selfIdOpt() match{
case Some(selfId) =>
mentionList.exists(_.id == selfId)
case _ => false
}
}
def forward (to: Conversation): Future[String]= {
resolver.puppet.messageForward(to.id, this.messageId)
}
/**
* Message sent date
*/
def date: Date ={
assertPayload()
val timestamp = this.payload.timestamp
timestampToDate(timestamp)
}
/**
* Returns the message age in seconds. <br>
*
* For example, the message is sent at time `8:43:01`,
* and when we received it in Wechaty, the time is `8:43:15`,
* then the age() will return `8:43:15 - 8:43:01 = 14 (seconds)`
* @returns {number}
*/
def age:Long={
val ageMilliseconds = System.currentTimeMillis() - this.date.getTime()
val ageSeconds = Math.floor(ageMilliseconds / 1000)
ageSeconds.longValue()
}
/**
* Extract the Media File from the Message, and put it into the FileBox.
* > Tips:
* This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
*
* @returns {Promise<FileBox>}
*
* @example <caption>Save media file from a message</caption>
* const fileBox = await message.toFileBox()
* const fileName = fileBox.name
* fileBox.toFile(fileName)
*/
def toResourceBox (): ResourceBox= {
if (this.`type` == MessageType.Text) {
throw new Error("text message no file")
}
resolver.puppet.messageFile(this.messageId)
}
def toImage (): Image ={
if (this.`type` != MessageType.Image) {
throw new Error("not a image type message. type: "+this.`type`)
}
new Image(this.messageId)
}
/**
* Get Share Card of the Message
* Extract the Contact Card from the Message, and encapsulate it into Contact class
* > Tips:
* This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
* @returns {Promise<Contact>}
*/
def toContact (): Contact ={
if (this.`type` != MessageType.Contact) {
throw new Error("message not a ShareCard")
}
val contactId = resolver.puppet.messageContact(this.messageId)
if (Puppet.isBlank(contactId)) {
throw new Error(s"can not get Contact id by message: ${contactId}")
}
new Contact(contactId)
}
def toUrlLink (): UrlLink= {
assertPayload()
if (this.`type` != MessageType.Url) {
throw new Error("message not a Url Link")
}
val urlPayload = resolver.puppet.messageUrl(this.messageId)
if (urlPayload == null) {
throw new Error(s"no url payload for message ${this.messageId}")
}
new UrlLink(urlPayload)
}
def toMiniProgram (): MiniProgram= {
assertPayload()
if (this.`type` != MessageType.MiniProgram) {
throw new Error("message not a MiniProgram")
}
val miniProgramPayload = resolver.puppet.messageMiniProgram(this.messageId)
if (miniProgramPayload == null) {
throw new Error(s"no miniProgram payload for message ${this.messageId}")
}
new MiniProgram(miniProgramPayload)
}
}