-
Notifications
You must be signed in to change notification settings - Fork 921
/
formatting.ts
122 lines (108 loc) · 3.81 KB
/
formatting.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { MessageEntity, User } from 'typegram'
import { zip } from '../../util'
export interface FmtString {
text: string
entities?: MessageEntity[]
parse_mode?: undefined
}
export class FmtString implements FmtString {
constructor(public text: string, entities?: MessageEntity[]) {
if (entities) {
this.entities = entities
// force parse_mode to undefined if entities are present
this.parse_mode = undefined
}
}
static normalise(content: string | FmtString) {
if (typeof content === 'string') return new FmtString(content)
return content
}
}
export namespace Types {
// prettier-ignore
export type Containers = 'bold' | 'italic' | 'spoiler' | 'strikethrough' | 'underline'
export type NonContainers = 'code' | 'pre'
export type Text = Containers | NonContainers
}
type TemplateParts = string | FmtString | readonly (FmtString | string)[]
// eslint-disable-next-line @typescript-eslint/ban-types
type Any = {} | undefined | null
const isArray: <T>(xs: T | readonly T[]) => xs is readonly T[] = Array.isArray
/** Given a base FmtString and something to append to it, mutates the base */
const _add = (base: FmtString, next: FmtString | Any) => {
const len = base.text.length
if (next instanceof FmtString) {
base.text = `${base.text}${next.text}`
// next.entities could be undefined and condition will fail
for (let i = 0; i < (next.entities?.length || 0); i++) {
// because of the above condition, next.entities[i] cannot be undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const entity = next.entities![i]!
// base.entities is ensured by caller
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
base.entities!.push({ ...entity, offset: entity.offset + len })
}
} else base.text = `${base.text}${next}`
}
/**
* Given an `Iterable<FmtString | string | Any>` and a separator, flattens the list into a single FmtString.
* Analogous to Array#join -> string, but for FmtString
*/
export const join = (
fragments: Iterable<FmtString | string | Any>,
separator?: string | FmtString
) => {
const result = new FmtString('')
// ensure entities array so loop doesn't need to check
result.entities = []
const iter = fragments[Symbol.iterator]()
let curr = iter.next()
while (!curr.done) {
_add(result, curr.value)
curr = iter.next()
if (separator && !curr.done) _add(result, separator)
}
// set parse_mode: undefined if entities are present
if (result.entities.length) result.parse_mode = undefined
// remove entities array if not relevant
else delete result.entities
return result
}
/** Internal constructor for all fmt helpers */
export function _fmt(
kind?: Types.Containers
): (parts: TemplateParts, ...items: (Any | FmtString)[]) => FmtString
export function _fmt(
kind: Types.NonContainers
): (parts: TemplateParts, ...items: Any[]) => FmtString
export function _fmt(
kind: 'pre',
opts: { language: string }
): (parts: TemplateParts, ...items: Any[]) => FmtString
export function _fmt(kind: Types.Text | undefined, opts?: object) {
return function fmt(parts: TemplateParts, ...items: (Any | FmtString)[]) {
parts = isArray(parts) ? parts : [parts]
const result = join(zip(parts, items))
if (kind) {
result.entities ??= []
result.entities.unshift({
type: kind,
offset: 0,
length: result.text.length,
...opts,
})
result.parse_mode = undefined
}
return result
}
}
export const linkOrMention = (
content: string | FmtString,
data:
| { type: 'text_link'; url: string }
| { type: 'text_mention'; user: User }
) => {
const { text, entities = [] } = FmtString.normalise(content)
entities.unshift(Object.assign(data, { offset: 0, length: text.length }))
return new FmtString(text, entities)
}