Skip to content

Commit 02d2b53

Browse files
committed
feat: add notification
1 parent 09f5cd5 commit 02d2b53

File tree

11 files changed

+251
-30
lines changed

11 files changed

+251
-30
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@vue-reactivity/fs": "^0.1.1",
3636
"commander": "^9.1.0",
3737
"enquirer": "^2.3.6",
38-
"jike-sdk": "^0.10.0",
38+
"jike-sdk": "^0.10.2",
3939
"node-fetch": "^3.2.3",
4040
"open": "^8.4.0",
4141
"terminal-image": "^2.0.0"

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { version } from './modules/version'
33
import { versionNumber } from './constants'
44
import { user } from './modules/user'
55
import { post } from './modules/post'
6+
import { msg } from './modules/msg'
67
import { initConfig } from './utils/config'
78

89
async function main() {
@@ -13,6 +14,7 @@ async function main() {
1314
.option('-u, --user <users...>', 'specify users')
1415
.addCommand(user)
1516
.addCommand(post)
17+
.addCommand(msg)
1618
.addCommand(version)
1719
.version(versionNumber)
1820
.parse()

src/modules/msg.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { logger } from '@poppinss/cliui'
2+
import { createCommand } from 'commander'
3+
import { JikeClient, limit } from 'jike-sdk/node'
4+
import { format } from 'date-fns'
5+
import { displayImage } from '../utils/terminal'
6+
import { displayUser, filterUsers } from '../utils/user'
7+
import type { Spinner } from '@poppinss/cliui/build/src/Logger/Spinner'
8+
import type { Entity } from 'jike-sdk/node'
9+
10+
interface NotificationOptions {
11+
avatar?: boolean
12+
image?: boolean
13+
count?: number
14+
}
15+
16+
export const msg = createCommand('msg')
17+
.description('display notifications')
18+
.aliases(['message', 'notification'])
19+
.option('--no-avatar', 'do not show avatar')
20+
.option('--no-image', 'do not show image')
21+
.option('-c, --count <count>', 'notification max count', '30')
22+
.action(() => {
23+
const opts = msg.opts<NotificationOptions>()
24+
showNotifications(opts)
25+
})
26+
27+
const showNotifications = async (opts: NotificationOptions) => {
28+
const [user] = filterUsers()
29+
const client = JikeClient.fromJSON(user)
30+
31+
const count = +(opts.count ?? 30)
32+
const spinner = logger.await('Loading notifications...')
33+
const notifications = await client
34+
.queryNotifications({
35+
limit: limit.limitMaxCount(count),
36+
onNextPage: (page, key, data) => {
37+
spinner.update(
38+
`Loading notifications... (page ${page}, ${+(
39+
(data.length / count) *
40+
100
41+
).toFixed(2)}%)`
42+
)
43+
},
44+
})
45+
.finally(() => spinner.stop())
46+
47+
logger.success('Loading notifications done!')
48+
49+
{
50+
const divider = logger.colors.gray('─'.repeat(process.stdout.columns || 30))
51+
52+
let spinner: Spinner | undefined
53+
if (opts.image) spinner = logger.await('Downloading images')
54+
55+
const texts = (
56+
await Promise.all(
57+
notifications.map((n) =>
58+
renderNotification(n, opts).then((result) => [...result, divider])
59+
)
60+
).finally(() => spinner?.stop())
61+
).flat()
62+
texts.unshift(divider)
63+
64+
process.stdout.write(`${texts.join('\n')}\n`)
65+
}
66+
}
67+
68+
const EMPTY_PLACEHOLDER = '(EMPTY)'
69+
70+
async function renderNotification(
71+
n: Entity.Notification,
72+
{ avatar, image }: NotificationOptions
73+
): Promise<string[]> {
74+
const users = n.actionItem?.users ?? []
75+
let usersText =
76+
users.length > 1
77+
? `${users.map((user) => displayUser(user)).join(', ')} `
78+
: users[0]
79+
? `${displayUser(users[0], true)} `
80+
: '-'
81+
const bio = logger.colors.gray(users[0].bio ?? '')
82+
83+
const usersCount = n.actionItem?.usersCount
84+
if (typeof usersCount === 'number' && users.length !== usersCount) {
85+
usersText += `等 ${usersCount} 人`
86+
}
87+
const content = n.actionItem?.content
88+
let refContent = n.referenceItem?.content?.trim() || '(empty)'
89+
if (refContent.length > 100) {
90+
refContent = `${refContent.slice(0, 100)}...`
91+
}
92+
93+
const userAvatarUrl = users[0]?.avatarImage.smallPicUrl
94+
const userAvatar = (height: number) => {
95+
if (!avatar || !image) return EMPTY_PLACEHOLDER
96+
return displayImage(userAvatarUrl, height).then(({ result }) => result)
97+
}
98+
99+
const referenceImageUrl = n.referenceItem?.referenceImageUrl
100+
const referenceImage = (height = 8) => {
101+
if (!image || !referenceImageUrl) return EMPTY_PLACEHOLDER
102+
return displayImage(referenceImageUrl, height).then(({ result }) => result)
103+
}
104+
105+
let texts = await renderStory()
106+
texts ||= await renderPersonalUpdate()
107+
texts ||= await renderUser()
108+
texts ||= await renderAvatar()
109+
110+
if (texts) {
111+
const timeStr = format(new Date(n.createdAt), 'yyyy-MM-dd HH:mm:ss')
112+
texts.unshift(logger.colors.gray(timeStr))
113+
return texts.filter((text) => text.trim() !== EMPTY_PLACEHOLDER)
114+
} else {
115+
warnUnknownType(n)
116+
return []
117+
}
118+
119+
async function renderStory(): Promise<string[] | undefined> {
120+
switch (n.type) {
121+
case 'LIKE_STORY':
122+
return [`👍 ${usersText}赞了你的日记`, await referenceImage()]
123+
case 'REPLIED_TO_STORY_COMMENT':
124+
return [await userAvatar(4), `📨 ${usersText}回复了你的留言`, content]
125+
case 'COMMENT_STORY':
126+
return [
127+
`📨 ${usersText}给你的日记留言了:`,
128+
content,
129+
await referenceImage(),
130+
]
131+
}
132+
}
133+
134+
async function renderPersonalUpdate(): Promise<string[] | undefined> {
135+
switch (n.type) {
136+
case 'LIKE_PERSONAL_UPDATE':
137+
return [`👍 ${usersText}赞了你的动态:`, refContent]
138+
case 'COMMENT_PERSONAL_UPDATE':
139+
return [await userAvatar(4), `📨 ${usersText}评论了你`, content]
140+
case 'REPLIED_TO_PERSONAL_UPDATE_COMMENT':
141+
return [await userAvatar(4), `📨 ${usersText}回复了你的评论`, content]
142+
case 'LIKE_PERSONAL_UPDATE_COMMENT':
143+
return [`👍 ${usersText}赞了你的评论:`, refContent]
144+
case 'COMMENT_AND_REPOST':
145+
return [`📨 ${usersText}评论并转发了你:`, content]
146+
}
147+
}
148+
149+
async function renderUser(): Promise<string[] | undefined> {
150+
switch (n.type) {
151+
case 'USER_RESPECT':
152+
return [`🎉 ${usersText}夸了夸你`, content]
153+
case 'MENTION':
154+
return [`👋 ${usersText}@了你:`, content]
155+
case 'USER_FOLLOWED':
156+
return [await userAvatar(4), bio, `✨ ${usersText}关注了你`]
157+
case 'USER_SILENT_FOLLOWED':
158+
return [
159+
await userAvatar(4),
160+
bio,
161+
`✨ ${usersText}关注了你(静默关注 🤔)`,
162+
]
163+
case 'PERSONAL_UPDATE_REPOSTED':
164+
return [
165+
await userAvatar(4),
166+
`🔗 ${usersText}转发了你的动态`,
167+
refContent,
168+
]
169+
}
170+
}
171+
172+
async function renderAvatar(): Promise<string[] | undefined> {
173+
switch (n.type) {
174+
case 'LIKE_AVATAR':
175+
return [await referenceImage(4), `👍 ${usersText}赞了你的头像`]
176+
case 'AVATAR_GREET':
177+
return [await referenceImage(4), `👉 ${usersText}赞了你的头像`]
178+
}
179+
}
180+
}
181+
182+
const warnUnknownType = (n: Entity.Notification) => {
183+
console.log(n)
184+
const info = [n.type, n.actionType, n.actionItem.type].join('||')
185+
logger.warning(
186+
`Unknown notification: ${info}. Please send it to developer, thanks!`
187+
)
188+
}

src/modules/post.ts renamed to src/modules/post/create.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,27 @@ import { ApiOptions, JikeClient } from 'jike-sdk/node'
66
import { format } from 'date-fns'
77
import { logger, sticker } from '@poppinss/cliui'
88
import enquirer from 'enquirer'
9-
import { configDir } from '../utils/config'
10-
import { displayUser, filterUsers } from '../utils/user'
11-
import { errorAndExit } from '../utils/log'
9+
import { configDir } from '../../utils/config'
10+
import { displayConfigUser, filterUsers } from '../../utils/user'
11+
import { errorAndExit } from '../../utils/log'
1212

13-
interface PostOptions {
13+
interface CreateOptions {
1414
content?: string
1515
topic?: string
1616
}
1717

18-
export const post = createCommand('post')
18+
export const create = createCommand('create')
19+
.alias('new')
1920
.description('send a post')
2021
.option('-c, --content <content>', 'the content you want to post')
2122
.option('-t, --topic <topicId>', 'topic id')
2223
.action(() => {
23-
const opts = post.opts<PostOptions>()
24+
const opts = create.opts<CreateOptions>()
2425
createPost(opts)
2526
})
2627

2728
/** 发布动态 */
28-
export const createPost = async ({ content, topic }: PostOptions) => {
29+
export const createPost = async ({ content, topic }: CreateOptions) => {
2930
const users = filterUsers()
3031

3132
if (!content) {
@@ -56,7 +57,7 @@ export const createPost = async ({ content, topic }: PostOptions) => {
5657
type: 'confirm',
5758
name: 'isConfirm',
5859
message: `Are you sure to send the above with accounts ${users
59-
.map((user) => displayUser(user))
60+
.map((user) => displayConfigUser(user))
6061
.join(', ')}?`,
6162
initial: true,
6263
})
@@ -73,7 +74,7 @@ export const createPost = async ({ content, topic }: PostOptions) => {
7374
})
7475
.catch((err) => logger.fatal(err))
7576
logger.success(
76-
`${logger.colors.bold(displayUser(user))} posted successfully!`
77+
`${logger.colors.bold(displayConfigUser(user))} posted successfully!`
7778
)
7879
}
7980
}

src/modules/post/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createCommand } from 'commander'
2+
import { create } from './create'
3+
4+
export const post = createCommand('post')
5+
.description('post-related operations')
6+
.addHelpText(
7+
'after',
8+
`
9+
10+
Example call:
11+
$ jike-cli post new --content="hello world"
12+
`
13+
)
14+
.usage('<command> [flags]')
15+
.addCommand(create)

src/modules/user/logout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { logger } from '@poppinss/cliui'
22
import { createCommand } from 'commander'
33
import { config, isSameUser } from '../../utils/config'
44
import { errorAndExit } from '../../utils/log'
5-
import { displayUser, filterUsers } from '../../utils/user'
5+
import { displayConfigUser, filterUsers } from '../../utils/user'
66

77
export const logout = createCommand('logout [users...]')
88
.description('logout users')
@@ -19,7 +19,7 @@ export const logoutUser = (queries: string[]) => {
1919
cfg.users = cfg.users.filter((user) => {
2020
const shouldRemove = removes.some((remove) => isSameUser(user, remove))
2121
if (shouldRemove) {
22-
logger.success(`Logout ${displayUser(user)}.`)
22+
logger.success(`Logout ${displayConfigUser(user)}.`)
2323
}
2424
return !shouldRemove
2525
})

src/modules/user/profile.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import process from 'process'
22
import { createCommand } from 'commander'
33
import { logger, sticker, table } from '@poppinss/cliui'
44
import { format } from 'date-fns'
5-
import fetch from 'node-fetch'
6-
import terminalImage from 'terminal-image'
75
import { JikeClient } from 'jike-sdk/node'
86
import { filterUsers } from '../../utils/user'
7+
import { displayImage } from '../../utils/terminal'
98
import type { ApiResponses } from 'jike-sdk/node'
109

1110
const { colors } = logger
@@ -49,17 +48,9 @@ export const queryProfile = async ({
4948
return
5049
}
5150

52-
const avatarResponse = await fetch(result.user.avatarImage.middlePicUrl)
53-
.then((res) => res.arrayBuffer())
54-
.then((ab) => Buffer.from(ab))
55-
const avatar = await terminalImage.buffer(avatarResponse, {
56-
height: 8,
57-
preserveAspectRatio: true,
58-
})
59-
process.stdout.write(`${avatar}\n`)
51+
await displayImage(result.user.avatarImage.middlePicUrl)
6052

6153
const createdAt = new Date(result.user.createdAt)
62-
;('yyyy-MM-dd HH:mm:ss')
6354
const createdAtStr = format(
6455
new Date(result.user.createdAt),
6556
'yyyy-MM-dd HH:mm:ss'

src/modules/user/renew.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { logger } from '@poppinss/cliui'
22
import { createCommand } from 'commander'
33
import { JikeClient } from 'jike-sdk/node'
4-
import { displayUser, filterUsers } from '../../utils/user'
4+
import { displayConfigUser, filterUsers } from '../../utils/user'
55

66
export const renew = createCommand('renew')
77
.description('refresh user info and token')
@@ -12,7 +12,7 @@ export const renewUsers = async () => {
1212
const users = filterUsers()
1313

1414
for (const user of users) {
15-
const userName = displayUser(user)
15+
const userName = displayConfigUser(user)
1616
const spinner = logger.await(`Renew user: ${userName}`)
1717
const client = JikeClient.fromJSON(user)
1818
try {

src/utils/terminal.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import terminalImage from 'terminal-image'
2+
3+
export const displayImage = async (url: string, height = 8) => {
4+
const response = await fetch(url)
5+
.then((res) => res.arrayBuffer())
6+
.then((ab) => Buffer.from(ab))
7+
const result = await terminalImage
8+
.buffer(response, {
9+
height,
10+
preserveAspectRatio: true,
11+
})
12+
.then((img) => img.trim())
13+
return {
14+
result,
15+
render: () => process.stdout.write(`${result}\n`),
16+
}
17+
}

0 commit comments

Comments
 (0)