cordo 2.9.4 → 2.10.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordo",
3
- "version": "2.9.4",
3
+ "version": "2.10.0-rc.1",
4
4
  "description": "A framework for handling complex discord api interactions",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -0,0 +1,113 @@
1
+ import { ComponentType, createComponent } from "../component"
2
+ import { Hooks } from "../../core/hooks"
3
+ import { value, type CordoFunct, type CordoFunctRun } from "../../functions"
4
+ import { FunctCompiler } from "../../functions/compiler"
5
+ import { MaxLengthConstants } from "../../lib/constants"
6
+
7
+
8
+ type CheckboxGroupOption<Value extends string = string> = ({
9
+ label: string
10
+ description?: string
11
+ default?: boolean
12
+ value?: Value
13
+ onSelect?: CordoFunct | CordoFunctRun
14
+ })
15
+
16
+ export function checkboxGroup<Values extends string = string>() {
17
+ let labelVal: string | undefined = undefined
18
+ let descriptionVal: string | undefined = undefined
19
+ let optionsVal: CheckboxGroupOption[] = []
20
+ let minValues: number | undefined = undefined
21
+ let maxValues: number | undefined = undefined
22
+ let requiredVal: boolean = false
23
+ const functVal: CordoFunct[] = []
24
+
25
+ function getLabel() {
26
+ if (!labelVal)
27
+ return 'Pick one'
28
+ return Hooks.callHook(
29
+ 'transformUserFacingText',
30
+ labelVal,
31
+ { component: 'CheckboxGroup', position: 'label' }
32
+ )
33
+ }
34
+
35
+ function getDescription() {
36
+ if (!descriptionVal)
37
+ return undefined
38
+ return Hooks.callHook(
39
+ 'transformUserFacingText',
40
+ descriptionVal,
41
+ { component: 'CheckboxGroup', position: 'description' }
42
+ )
43
+ }
44
+
45
+ function getOptions(): CheckboxGroupOption[] {
46
+ return optionsVal.slice(0, 10).map(o => ({
47
+ ...o,
48
+ label: Hooks.callHook('transformUserFacingText', o.label, { component: 'CheckboxGroup', position: 'option.label' })?.slice(0, MaxLengthConstants.SELECT_OPTION_LABEL),
49
+ description: o.description
50
+ ? Hooks.callHook('transformUserFacingText', o.description, { component: 'CheckboxGroup', position: 'option.description' })?.slice(0, MaxLengthConstants.SELECT_OPTION_DESCRIPTION)
51
+ : undefined,
52
+ value: FunctCompiler.toCustomId([
53
+ ...(o.onSelect
54
+ ? Array.isArray(o.onSelect)
55
+ ? o.onSelect
56
+ : [ o.onSelect ]
57
+ : []
58
+ ),
59
+ o.value ? value(o.value) : null
60
+ ])
61
+ }))
62
+ }
63
+
64
+ const out = {
65
+ ...createComponent('CheckboxGroup', () => ({
66
+ type: ComponentType.CheckboxGroup,
67
+ label: getLabel(),
68
+ description: getDescription(),
69
+ min_values: Math.max(minValues ?? 1, 0),
70
+ max_values: Math.min(maxValues ?? minValues ?? 1, optionsVal.length),
71
+ required: requiredVal,
72
+ options: getOptions(),
73
+ custom_id: FunctCompiler.toCustomId(requiredVal ? [] : functVal),
74
+ 'modal:label': getLabel(),
75
+ 'modal:description': getDescription(),
76
+ })),
77
+
78
+ required(required = true) {
79
+ requiredVal = required
80
+ return out
81
+ },
82
+ min: (num: number = 1) => {
83
+ minValues = num
84
+ return out
85
+ },
86
+ max: (num: number = 10) => {
87
+ maxValues = num
88
+ return out
89
+ },
90
+ onSubmit: (...funct: CordoFunctRun) => {
91
+ functVal.push(...funct)
92
+ return out
93
+ },
94
+ setOptions(options: Array<CheckboxGroupOption<Values>>) {
95
+ optionsVal = options
96
+ return out
97
+ },
98
+ addOption(o: CheckboxGroupOption<Values>) {
99
+ optionsVal.push(o)
100
+ return out
101
+ },
102
+ withLabel: (text: string) => {
103
+ labelVal = text
104
+ return out
105
+ },
106
+ withDescription: (text: string) => {
107
+ descriptionVal = text
108
+ return out
109
+ },
110
+ }
111
+
112
+ return out
113
+ }
@@ -0,0 +1,75 @@
1
+ import { ComponentType, createComponent } from "../component"
2
+ import { Hooks } from "../../core/hooks"
3
+ import { type CordoFunct, type CordoFunctRun } from "../../functions"
4
+ import { FunctCompiler } from "../../functions/compiler"
5
+
6
+
7
+ export function fileUpload() {
8
+ let labelVal: string | undefined = undefined
9
+ let descriptionVal: string | undefined = undefined
10
+ let minValues: number | undefined = undefined
11
+ let maxValues: number | undefined = undefined
12
+ let requiredVal: boolean = false
13
+ const functVal: CordoFunct[] = []
14
+
15
+ function getLabel() {
16
+ if (!labelVal)
17
+ return 'Your Response'
18
+ return Hooks.callHook(
19
+ 'transformUserFacingText',
20
+ labelVal,
21
+ { component: 'FileUpload', position: 'label' }
22
+ )
23
+ }
24
+
25
+ function getDescription() {
26
+ if (!descriptionVal)
27
+ return undefined
28
+ return Hooks.callHook(
29
+ 'transformUserFacingText',
30
+ descriptionVal,
31
+ { component: 'FileUpload', position: 'description' }
32
+ )
33
+ }
34
+
35
+ const out = {
36
+ ...createComponent('FileUpload', () => ({
37
+ type: ComponentType.FileUpload,
38
+ label: getLabel(),
39
+ description: getDescription(),
40
+ min_values: Math.max(minValues ?? 1, 0),
41
+ max_values: Math.min(maxValues ?? minValues ?? 1, 10),
42
+ required: requiredVal,
43
+ custom_id: FunctCompiler.toCustomId(functVal),
44
+ 'modal:label': getLabel(),
45
+ 'modal:description': getDescription(),
46
+ })),
47
+
48
+ min: (num: number = 1) => {
49
+ minValues = num
50
+ return out
51
+ },
52
+ max: (num: number = 10) => {
53
+ maxValues = num
54
+ return out
55
+ },
56
+ required(required = true) {
57
+ requiredVal = required
58
+ return out
59
+ },
60
+ onSubmit: (...funct: CordoFunctRun) => {
61
+ functVal.push(...funct)
62
+ return out
63
+ },
64
+ withLabel: (text: string) => {
65
+ labelVal = text
66
+ return out
67
+ },
68
+ withDescription: (text: string) => {
69
+ descriptionVal = text
70
+ return out
71
+ },
72
+ }
73
+
74
+ return out
75
+ }
@@ -1,3 +1,4 @@
1
+ import { URL } from "node:url"
1
2
  import { ButtonStyle } from "discord-api-types/v10"
