Skip to content

Commit

Permalink
feat: event handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Innei committed Jul 23, 2023
1 parent 4dcad35 commit dca6ebe
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 13 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"check-config": "test -f app.config.ts || cp app.config.example.ts app.config.ts",
"build": "tsup",
"build:ts": "tsup --env.NODE_ENV production",
"dev": "yarn dev:watch",
"dev:watch": "tsx watch src/server.ts",
"dev": "npm run dev:watch",
"dev:watch": "NODE_ENV=development tsx watch src/server.ts",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prepare": "husky install && npm run check-config",
Expand Down
4 changes: 3 additions & 1 deletion src/modules/bilibili/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import type { PluginFunction } from '~/lib/plugin'
import type { BLRoom } from './types/room'
import type { BLUser } from './types/user'

import { userAgent } from '~/constants'
import { isDev, userAgent } from '~/constants'

const headers = {
referer: `https://link.bilibili.com/p/center/index?visit_id=22ast2mb9zhc`,
'User-Agent': userAgent,
}

export const register: PluginFunction = (ctx) => {
if (isDev) return
let playStatus = false
const config = appConfig.bilibili

Expand Down
267 changes: 257 additions & 10 deletions src/modules/mx-space/event-handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { appConfig } from 'app.config'
import type { ModuleContext } from '~/types/context'
import dayjs from 'dayjs'
import rmd from 'remove-markdown'
import type {
MxSocketEventTypes,
MxSystemEventBusEvents,
} from './types/mx-socket-types'
CommentModel,
LinkModel,
NoteModel,
PageModel,
PostModel,
RecentlyModel,
SayModel,
} from '@mx-space/api-client'
import type { ModuleContext } from '~/types/context'

import { LinkState } from '@mx-space/api-client'

import { createNamespaceLogger } from '~/lib/logger'
import { getShortDateTime, relativeTimeFromNow } from '~/lib/time'

import { apiClient } from './api-client'
import { getMxSpaceAggregateData } from './data'
import {
MxSocketEventTypes,
MxSystemEventBusEvents,
} from './types/mx-socket-types'
import { urlBuilder } from './utils'

const logger = createNamespaceLogger('mx-event')

Expand All @@ -17,22 +33,22 @@ interface TextMessage {
}

interface MediaMessage {
type: 'media'
type: 'photo'
url: string[]

caption?: string
}

type IMessage = TextMessage | MediaMessage
type Sendable = string | IMessage[]

export const handleEvent =
(ctx: ModuleContext) =>
async (
type: MxSocketEventTypes | MxSystemEventBusEvents,
payload: any,
code?: number,
) => {
async (type: MxSocketEventTypes | MxSystemEventBusEvents, payload: any) => {
logger.debug(type, payload)

const aggregateData = await getMxSpaceAggregateData()
const owner = aggregateData.user
const sendToGroup = async (message: Sendable) => {
const { watchGroupIds } = appConfig.mxSpace

Expand All @@ -51,10 +67,241 @@ export const handleEvent =
parse_mode: msg.type,
})
continue

case 'photo': {
const { url, caption = '' } = msg
ctx.tgBot.telegram.sendMediaGroup(
id,
url.map((u, i) => {
return {
type: 'photo',
media: u,
caption: i === 0 ? caption : undefined,
}
}),
)
break
}
}
}
} else return ctx.tgBot.telegram.sendMessage(id, message)
}),
)
}

