Skip to content

Commit

Permalink
Add APIs for Post & Tap (#182)
Browse files Browse the repository at this point in the history
* add sayable & post payloads, workaround for #180

* add sayable schemas etc.

* add tap mixin

* init post interface

* fix all unit tests (typing imports)

* 1.13.1

* export sayable payloads

* 1.13.2

* move type in post payload, add post event

* clean

* 1.13.3

* fix names & exports

* 1.13.4

* rename create -> publish

* clean

* 1.13.5
  • Loading branch information
huan authored Jan 22, 2022
1 parent 387db13 commit c3293a8
Show file tree
Hide file tree
Showing 33 changed files with 895 additions and 186 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wechaty-puppet",
"version": "1.11.17",
"version": "1.13.5",
"description": "Abstract Puppet for Wechaty",
"type": "module",
"exports": {
Expand Down Expand Up @@ -109,6 +109,7 @@
"memory-card": "^1.1.2",
"state-switch": "^1.7.1",
"typed-emitter": "^1.5.0-from-event",
"typesafe-actions": "^5.1.0",
"uuid": "^8.3.2",
"watchdog": "^0.9.2"
},
Expand Down
10 changes: 8 additions & 2 deletions src/agents/cache-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import type {
import type {
PuppetOptions,
} from '../schemas/puppet.js'
import type {
PostPayload,
} from '../schemas/post.js'

type PayloadCacheOptions = Required<PuppetOptions>['cache']

Expand All @@ -38,6 +41,7 @@ class CacheAgent {
readonly contact : QuickLru<string, ContactPayload>
readonly friendship : QuickLru<string, FriendshipPayload>
readonly message : QuickLru<string, MessagePayload>
readonly post : QuickLru<string, PostPayload>
readonly room : QuickLru<string, RoomPayload>
readonly roomInvitation : QuickLru<string, RoomInvitationPayload>
readonly roomMember : QuickLru<string, LruRoomMemberPayload>
Expand Down Expand Up @@ -77,12 +81,13 @@ class CacheAgent {
this.room = new QuickLru<string, RoomPayload>(lruOptions(
envVars.WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM(options?.room)),
)

this.post = new QuickLru<string, PostPayload>(lruOptions(
envVars.WECHATY_PUPPET_LRU_CACHE_SIZE_POST(options?.post)),
)
}

start (): void {
log.verbose('PuppetCacheAgent', 'start()')
this.clear()
}

stop (): void {
Expand All @@ -106,6 +111,7 @@ class CacheAgent {
this.contact.clear()
this.friendship.clear()
this.message.clear()
this.post.clear()
this.room.clear()
this.roomInvitation.clear()
this.roomMember.clear()
Expand Down
8 changes: 7 additions & 1 deletion src/env-vars.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const DEFAULT_LRU_CACHE_SIZE_CONTACT = 500
const DEFAULT_LRU_CACHE_SIZE_FRIENDSHIP = 100
const DEFAULT_LRU_CACHE_SIZE_MESSAGE = 500
const DEFAULT_LRU_CACHE_SIZE_POST = 100
const DEFAULT_LRU_CACHE_SIZE_ROOM = 100
const DEFAULT_LRU_CACHE_SIZE_ROOM_INVITATION = 100
const DEFAULT_LRU_CACHE_SIZE_ROOM_MEMBER = 50
const DEFAULT_LRU_CACHE_SIZE_ROOM_MEMBER = 100

const getNumberEnv = (env: typeof process.env) => (
varName : string,
Expand Down Expand Up @@ -37,6 +38,10 @@ const WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE = (v?: number) => v ?? getNumber(
'WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE',
DEFAULT_LRU_CACHE_SIZE_MESSAGE,
)
const WECHATY_PUPPET_LRU_CACHE_SIZE_POST = (v?: number) => v ?? getNumber(
'WECHATY_PUPPET_LRU_CACHE_SIZE_POST',
DEFAULT_LRU_CACHE_SIZE_POST,
)
const WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM = (v?: number) => v ?? getNumber(
'WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM',
DEFAULT_LRU_CACHE_SIZE_ROOM,
Expand All @@ -55,6 +60,7 @@ export {
WECHATY_PUPPET_LRU_CACHE_SIZE_CONTACT,
WECHATY_PUPPET_LRU_CACHE_SIZE_FRIENDSHIP,
WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE,
WECHATY_PUPPET_LRU_CACHE_SIZE_POST,
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_INVITATION,
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_MEMBER,
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM,
Expand Down
27 changes: 14 additions & 13 deletions src/mixins/cache-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
PuppetOptions,
EventDirtyPayload,
} from '../schemas/mod.js'
import { PayloadType } from '../schemas/mod.js'
import { DirtyType } from '../schemas/mod.js'

import { CacheAgent } from '../agents/mod.js'

Expand Down Expand Up @@ -82,10 +82,10 @@ const cacheMixin = <MixinBase extends typeof PuppetSkeleton & LoginMixin>(mixinB
* Call this method when you want to notify the server that the data cache need to be invalidated.
*/
dirtyPayload (
type : PayloadType,
type : DirtyType,
id : string,
): void {
log.verbose('PuppetCacheMixin', 'dirtyPayload(%s<%s>, %s)', PayloadType[type], type, id)
log.verbose('PuppetCacheMixin', 'dirtyPayload(%s<%s>, %s)', DirtyType[type], type, id)

/**
* Huan(202111): we return first before emit the `dirty` event?
Expand All @@ -106,15 +106,16 @@ const cacheMixin = <MixinBase extends typeof PuppetSkeleton & LoginMixin>(mixinB
payloadId,
}: EventDirtyPayload,
): void {
log.verbose('PuppetCacheMixin', 'onDirty(%s<%s>, %s)', PayloadType[payloadType], payloadType, payloadId)
log.verbose('PuppetCacheMixin', 'onDirty(%s<%s>, %s)', DirtyType[payloadType], payloadType, payloadId)

const dirtyFuncMap = {
[PayloadType.Contact]: (id: string) => this.cache.contact.delete(id),
[PayloadType.Friendship]: (id: string) => this.cache.friendship.delete(id),
[PayloadType.Message]: (id: string) => this.cache.message.delete(id),
[PayloadType.Room]: (id: string) => this.cache.room.delete(id),
[PayloadType.RoomMember]: (id: string) => this.cache.roomMember.delete(id),
[PayloadType.Unspecified]: (id: string) => { throw new Error('Unspecified type with id: ' + id) },
[DirtyType.Contact]: (id: string) => this.cache.contact.delete(id),
[DirtyType.Friendship]: (id: string) => this.cache.friendship.delete(id),
[DirtyType.Message]: (id: string) => this.cache.message.delete(id),
[DirtyType.Post]: (id: string) => this.cache.post.delete(id),
[DirtyType.Room]: (id: string) => this.cache.room.delete(id),
[DirtyType.RoomMember]: (id: string) => this.cache.roomMember.delete(id),
[DirtyType.Unspecified]: (id: string) => { throw new Error('Unspecified type with id: ' + id) },
}

const dirtyFunc = dirtyFuncMap[payloadType]
Expand All @@ -126,10 +127,10 @@ const cacheMixin = <MixinBase extends typeof PuppetSkeleton & LoginMixin>(mixinB
* and we need to wait for the `dirty` event so we can make sure the cache has been invalidated.
*/
async __dirtyPayloadAwait (
type : PayloadType,
type : DirtyType,
id : string,
): Promise<void> {
log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait(%s<%s>, %s)', PayloadType[type], type, id)
log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait(%s<%s>, %s)', DirtyType[type], type, id)

if (!this.__currentUserId) {
log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait() will not dirty any payload when the puppet is not logged in')
Expand Down Expand Up @@ -179,7 +180,7 @@ const cacheMixin = <MixinBase extends typeof PuppetSkeleton & LoginMixin>(mixinB
'error: %s',
'stack: %s',
].join('\n '),
PayloadType[type],
DirtyType[type],
type,
id,
(e as Error).message,
Expand Down
4 changes: 2 additions & 2 deletions src/mixins/contact-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
ContactPayloadFilterFunction,
ContactQueryFilter,
} from '../schemas/contact.js'
import { PayloadType } from '../schemas/payload.js'
import { DirtyType } from '../schemas/dirty.js'

import type { CacheMixin } from './cache-mixin.js'

Expand Down Expand Up @@ -310,7 +310,7 @@ const contactMixin = <MixinBase extends CacheMixin & typeof PuppetSkeleton>(mixi
log.verbose('PuppetContactMixin', 'contactPayloadDirty(%s)', id)

await this.__dirtyPayloadAwait(
PayloadType.Contact,
DirtyType.Contact,
id,
)
}
Expand Down
9 changes: 5 additions & 4 deletions src/mixins/friendship-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import type {
FriendshipSearchQueryFilter,
} from '../schemas/friendship.js'

import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
import type { CacheMixin } from './cache-mixin.js'
import { PayloadType } from '../schemas/payload.js'
import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
import { DirtyType } from '../schemas/dirty.js'

import type { CacheMixin } from './cache-mixin.js'

const friendshipMixin = <MixinBase extends typeof PuppetSkeleton & CacheMixin>(mixinBase: MixinBase) => {

Expand Down Expand Up @@ -132,7 +133,7 @@ const friendshipMixin = <MixinBase extends typeof PuppetSkeleton & CacheMixin>(m
log.verbose('PuppetFriendshipMixin', 'friendshipPayloadDirty(%s)', id)

await this.__dirtyPayloadAwait(
PayloadType.Friendship,
DirtyType.Friendship,
id,
)
}
Expand Down
62 changes: 54 additions & 8 deletions src/mixins/message-mixin.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// import {
// FileBox,
// } from 'file-box'
import type {
FileBoxInterface,
import {
type FileBoxInterface,
FileBox,
} from 'file-box'

import {
log,
} from '../config.js'
Expand All @@ -26,11 +25,20 @@ import type {
import type {
LocationPayload,
} from '../schemas/location.js'
import type {
PostPayload,
} from '../schemas/post.js'

import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
import { PayloadType } from '../schemas/payload.js'
import { DirtyType } from '../schemas/dirty.js'

import type { CacheMixin } from './cache-mixin.js'
import {
type SayablePayload,
sayableTypes,
} from '../schemas/sayable.js'

const filebox = (filebox: string | FileBoxInterface) => typeof filebox === 'string' ? FileBox.fromJSON(filebox) : filebox

const messageMixin = <MinxinBase extends typeof PuppetSkeleton & CacheMixin>(baseMixin: MinxinBase) => {

Expand Down Expand Up @@ -63,10 +71,11 @@ const messageMixin = <MinxinBase extends typeof PuppetSkeleton & CacheMixin>(bas
abstract messageForward (conversationId: string, messageId: string,) : Promise<void | string>
abstract messageSendContact (conversationId: string, contactId: string) : Promise<void | string>
abstract messageSendFile (conversationId: string, file: FileBoxInterface) : Promise<void | string>
abstract messageSendLocation (conversationId: string, locationPayload: LocationPayload) : Promise<void | string>
abstract messageSendMiniProgram (conversationId: string, miniProgramPayload: MiniProgramPayload) : Promise<void | string>
abstract messageSendPost (conversationId: string, postPayload: PostPayload) : Promise<void | string>
abstract messageSendText (conversationId: string, text: string, mentionIdList?: string[]) : Promise<void | string>
abstract messageSendUrl (conversationId: string, urlLinkPayload: UrlLinkPayload) : Promise<void | string>
abstract messageSendLocation (conversationId: string, locationPayload: LocationPayload) : Promise<void | string>

abstract messageRecall (messageId: string) : Promise<boolean>

Expand Down Expand Up @@ -234,11 +243,48 @@ const messageMixin = <MinxinBase extends typeof PuppetSkeleton & CacheMixin>(bas
log.verbose('PuppetMessageMixin', 'messagePayloadDirty(%s)', id)

await this.__dirtyPayloadAwait(
PayloadType.Message,
DirtyType.Message,
id,
)
}

/**
* send a sayable payload for event driven API and convenience
*
* @param conversationId
* @param sayable
* @returns
*/
messageSend (
conversationId: string,
sayable: SayablePayload,
): Promise<void | string> {
log.verbose('PuppetMessageMixin', 'messageSend(%s, {type:%s})', conversationId, sayable.type)

switch (sayable.type) {
case sayableTypes.Attachment:
case sayableTypes.Audio:
case sayableTypes.Emoticon:
case sayableTypes.Image:
case sayableTypes.Video:
return this.messageSendFile(conversationId, filebox(sayable.payload.filebox))
case sayableTypes.Contact:
return this.messageSendContact(conversationId, sayable.payload.contactId)
case sayableTypes.Location:
return this.messageSendLocation(conversationId, sayable.payload)
case sayableTypes.MiniProgram:
return this.messageSendMiniProgram(conversationId, sayable.payload)
case sayableTypes.Url:
return this.messageSendUrl(conversationId, sayable.payload)
case sayableTypes.Text:
return this.messageSendText(conversationId, sayable.payload.text)
case sayableTypes.Post:
return this.messageSendPost(conversationId, sayable.payload)
default:
throw new Error('unsupported sayable payload: ' + JSON.stringify(sayable))
}
}

}

return MessageMixin
Expand Down
14 changes: 13 additions & 1 deletion src/mixins/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ import {
tagMixin,
ProtectedPropertyTagMixin,
} from './tag-mixin.js'
import {
tapMixin,
ProtectedPropertyTapMixin,
} from './tap-mixin.js'
import {
postMixin,
ProtectedPropertyPostMixin,
} from './post-mixin.js'
import {
validateMixin,
ProtectedPropertyValidateMixin,
Expand All @@ -68,11 +76,13 @@ type MixinProtectedProperty =
| ProtectedPropertyLoginMixin
| ProtectedPropertyMemoryMixin
| ProtectedPropertyMessageMixin
| ProtectedPropertyPostMixin
| ProtectedPropertyRoomInvitationMixin
| ProtectedPropertyRoomMemberMixin
| ProtectedPropertyRoomMixin
| ProtectedPropertyServiceMixin
| ProtectedPropertyTagMixin
| ProtectedPropertyTapMixin
| ProtectedPropertyValidateMixin

export type {
Expand All @@ -86,11 +96,13 @@ export {
memoryMixin,
messageMixin,
miscMixin,
postMixin,
readyMixin,
roomInvitationMixin,
roomMemberMixin,
roomMixin,
serviceMixin,
tagMixin,
tapMixin,
validateMixin,
readyMixin,
}
18 changes: 18 additions & 0 deletions src/mixins/post-mixin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm

import {
test,
} from 'tstest'

import type {
PostMixin,
ProtectedPropertyPostMixin,
} from './post-mixin.js'

test('ProtectedPropertyPostMixin', async t => {
type NotExistInMixin = Exclude<ProtectedPropertyPostMixin, keyof InstanceType<PostMixin>>
type NotExistTest = NotExistInMixin extends never ? true : false

const noOneLeft: NotExistTest = true
t.ok(noOneLeft, 'should match Mixin properties for every protected property')
})
Loading

0 comments on commit c3293a8

Please sign in to comment.