2
3
  import { ComponentType, createComponent } from "../component"
3
4
  import { Hooks } from "../../core/hooks"
@@ -1,13 +1,38 @@
1
1
  import { Hooks } from "../../core/hooks"
2
2
  import type { CordoFunct, CordoFunctRun } from "../../functions"
3
3
  import { FunctCompiler } from "../../functions/compiler"
4
- import { ComponentType, createComponent, renderComponentList, type CordoComponent } from "../component"
4
+ import { ComponentType, createComponent, renderComponent, renderComponentList, type CordoComponent, type CordoComponentPayload, type StringComponentType } from "../component"
5
5
  import type { CordoModifier } from "../modifier"
6
6
 
7
7
 
8
- export type AllowedComponents = CordoComponent<'TextInput'> | CordoModifier
8
+ export type AllowedComponents = CordoComponent<'TextDisplay' | 'TextInput' | 'RoleSelect' | 'UserSelect' | 'StringSelect' | 'ChannelSelect' | 'MentionableSelect'> | CordoModifier
9
9
  type AllowedComponentArray = Array<AllowedComponents | AllowedComponents[]>
10
10
 
11
+ const componentsRequiringLabel: StringComponentType[] = [ 'TextInput', 'RoleSelect', 'UserSelect', 'StringSelect', 'ChannelSelect', 'MentionableSelect', 'ChannelSelect', 'FileUpload', 'RadioGroup', 'CheckboxGroup', 'Checkbox' ]
12
+ function labelize(component: CordoComponent<StringComponentType>, parsed: CordoComponentPayload<StringComponentType>): CordoComponent<StringComponentType> | null {
13
+ if (!parsed.visible)
14
+ return null
15
+
16
+ if (!componentsRequiringLabel.includes(parsed.nativeName))
17
+ return component
18
+
19
+ return createComponent('Label', ({ attributes, hirarchy }) => {
20
+ const rendered = renderComponent(component, 'Modal', hirarchy, attributes)
21
+ return {
22
+ type: ComponentType.Label,
23
+ label: rendered?.['modal:label'] ?? parsed.nativeName,
24
+ id: rendered?.['modal:id'],
25
+ description: rendered?.['modal:description'],
26
+ component: {
27
+ ...rendered,
28
+ 'modal:label': undefined,
29
+ 'modal:id': undefined,
30
+ 'modal:description': undefined
31
+ }
32
+ }
33
+ })
34
+ }
35
+
11
36
  export function modal(...components: AllowedComponentArray) {
12
37
  let titleVal: string | undefined = undefined
13
38
  const functVal: CordoFunct[] = []
@@ -26,7 +51,7 @@ export function modal(...components: AllowedComponentArray) {
26
51
  const out = {
27
52
  ...createComponent('Modal', ({ hirarchy, attributes }) => ({
28
53
  type: ComponentType.Modal,
29
- components: renderComponentList(components.flat(), 'Modal', hirarchy, attributes),
54
+ components: renderComponentList(components.flat(), 'Modal', hirarchy, attributes, labelize),
30
55
  title: getTitle(),
31
56
  custom_id: FunctCompiler.toCustomId(functVal)
32
57
  })),
@@ -35,7 +60,7 @@ export function modal(...components: AllowedComponentArray) {
35
60
  titleVal = value
36
61
  return out
37
62
  },
38
- onClick: (...funct: CordoFunctRun) => {
63
+ onSubmit: (...funct: CordoFunctRun) => {
39
64
  functVal.push(...funct)
40
65
  return out
41
66
  },
@@ -0,0 +1,101 @@
1
+ import { ComponentType, createComponent } from "../component"
2
+ import { Hooks } from "../../core/hooks"
3
+ import { value, type CordoFunct, type CordoFunctRun } from "../../functions"
4
+ import { FunctCompiler } from "../../functions/compiler"
5
+ import { MaxLengthConstants } from "../../lib/constants"
6
+
7
+
8
+ type RadioGroupOption<Value extends string = string> = ({
9
+ label: string
10
+ description?: string
11
+ default?: boolean
12
+ value?: Value
13
+ onSelect?: CordoFunct | CordoFunctRun
14
+ })
15
+
16
+ export function radioGroup<Values extends string = string>() {
17
+ let labelVal: string | undefined = undefined
18
+ let descriptionVal: string | undefined = undefined
19
+ let optionsVal: RadioGroupOption[] = []
20
+ let requiredVal: boolean = false
21
+ const functVal: CordoFunct[] = []
22
+
23
+ function getLabel() {
24
+ if (!labelVal)
25
+ return 'Pick one'
26
+ return Hooks.callHook(
27
+ 'transformUserFacingText',
28
+ labelVal,
29
+ { component: 'RadioGroup', position: 'label' }
30
+ )
31
+ }
32
+
33
+ function getDescription() {
34
+ if (!descriptionVal)
35
+ return undefined
36
+ return Hooks.callHook(
37
+ 'transformUserFacingText',
38
+ descriptionVal,
39
+ { component: 'RadioGroup', position: 'description' }
40
+ )
41
+ }
42
+
43
+ function getOptions(): RadioGroupOption[] {
44
+ return optionsVal.slice(0, 25).map(o => ({
45
+ ...o,
46
+ label: Hooks.callHook('transformUserFacingText', o.label, { component: 'RadioGroup', position: 'option.label' })?.slice(0, MaxLengthConstants.SELECT_OPTION_LABEL),
47
+ description: o.description
48
+ ? Hooks.callHook('transformUserFacingText', o.description, { component: 'RadioGroup', position: 'option.description' })?.slice(0, MaxLengthConstants.SELECT_OPTION_DESCRIPTION)
49
+ : undefined,
50
+ value: FunctCompiler.toCustomId([
51
+ ...(o.onSelect
52
+ ? Array.isArray(o.onSelect)
53
+ ? o.onSelect
54
+ : [ o.onSelect ]
55
+ : []
56
+ ),
57
+ o.value ? value(o.value) : null
58
+ ])
59
+ }))
60
+ }
61
+
62
+ const out = {
63
+ ...createComponent('RadioGroup', () => ({
64
+ type: ComponentType.RadioGroup,
65
+ label: getLabel(),
66
+ description: getDescription(),
67
+ required: requiredVal,
68
+ options: getOptions(),
69
+ custom_id: FunctCompiler.toCustomId(requiredVal ? [] : functVal),
70
+ 'modal:label': getLabel(),
71
+ 'modal:description': getDescription(),
72
+ })),
73
+
74
+ required(required = true) {
75
+ requiredVal = required
76
+ return out
77
+ },
78
+ onSubmit: (...funct: CordoFunctRun) => {
79
+ functVal.push(...funct)
80
+ return out
81
+ },
82
+ setOptions(options: Array<RadioGroupOption<Values>>) {
83
+ optionsVal = options
84
+ return out
85
+ },
86
+ addOption(o: RadioGroupOption<Values>) {
87
+ optionsVal.push(o)
88
+ return out
89
+ },
90
+ withLabel: (text: string) => {
91
+ labelVal = text
92
+ return out
93
+ },
94
+ withDescription: (text: string) => {
95
+ descriptionVal = text
96
+ return out
97
+ },
98
+ }
99
+
100
+ return out
101
+ }
@@ -13,6 +13,8 @@ type SelectMenuOption<Value extends string = string> = Omit<APISelectMenuOption,
13
13
 
14
14
  export function selectString<Values extends string = string>() {
15
15
  let placeholderVal: string | undefined = undefined
16
+ let labelVal: string | undefined = undefined
17
+ let descriptionVal: string | undefined = undefined
16
18
  let minValues: number | undefined = undefined
17
19
  let maxValues: number | undefined = undefined
18
20
  let optionsVal: SelectMenuOption[] = []
@@ -29,6 +31,26 @@ export function selectString<Values extends string = string>() {
29
31
  )
30
32
  }
31
33
 
34
+ function getLabel() {
35
+ if (!labelVal)
36
+ return 'Your Response'
37
+ return Hooks.callHook(
38
+ 'transformUserFacingText',
39
+ labelVal,
40
+ { component: 'StringSelect', position: 'label' }
41
+ )
42
+ }
43
+
44
+ function getDescription() {
45
+ if (!descriptionVal)
46
+ return undefined
47
+ return Hooks.callHook(
48
+ 'transformUserFacingText',
49
+ descriptionVal,
50
+ { component: 'StringSelect', position: 'description' }
51
+ )
52
+ }
53
+
32
54
  function getOptions(): SelectMenuOption[] {
33
55
  return optionsVal.slice(0, 25).map(o => ({
34
56
  ...o,
@@ -56,7 +78,9 @@ export function selectString<Values extends string = string>() {
56
78
  max_values: maxValues ? Math.min(optionsVal.length, maxValues) : undefined,
57
79
  disabled: disabledVal,
58
80
  options: getOptions(),
59
- custom_id: FunctCompiler.toCustomId(disabledVal ? [] : functVal)
81
+ custom_id: FunctCompiler.toCustomId(disabledVal ? [] : functVal),
82
+ 'modal:label': getLabel(),
83
+ 'modal:description': getDescription(),
60
84
  })),
61
85
 
62
86
  placeholder: (text: string) => {
@@ -86,7 +110,15 @@ export function selectString<Values extends string = string>() {
86
110
  addOption(o: SelectMenuOption<Values>) {
87
111
  optionsVal.push(o)
88
112
  return out
89
- }
113
+ },
114
+ withLabel: (text: string) => {
115
+ labelVal = text
116
+ return out
117
+ },
118
+ withDescription: (text: string) => {
119
+ descriptionVal = text
120
+ return out
121
+ },
90
122
  }
91
123
 
92
124
  return out
@@ -6,6 +6,7 @@ import { FunctCompiler } from "../../functions/compiler"
6
6
  export function textInput() {
7
7
  let placeholderVal: string | undefined = undefined
8
8
  let labelVal: string | undefined = undefined
9
+ let descriptionVal: string | undefined = undefined
9
10
  let minLength: number | undefined = undefined
10
11
  let maxLength: number | undefined = undefined
11
12
  let requiredVal: boolean | undefined = undefined
@@ -33,17 +34,28 @@ export function textInput() {
33
34
  )
34
35
  }
35
36
 
37
+ function getDescription() {
38
+ if (!descriptionVal)
39
+ return undefined
40
+ return Hooks.callHook(
41
+ 'transformUserFacingText',
42
+ descriptionVal,
43
+ { component: 'TextInput', position: 'description' }
44
+ )
45
+ }
46
+
36
47
  const out = {
37
48
  ...createComponent('TextInput', () => ({
38
49
  type: ComponentType.TextInput,
39
50
  placeholder: getPlaceholder(),
40
- label: getLabel(),
41
51
  min_length: minLength,
42
52
  max_length: maxLength,
43
53
  required: requiredVal,
44
54
  style: sizeVal ?? 1,
45
55
  value: currentVal,
46
- custom_id: ref ?? FunctCompiler.toCustomId([]) // get a noop if no ref
56
+ custom_id: ref ?? FunctCompiler.toCustomId([]), // get a noop if no ref
57
+ 'modal:label': getLabel(),
58
+ 'modal:description': getDescription(),
47
59
  })),
48
60
 
49
61
  as: (id: string) => {
@@ -54,10 +66,14 @@ export function textInput() {
54
66
  placeholderVal = text
55
67
  return out
56
68
  },
57
- label: (text: string) => {
69
+ withLabel: (text: string) => {
58
70
  labelVal = text
59
71
  return out
60
72
  },
73
+ withDescription: (text: string) => {
74
+ descriptionVal = text
75
+ return out
76
+ },
61
77
  current: (text: string) => {
62
78
  currentVal = text
63
79
  return out
@@ -23,6 +23,11 @@ export const ComponentType = {
23
23
  File: 13,
24
24
  Seperator: 14,
25
25
  Container: 17,
26
+ Label: 18,
27
+ FileUpload: 19,
28
+ RadioGroup: 21,
29
+ CheckboxGroup: 22,
30
+ Checkbox: 23,
26
31
 
27
32
  Modal: -1, // This is a special type that is not used in the API, but for Cordo's internal use
28
33
  } as const
@@ -60,7 +65,7 @@ export function createComponent<Type extends StringComponentType>(
60
65
  nativeType: ComponentType[type],
61
66
  visible: true,
62
67
  attributes: {},
63
- render
68
+ render,
64
69
  }
65
70
  const out = {
66
71
  [CordoComponentSymbol]: comp,
@@ -105,14 +110,15 @@ export function renderComponentList(
105
110
  list: Array<CordoComponent<StringComponentType> | CordoModifier>,
106
111
  parent: StringComponentType | null,
107
112
  hirarchy: Array<StringComponentType> = [],
108
- inheritAttributes: Record<string, any> = {}
113
+ inheritAttributes: Record<string, any> = {},
114
+ transform?: (component: CordoComponent<StringComponentType>, parsed: CordoComponentPayload<StringComponentType>) => CordoComponent<StringComponentType> | null
109
115
  ) {
110
116
  let pipeline: Array<CordoComponentPayload<StringComponentType>> = []
111
117
  /* when we have buttons without a host row we use this to collect up to 5 and then create the row for it */
112
118
  const rowBuilder: Array<CordoComponent<StringComponentType>> = []
113
119
  const modifiers: Array<ReturnType<typeof readModifier>> = []
114
120
 
115
- for (const item of list) {
121
+ for (let item of list) {
116
122
  if (!item)
117
123
  continue
118
124
 
@@ -129,6 +135,14 @@ export function renderComponentList(
129
135
  parsed = mod.hooks.onRender(parsed)
130
136
  }
131
137
 
138
+ if (transform) {
139
+ const transformed = transform(item, parsed)
140
+ if (!transformed)
141
+ continue
142
+ item = transformed
143
+ parsed = readComponent(transformed)
144
+ }
145
+
132
146
  if (parent !== 'ActionRow') {
133
147
  if (parsed.nativeName === 'Button') {
134
148
  rowBuilder.push(item)
@@ -10,13 +10,16 @@ import {
10
10
  } from './component'
11
11
 
12
12
  export { button } from './builtin/button'
13
+ export { checkboxGroup } from './builtin/checkbox-group'
13
14
  export { container } from './builtin/container'
14
15
  export { collection } from './builtin/collection'
15
16
  export { divider } from './builtin/divider'
17
+ export { fileUpload } from './builtin/file-upload'
16
18
  export { gallery } from './builtin/gallery'
17
19
  export { image } from './builtin/image'
18
20
  export { linkButton } from './builtin/link-button'
19
21
  export { modal } from './builtin/modal'
22
+ export { radioGroup } from './builtin/radio-group'
20
23
  export { row } from './builtin/row'
21
24
  export { section } from './builtin/section'
22
25
  export { selectString } from './builtin/select-string'
@@ -157,7 +157,9 @@ export namespace RoutingRespond {
157
157
  has: (key: string) => key in interaction.locals
158
158
  },
159
159
 
160
- ack: opts.disableRendering ? noOp : () => CordoGateway.respondTo(interaction, null),
160
+ ack: opts.disableRendering ? noOp : () => {
161
+ CordoGateway.respondTo(interaction, null)
162
+ },
161
163
  render: opts.disableRendering ? noOp : (...response) => {
162
164
  const rendered = renderRouteResponse(response, interaction, opts)
163
165
  CordoGateway.respondTo(interaction, rendered)