Skip to content

Commit c3293a8

Browse files
authored
Add APIs for Post & Tap (#182)
* 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
1 parent 387db13 commit c3293a8

33 files changed

+895
-186
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wechaty-puppet",
3-
"version": "1.11.17",
3+
"version": "1.13.5",
44
"description": "Abstract Puppet for Wechaty",
55
"type": "module",
66
"exports": {
@@ -109,6 +109,7 @@
109109
"memory-card": "^1.1.2",
110110
"state-switch": "^1.7.1",
111111
"typed-emitter": "^1.5.0-from-event",
112+
"typesafe-actions": "^5.1.0",
112113
"uuid": "^8.3.2",
113114
"watchdog": "^0.9.2"
114115
},

src/agents/cache-agent.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import type {
2626
import type {
2727
PuppetOptions,
2828
} from '../schemas/puppet.js'
29+
import type {
30+
PostPayload,
31+
} from '../schemas/post.js'
2932

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

@@ -38,6 +41,7 @@ class CacheAgent {
3841
readonly contact : QuickLru<string, ContactPayload>
3942
readonly friendship : QuickLru<string, FriendshipPayload>
4043
readonly message : QuickLru<string, MessagePayload>
44+
readonly post : QuickLru<string, PostPayload>
4145
readonly room : QuickLru<string, RoomPayload>
4246
readonly roomInvitation : QuickLru<string, RoomInvitationPayload>
4347
readonly roomMember : QuickLru<string, LruRoomMemberPayload>
@@ -77,12 +81,13 @@ class CacheAgent {
7781
this.room = new QuickLru<string, RoomPayload>(lruOptions(
7882
envVars.WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM(options?.room)),
7983
)
80-
84+
this.post = new QuickLru<string, PostPayload>(lruOptions(
85+
envVars.WECHATY_PUPPET_LRU_CACHE_SIZE_POST(options?.post)),
86+
)
8187
}
8288

8389
start (): void {
8490
log.verbose('PuppetCacheAgent', 'start()')
85-
this.clear()
8691
}
8792

8893
stop (): void {
@@ -106,6 +111,7 @@ class CacheAgent {
106111
this.contact.clear()
107112
this.friendship.clear()
108113
this.message.clear()
114+
this.post.clear()
109115
this.room.clear()
110116
this.roomInvitation.clear()
111117
this.roomMember.clear()

src/env-vars.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const DEFAULT_LRU_CACHE_SIZE_CONTACT = 500
22
const DEFAULT_LRU_CACHE_SIZE_FRIENDSHIP = 100
33
const DEFAULT_LRU_CACHE_SIZE_MESSAGE = 500
4+
const DEFAULT_LRU_CACHE_SIZE_POST = 100
45
const DEFAULT_LRU_CACHE_SIZE_ROOM = 100
56
const DEFAULT_LRU_CACHE_SIZE_ROOM_INVITATION = 100
6-
const DEFAULT_LRU_CACHE_SIZE_ROOM_MEMBER = 50
7+
const DEFAULT_LRU_CACHE_SIZE_ROOM_MEMBER = 100
78

89
const getNumberEnv = (env: typeof process.env) => (
910
varName : string,
@@ -37,6 +38,10 @@ const WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE = (v?: number) => v ?? getNumber(
3738
'WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE',
3839
DEFAULT_LRU_CACHE_SIZE_MESSAGE,
3940
)
41+
const WECHATY_PUPPET_LRU_CACHE_SIZE_POST = (v?: number) => v ?? getNumber(
42+
'WECHATY_PUPPET_LRU_CACHE_SIZE_POST',
43+
DEFAULT_LRU_CACHE_SIZE_POST,
44+
)
4045
const WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM = (v?: number) => v ?? getNumber(
4146
'WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM',
4247
DEFAULT_LRU_CACHE_SIZE_ROOM,
@@ -55,6 +60,7 @@ export {
5560
WECHATY_PUPPET_LRU_CACHE_SIZE_CONTACT,
5661
WECHATY_PUPPET_LRU_CACHE_SIZE_FRIENDSHIP,
5762
WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE,
63+
WECHATY_PUPPET_LRU_CACHE_SIZE_POST,
5864
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_INVITATION,
5965
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_MEMBER,
6066
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM,

src/mixins/cache-mixin.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
PuppetOptions,
99
EventDirtyPayload,
1010
} from '../schemas/mod.js'
11-
import { PayloadType } from '../schemas/mod.js'
11+
import { DirtyType } from '../schemas/mod.js'
1212

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

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

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

111111
const dirtyFuncMap = {
112-
[PayloadType.Contact]: (id: string) => this.cache.contact.delete(id),
113-
[PayloadType.Friendship]: (id: string) => this.cache.friendship.delete(id),
114-
[PayloadType.Message]: (id: string) => this.cache.message.delete(id),
115-
[PayloadType.Room]: (id: string) => this.cache.room.delete(id),
116-
[PayloadType.RoomMember]: (id: string) => this.cache.roomMember.delete(id),
117-
[PayloadType.Unspecified]: (id: string) => { throw new Error('Unspecified type with id: ' + id) },
112+
[DirtyType.Contact]: (id: string) => this.cache.contact.delete(id),
113+
[DirtyType.Friendship]: (id: string) => this.cache.friendship.delete(id),
114+
[DirtyType.Message]: (id: string) => this.cache.message.delete(id),
115+
[DirtyType.Post]: (id: string) => this.cache.post.delete(id),
116+
[DirtyType.Room]: (id: string) => this.cache.room.delete(id),
117+
[DirtyType.RoomMember]: (id: string) => this.cache.roomMember.delete(id),
118+
[DirtyType.Unspecified]: (id: string) => { throw new Error('Unspecified type with id: ' + id) },
118119
}
119120

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

134135
if (!this.__currentUserId) {
135136
log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait() will not dirty any payload when the puppet is not logged in')
@@ -179,7 +180,7 @@ const cacheMixin = <MixinBase extends typeof PuppetSkeleton & LoginMixin>(mixinB
179180
'error: %s',
180181
'stack: %s',
181182
].join('\n '),
182-
PayloadType[type],
183+
DirtyType[type],
183184
type,
184185
id,
185186
(e as Error).message,

src/mixins/contact-mixin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
ContactPayloadFilterFunction,
1212
ContactQueryFilter,
1313
} from '../schemas/contact.js'
14-
import { PayloadType } from '../schemas/payload.js'
14+
import { DirtyType } from '../schemas/dirty.js'
1515

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

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

312312
await this.__dirtyPayloadAwait(
313-
PayloadType.Contact,
313+
DirtyType.Contact,
314314
id,
315315
)
316316
}

src/mixins/friendship-mixin.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import type {
77
FriendshipSearchQueryFilter,
88
} from '../schemas/friendship.js'
99

10-
import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
11-
import type { CacheMixin } from './cache-mixin.js'
12-
import { PayloadType } from '../schemas/payload.js'
10+
import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
11+
import { DirtyType } from '../schemas/dirty.js'
12+
13+
import type { CacheMixin } from './cache-mixin.js'
1314

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

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

134135
await this.__dirtyPayloadAwait(
135-
PayloadType.Friendship,
136+
DirtyType.Friendship,
136137
id,
137138
)
138139
}

src/mixins/message-mixin.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
// import {
2-
// FileBox,
3-
// } from 'file-box'
4-
import type {
5-
FileBoxInterface,
1+
import {
2+
type FileBoxInterface,
3+
FileBox,
64
} from 'file-box'
5+
76
import {
87
log,
98
} from '../config.js'
@@ -26,11 +25,20 @@ import type {
2625
import type {
2726
LocationPayload,
2827
} from '../schemas/location.js'
28+
import type {
29+
PostPayload,
30+
} from '../schemas/post.js'
2931

3032
import type { PuppetSkeleton } from '../puppet/puppet-skeleton.js'
31-
import { PayloadType } from '../schemas/payload.js'
33+
import { DirtyType } from '../schemas/dirty.js'
3234

3335
import type { CacheMixin } from './cache-mixin.js'
36+
import {
37+
type SayablePayload,
38+
sayableTypes,
39+
} from '../schemas/sayable.js'
40+
41+
const filebox = (filebox: string | FileBoxInterface) => typeof filebox === 'string' ? FileBox.fromJSON(filebox) : filebox
3442

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

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

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

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

236245
await this.__dirtyPayloadAwait(
237-
PayloadType.Message,
246+
DirtyType.Message,
238247
id,
239248
)
240249
}
241250

251+
/**
252+
* send a sayable payload for event driven API and convenience
253+
*
254+
* @param conversationId
255+
* @param sayable
256+
* @returns
257+
*/
258+
messageSend (
259+
conversationId: string,
260+
sayable: SayablePayload,
261+
): Promise<void | string> {
262+
log.verbose('PuppetMessageMixin', 'messageSend(%s, {type:%s})', conversationId, sayable.type)
263+
264+
switch (sayable.type) {
265+
case sayableTypes.Attachment:
266+
case sayableTypes.Audio:
267+
case sayableTypes.Emoticon:
268+
case sayableTypes.Image:
269+
case sayableTypes.Video:
270+
return this.messageSendFile(conversationId, filebox(sayable.payload.filebox))
271+
case sayableTypes.Contact:
272+
return this.messageSendContact(conversationId, sayable.payload.contactId)
273+
case sayableTypes.Location:
274+
return this.messageSendLocation(conversationId, sayable.payload)
275+
case sayableTypes.MiniProgram:
276+
return this.messageSendMiniProgram(conversationId, sayable.payload)
277+
case sayableTypes.Url:
278+
return this.messageSendUrl(conversationId, sayable.payload)
279+
case sayableTypes.Text:
280+
return this.messageSendText(conversationId, sayable.payload.text)
281+
case sayableTypes.Post:
282+
return this.messageSendPost(conversationId, sayable.payload)
283+
default:
284+
throw new Error('unsupported sayable payload: ' + JSON.stringify(sayable))
285+
}
286+
}
287+
242288
}
243289

244290
return MessageMixin

src/mixins/mod.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ import {
4646
tagMixin,
4747
ProtectedPropertyTagMixin,
4848
} from './tag-mixin.js'
49+
import {
50+
tapMixin,
51+
ProtectedPropertyTapMixin,
52+
} from './tap-mixin.js'
53+
import {
54+
postMixin,
55+
ProtectedPropertyPostMixin,
56+
} from './post-mixin.js'
4957
import {
5058
validateMixin,
5159
ProtectedPropertyValidateMixin,
@@ -68,11 +76,13 @@ type MixinProtectedProperty =
6876
| ProtectedPropertyLoginMixin
6977
| ProtectedPropertyMemoryMixin
7078
| ProtectedPropertyMessageMixin
79+
| ProtectedPropertyPostMixin
7180
| ProtectedPropertyRoomInvitationMixin
7281
| ProtectedPropertyRoomMemberMixin
7382
| ProtectedPropertyRoomMixin
7483
| ProtectedPropertyServiceMixin
7584
| ProtectedPropertyTagMixin
85+
| ProtectedPropertyTapMixin
7686
| ProtectedPropertyValidateMixin
7787

7888
export type {
@@ -86,11 +96,13 @@ export {
8696
memoryMixin,
8797
messageMixin,
8898
miscMixin,
99+
postMixin,
100+
readyMixin,
89101
roomInvitationMixin,
90102
roomMemberMixin,
91103
roomMixin,
92104
serviceMixin,
93105
tagMixin,
106+
tapMixin,
94107
validateMixin,
95-
readyMixin,
96108
}

src/mixins/post-mixin.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
2+
3+
import {
4+
test,
5+
} from 'tstest'
6+
7+
import type {
8+
PostMixin,
9+
ProtectedPropertyPostMixin,
10+
} from './post-mixin.js'
11+
12+
test('ProtectedPropertyPostMixin', async t => {
13+
type NotExistInMixin = Exclude<ProtectedPropertyPostMixin, keyof InstanceType<PostMixin>>
14+
type NotExistTest = NotExistInMixin extends never ? true : false
15+
16+
const noOneLeft: NotExistTest = true
17+
t.ok(noOneLeft, 'should match Mixin properties for every protected property')
18+
})

0 commit comments

Comments
 (0)