switch (type) {
case MxSocketEventTypes.POST_CREATE:
case MxSocketEventTypes.POST_UPDATE: {
const isNew = type === MxSocketEventTypes.POST_CREATE
const publishDescription = isNew ? '发布了新文章' : '更新了文章'
const { title, text, category, id, summary, created } =
payload as PostModel

if (type === MxSocketEventTypes.POST_UPDATE) {
// only emit created date after 90 days
//
const createdDate = dayjs(created)
const now = dayjs()
const diff = now.diff(createdDate, 'day')
if (diff < 90) {
return
}
}
if (!category) {
logger.error(`category not found, post id: ${id}`)
return
}
const simplePreview = getSimplePreview(text)
const url = await urlBuilder.build(payload as PostModel)
const message = `${
owner.name
} ${publishDescription}: ${title}\n\n${simplePreview}\n\n${
summary ? `${summary}\n\n` : ''
}前往阅读:${url}`
await sendToGroup(message)

return
}
case MxSocketEventTypes.NOTE_CREATE: {
const publishDescription = '发布了新生活观察日记'
const { title, text, mood, weather, images, hide, password } =
payload as NoteModel
const isSecret = checkNoteIsSecret(payload as NoteModel)

if (hide || password || isSecret) {
return
}
const simplePreview = getSimplePreview(text)

const status = [mood ? `心情: ${mood}` : '']
.concat(weather ? `天气: ${weather}` : '')
.filter(Boolean)
.join('\t')
const message = `${owner.name} ${publishDescription}: ${title}\n${
status ? `\n${status}\n\n` : '\n'
}${simplePreview}\n\n前往阅读:${await urlBuilder.build(
payload as NoteModel,
)}`

if (Array.isArray(images) && images.length > 0) {
await sendToGroup([
{
type: 'photo',
url: images.map((i) => i.src),
caption: message,
},
])
} else {
await sendToGroup(message)
}

return
}

case MxSocketEventTypes.LINK_APPLY: {
const { avatar, name, url, description, state } = payload as LinkModel
if (state !== LinkState.Audit) {
return
}

const message =
`有新的友链申请了耶!\n` + `${name}\n${url}\n\n` + `${description}`
const sendable: Sendable = []

if (avatar) {
sendable.push({
type: 'photo',
url: [avatar],
caption: message,
})
} else {
sendable.push({
type: 'text',
content: message,
})
}

await sendToGroup(sendable)
return
}
case MxSocketEventTypes.COMMENT_CREATE: {
const { author, text, refType, parent, id, isWhispers } =
payload as CommentModel
const siteTitle = aggregateData.seo.title
if (isWhispers) {
await sendToGroup(`「${siteTitle}」嘘,有人说了一句悄悄话。`)
return
}

const parentIsWhispers = (() => {
const walk: (parent: any) => boolean = (parent) => {
if (!parent || typeof parent == 'string') {
return false
}
return parent.isWhispers || walk(parent?.parent)
}

return walk(parent)
})()
if (parentIsWhispers) {
return
}

const refId = payload.ref?.id || payload.ref?._id || payload.ref
let refModel: PostModel | NoteModel | PageModel | null = null

switch (refType) {
case 'Post': {
refModel = await apiClient.post.getPost(refId)
break
}
case 'Note': {
refModel = await apiClient.note.getNoteById(refId as string)

break
}
case 'Page': {
refModel = await apiClient.page.getById(refId)
break
}
}

if (!refModel) {
return
}
const isMaster = author === owner.name || author === owner.username
let message: string
if (isMaster && !parent) {
message = `${author} 在「${
refModel.title
}」发表之后的 ${relativeTimeFromNow(refModel.created)}又说:${text}`
} else {
message = `${author} 在「${refModel.title}」发表了评论:${text}`
}

const uri = (() => {
switch (refType) {
case 'Post': {
return `/posts/${(refModel as PostModel).category.slug}/${
(refModel as PostModel).slug
}`
}
case 'Note': {
return `/notes/${(refModel as NoteModel).nid}`
}
case 'Page': {
return `/${(refModel as PageModel).slug}`
}
}
})()

const webUrl = aggregateData.url.webUrl
if (uri) {
message += `\n\n查看评论:${webUrl}${uri}#comments-${id}`
}

await sendToGroup(message)
return
}
case MxSocketEventTypes.SAY_CREATE: {
const { author, source, text } = payload as SayModel

const message =
`${owner.name} 发布一条说说:\n` +
`${text}\n${source || author ? `来自: ${source || author}` : ''}`
await sendToGroup(message)

return
}
case MxSocketEventTypes.RECENTLY_CREATE: {
const { content } = payload as RecentlyModel

const message = `${owner.name} 发布一条动态说:\n${content}`
await sendToGroup(message)

return
}
case MxSystemEventBusEvents.SystemException: {
const { message, stack } = payload as Error
const messageWithStack = `来自 Mix Space 的系统异常:${getShortDateTime(
new Date(),
)}\n${message}\n\n${stack}`
await sendToGroup(messageWithStack)
return
}
}
}

const getSimplePreview = (text: string) => {
const _text = rmd(text) as string
return _text.length > 200 ? `${_text.slice(0, 200)}...` : _text
}

function checkNoteIsSecret(note: NoteModel) {
if (!note.secret) {
return false
}
const isSecret = dayjs(note.secret).isAfter(new Date())

return isSecret
}

0 comments on commit dca6ebe

Please sign in to comment.