@agnocon/piece-bitly 0.1.7

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.
Files changed (52) hide show
  1. package/LICENSE.MIT-AP +24 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +44 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/actions/archive-bitlink.d.ts +11 -0
  7. package/dist/lib/actions/archive-bitlink.d.ts.map +1 -0
  8. package/dist/lib/actions/archive-bitlink.js +54 -0
  9. package/dist/lib/actions/archive-bitlink.js.map +1 -0
  10. package/dist/lib/actions/create-bitlink.d.ts +34 -0
  11. package/dist/lib/actions/create-bitlink.d.ts.map +1 -0
  12. package/dist/lib/actions/create-bitlink.js +134 -0
  13. package/dist/lib/actions/create-bitlink.js.map +1 -0
  14. package/dist/lib/actions/create-qr-code.d.ts +39 -0
  15. package/dist/lib/actions/create-qr-code.d.ts.map +1 -0
  16. package/dist/lib/actions/create-qr-code.js +383 -0
  17. package/dist/lib/actions/create-qr-code.js.map +1 -0
  18. package/dist/lib/actions/get-bitlink-details.d.ts +11 -0
  19. package/dist/lib/actions/get-bitlink-details.d.ts.map +1 -0
  20. package/dist/lib/actions/get-bitlink-details.js +50 -0
  21. package/dist/lib/actions/get-bitlink-details.js.map +1 -0
  22. package/dist/lib/actions/update-bitlink.d.ts +18 -0
  23. package/dist/lib/actions/update-bitlink.d.ts.map +1 -0
  24. package/dist/lib/actions/update-bitlink.js +135 -0
  25. package/dist/lib/actions/update-bitlink.js.map +1 -0
  26. package/dist/lib/common/auth.d.ts +4 -0
  27. package/dist/lib/common/auth.d.ts.map +1 -0
  28. package/dist/lib/common/auth.js +44 -0
  29. package/dist/lib/common/auth.js.map +1 -0
  30. package/dist/lib/common/client.d.ts +13 -0
  31. package/dist/lib/common/client.d.ts.map +1 -0
  32. package/dist/lib/common/client.js +69 -0
  33. package/dist/lib/common/client.js.map +1 -0
  34. package/dist/lib/common/props.d.ts +10 -0
  35. package/dist/lib/common/props.d.ts.map +1 -0
  36. package/dist/lib/common/props.js +118 -0
  37. package/dist/lib/common/props.js.map +1 -0
  38. package/dist/lib/triggers/new-bitlink-created.d.ts +43 -0
  39. package/dist/lib/triggers/new-bitlink-created.d.ts.map +1 -0
  40. package/dist/lib/triggers/new-bitlink-created.js +312 -0
  41. package/dist/lib/triggers/new-bitlink-created.js.map +1 -0
  42. package/package.json +46 -0
  43. package/src/index.ts +38 -0
  44. package/src/lib/actions/archive-bitlink.ts +59 -0
  45. package/src/lib/actions/create-bitlink.ts +158 -0
  46. package/src/lib/actions/create-qr-code.ts +398 -0
  47. package/src/lib/actions/get-bitlink-details.ts +54 -0
  48. package/src/lib/actions/update-bitlink.ts +153 -0
  49. package/src/lib/common/auth.ts +37 -0
  50. package/src/lib/common/client.ts +122 -0
  51. package/src/lib/common/props.ts +123 -0
  52. package/src/lib/triggers/new-bitlink-created.ts +361 -0
@@ -0,0 +1,398 @@
1
+ import { HttpMethod } from '@agnocon/pieces-common';
2
+ import {
3
+ createAction,
4
+ Property,
5
+ DynamicPropsValue,
6
+ } from '@agnocon/pieces-framework';
7
+ import { bitlyApiCall } from '../common/client';
8
+ import { bitlyAuth } from '../common/auth';
9
+ import { groupGuid } from '../common/props';
10
+
11
+ export const createQrCodeAction = createAction({
12
+ auth: bitlyAuth,
13
+ name: 'create_qr_code',
14
+ displayName: 'Create QR Code',
15
+ description: 'Generate a customized QR code for a Bitlink.',
16
+ audience: 'both',
17
+ aiMetadata: { description: 'Creates a styled QR code that points either to a raw long URL or to an existing Bitlink (selected via the destination type), with extensive optional customization of colors, dot patterns, corners, gradients, frames, branding, and error correction. Use to generate a scannable QR for a destination. Not idempotent: each call creates a new QR code.', idempotent: false },
18
+ props: {
19
+ group_guid: groupGuid,
20
+ destination_type: Property.StaticDropdown({
21
+ displayName: 'Destination Type',
22
+ required: true,
23
+ defaultValue: 'long_url',
24
+ options: {
25
+ options: [
26
+ { label: 'Long URL', value: 'long_url' },
27
+ { label: 'Existing Bitlink', value: 'bitlink_id' },
28
+ ],
29
+ },
30
+ }),
31
+ destination: Property.DynamicProperties({
32
+ auth: bitlyAuth,
33
+ displayName: 'Destination',
34
+ required: true,
35
+ refreshers: ['destination_type'],
36
+ props: async (
37
+ propsValue: Record<string, unknown>,
38
+ ) => {
39
+ const destination_type = propsValue[
40
+ 'destination_type'
41
+ ] as unknown as string;
42
+ const props: DynamicPropsValue = {};
43
+ if (destination_type === 'long_url') {
44
+ props['long_url'] = Property.ShortText({
45
+ displayName: 'Long URL',
46
+ required: true,
47
+ });
48
+ } else if (destination_type === 'bitlink_id') {
49
+ props['bitlink_id'] = Property.ShortText({
50
+ displayName: 'Bitlink (e.g., bit.ly/xyz)',
51
+ required: true,
52
+ });
53
+ }
54
+ return props;
55
+ },
56
+ }),
57
+ title: Property.ShortText({
58
+ displayName: 'Title',
59
+ description: 'Internal title for the QR Code.',
60
+ required: false,
61
+ }),
62
+ archived: Property.Checkbox({
63
+ displayName: 'Archive on Create',
64
+ description: 'Archive the QR code upon creation.',
65
+ required: false,
66
+ }),
67
+ background_color: Property.ShortText({
68
+ displayName: 'Style: Background Color',
69
+ description: 'Hex code (e.g., #FFFFFF)',
70
+ required: false,
71
+ }),
72
+ dot_pattern_color: Property.ShortText({
73
+ displayName: 'Style: Dot Pattern Color',
74
+ description: 'Hex code (e.g., #000000)',
75
+ required: false,
76
+ }),
77
+ dot_pattern_type: Property.StaticDropdown({
78
+ displayName: 'Style: Dot Pattern Type',
79
+ required: false,
80
+ options: {
81
+ options: [
82
+ { label: 'Standard', value: 'standard' },
83
+ { label: 'Circle', value: 'circle' },
84
+ { label: 'Block', value: 'block' },
85
+ { label: 'Blob', value: 'blob' },
86
+ { label: 'Rounded', value: 'rounded' },
87
+ { label: 'Vertical', value: 'vertical' },
88
+ { label: 'Horizontal', value: 'horizontal' },
89
+ { label: 'Triangle', value: 'triangle' },
90
+ { label: 'Heart', value: 'heart' },
91
+ { label: 'Star', value: 'star' },
92
+ { label: 'Diamond', value: 'diamond' },
93
+ ],
94
+ },
95
+ }),
96
+ corner_1_shape: Property.StaticDropdown({
97
+ displayName: 'Corner 1 (Top-Left): Shape',
98
+ required: false,
99
+ options: {
100
+ options: [
101
+ { label: 'Standard', value: 'standard' },
102
+ { label: 'Slightly Round', value: 'slightly_round' },
103
+ { label: 'Rounded', value: 'rounded' },
104
+ { label: 'Extra Round', value: 'extra_round' },
105
+ { label: 'Leaf', value: 'leaf' },
106
+ { label: 'Leaf Inner', value: 'leaf_inner' },
107
+ { label: 'Leaf Outer', value: 'leaf_outer' },
108
+ { label: 'Target', value: 'target' },
109
+ { label: 'Concave', value: 'concave' },
110
+ ],
111
+ },
112
+ }),
113
+ corner_1_inner_color: Property.ShortText({
114
+ displayName: 'Corner 1 (Top-Left): Inner Color',
115
+ required: false,
116
+ }),
117
+ corner_1_outer_color: Property.ShortText({
118
+ displayName: 'Corner 1 (Top-Left): Outer Color',
119
+ required: false,
120
+ }),
121
+ corner_2_shape: Property.StaticDropdown({
122
+ displayName: 'Corner 2 (Top-Right): Shape',
123
+ required: false,
124
+ options: {
125
+ options: [
126
+ { label: 'Standard', value: 'standard' },
127
+ { label: 'Slightly Round', value: 'slightly_round' },
128
+ { label: 'Rounded', value: 'rounded' },
129
+ { label: 'Extra Round', value: 'extra_round' },
130
+ { label: 'Leaf', value: 'leaf' },
131
+ { label: 'Leaf Inner', value: 'leaf_inner' },
132
+ { label: 'Leaf Outer', value: 'leaf_outer' },
133
+ { label: 'Target', value: 'target' },
134
+ { label: 'Concave', value: 'concave' },
135
+ ],
136
+ },
137
+ }),
138
+ corner_2_inner_color: Property.ShortText({
139
+ displayName: 'Corner 2 (Top-Right): Inner Color',
140
+ required: false,
141
+ }),
142
+ corner_2_outer_color: Property.ShortText({
143
+ displayName: 'Corner 2 (Top-Right): Outer Color',
144
+ required: false,
145
+ }),
146
+ corner_3_shape: Property.StaticDropdown({
147
+ displayName: 'Corner 3 (Bottom-Right): Shape',
148
+ required: false,
149
+ options: {
150
+ options: [
151
+ { label: 'Standard', value: 'standard' },
152
+ { label: 'Slightly Round', value: 'slightly_round' },
153
+ { label: 'Rounded', value: 'rounded' },
154
+ { label: 'Extra Round', value: 'extra_round' },
155
+ { label: 'Leaf', value: 'leaf' },
156
+ { label: 'Leaf Inner', value: 'leaf_inner' },
157
+ { label: 'Leaf Outer', value: 'leaf_outer' },
158
+ { label: 'Target', value: 'target' },
159
+ { label: 'Concave', value: 'concave' },
160
+ ],
161
+ },
162
+ }),
163
+ corner_3_inner_color: Property.ShortText({
164
+ displayName: 'Corner 3 (Bottom-Right): Inner Color',
165
+ required: false,
166
+ }),
167
+ corner_3_outer_color: Property.ShortText({
168
+ displayName: 'Corner 3 (Bottom-Right): Outer Color',
169
+ required: false,
170
+ }),
171
+ gradient_style: Property.StaticDropdown({
172
+ displayName: 'Gradient: Style',
173
+ required: false,
174
+ options: {
175
+ options: [
176
+ { label: 'No Gradient', value: 'no_gradient' },
177
+ { label: 'Linear', value: 'linear' },
178
+ { label: 'Radial', value: 'radial' },
179
+ ],
180
+ },
181
+ }),
182
+ gradient_color_1: Property.ShortText({
183
+ displayName: 'Gradient: Color 1',
184
+ description: 'First gradient color (hex code)',
185
+ required: false,
186
+ }),
187
+ gradient_color_2: Property.ShortText({
188
+ displayName: 'Gradient: Color 2',
189
+ description: 'Second gradient color (hex code)',
190
+ required: false,
191
+ }),
192
+ gradient_angle: Property.Number({
193
+ displayName: 'Gradient: Angle (for Linear)',
194
+ required: false,
195
+ }),
196
+ gradient_exclude_corners: Property.Checkbox({
197
+ displayName: 'Gradient: Exclude Corners',
198
+ required: false,
199
+ }),
200
+ frame_id: Property.StaticDropdown({
201
+ displayName: 'Frame: Type',
202
+ required: false,
203
+ options: {
204
+ options: [
205
+ { label: 'None', value: 'none' },
206
+ { label: 'Border Only', value: 'border_only' },
207
+ { label: 'Text Bottom', value: 'text_bottom' },
208
+ { label: 'Tooltip Bottom', value: 'tooltip_bottom' },
209
+ { label: 'Arrow', value: 'arrow' },
210
+ { label: 'Text Top', value: 'text_top' },
211
+ { label: 'Text Bottom In Frame', value: 'text_bottom_in_frame' },
212
+ { label: 'Script', value: 'script' },
213
+ { label: 'Text Top and Bottom', value: 'text_top_and_bottom' },
214
+ { label: 'URL', value: 'url' },
215
+ { label: 'Instagram', value: 'instagram' },
216
+ ],
217
+ },
218
+ }),
219
+ frame_primary_color: Property.ShortText({
220
+ displayName: 'Frame: Primary Color',
221
+ required: false,
222
+ }),
223
+ frame_secondary_color: Property.ShortText({
224
+ displayName: 'Frame: Secondary Color',
225
+ required: false,
226
+ }),
227
+ frame_background_color: Property.ShortText({
228
+ displayName: 'Frame: Background Color',
229
+ required: false,
230
+ }),
231
+ frame_text: Property.ShortText({
232
+ displayName: 'Frame: Text',
233
+ description: 'Primary text for frames that support it.',
234
+ required: false,
235
+ }),
236
+ logo_image_guid: Property.ShortText({
237
+ displayName: 'Branding: Logo Image GUID',
238
+ description: 'A GUID for a logo image previously uploaded to Bitly.',
239
+ required: false,
240
+ }),
241
+ bitly_brand: Property.Checkbox({
242
+ displayName: 'Branding: Show Bitly Logo',
243
+ description: 'Show the Bitly logo in the bottom right corner.',
244
+ required: false,
245
+ defaultValue: true,
246
+ }),
247
+ error_correction: Property.StaticDropdown({
248
+ displayName: 'Specs: Error Correction',
249
+ required: false,
250
+ options: {
251
+ options: [
252
+ { label: 'Low (1)', value: 1 },
253
+ { label: 'Medium (2)', value: 2 },
254
+ { label: 'Quartile (3)', value: 3 },
255
+ { label: 'High (4)', value: 4 },
256
+ ],
257
+ },
258
+ }),
259
+ },
260
+ async run(context) {
261
+ const props = context.propsValue;
262
+
263
+ try {
264
+ const body: any = {
265
+ group_guid: props.group_guid,
266
+ destination: { ...props.destination },
267
+ };
268
+ if (props.title) body.title = props.title;
269
+ if (props.archived) body.archived = props.archived;
270
+
271
+ const customizations: any = {};
272
+ if (props.background_color)
273
+ customizations.background_color = props.background_color;
274
+ if (props.dot_pattern_color)
275
+ customizations.dot_pattern_color = props.dot_pattern_color;
276
+ if (props.dot_pattern_type)
277
+ customizations.dot_pattern_type = props.dot_pattern_type;
278
+
279
+ const corners: any = {};
280
+ if (
281
+ props.corner_1_shape ||
282
+ props.corner_1_inner_color ||
283
+ props.corner_1_outer_color
284
+ )
285
+ corners.corner_1 = {
286
+ shape: props.corner_1_shape,
287
+ inner_color: props.corner_1_inner_color,
288
+ outer_color: props.corner_1_outer_color,
289
+ };
290
+ if (
291
+ props.corner_2_shape ||
292
+ props.corner_2_inner_color ||
293
+ props.corner_2_outer_color
294
+ )
295
+ corners.corner_2 = {
296
+ shape: props.corner_2_shape,
297
+ inner_color: props.corner_2_inner_color,
298
+ outer_color: props.corner_2_outer_color,
299
+ };
300
+ if (
301
+ props.corner_3_shape ||
302
+ props.corner_3_inner_color ||
303
+ props.corner_3_outer_color
304
+ )
305
+ corners.corner_3 = {
306
+ shape: props.corner_3_shape,
307
+ inner_color: props.corner_3_inner_color,
308
+ outer_color: props.corner_3_outer_color,
309
+ };
310
+ if (Object.keys(corners).length > 0) customizations.corners = corners;
311
+
312
+ const gradient: any = {};
313
+ if (props.gradient_style) gradient.style = props.gradient_style;
314
+ if (props.gradient_angle) gradient.angle = props.gradient_angle;
315
+ if (props.gradient_exclude_corners)
316
+ gradient.exclude_corners = props.gradient_exclude_corners;
317
+
318
+ // Build gradient colors array from individual color inputs
319
+ if (props.gradient_color_1 || props.gradient_color_2) {
320
+ const colors = [];
321
+ if (props.gradient_color_1) {
322
+ colors.push({ color: props.gradient_color_1, offset: 0 });
323
+ }
324
+ if (props.gradient_color_2) {
325
+ colors.push({ color: props.gradient_color_2, offset: 100 });
326
+ }
327
+ gradient.colors = colors;
328
+ }
329
+ if (Object.keys(gradient).length > 0) customizations.gradient = gradient;
330
+
331
+ const frame: any = {};
332
+ if (props.frame_id) frame.id = props.frame_id;
333
+ const frameColors: any = {};
334
+ if (props.frame_primary_color)
335
+ frameColors.primary = props.frame_primary_color;
336
+ if (props.frame_secondary_color)
337
+ frameColors.secondary = props.frame_secondary_color;
338
+ if (props.frame_background_color)
339
+ frameColors.background = props.frame_background_color;
340
+ if (Object.keys(frameColors).length > 0) frame.colors = frameColors;
341
+ if (props.frame_text)
342
+ frame.text = { primary: { content: props.frame_text } };
343
+ if (Object.keys(frame).length > 0) customizations.frame = frame;
344
+
345
+ const branding: any = {};
346
+ if (props.bitly_brand !== undefined)
347
+ branding.bitly_brand = props.bitly_brand;
348
+ if (Object.keys(branding).length > 0) customizations.branding = branding;
349
+
350
+ const logo: any = {};
351
+ if (props.logo_image_guid) logo.image_guid = props.logo_image_guid;
352
+ if (Object.keys(logo).length > 0) customizations.logo = logo;
353
+
354
+ const specSettings: any = {};
355
+ if (props.error_correction)
356
+ specSettings.error_correction = props.error_correction;
357
+ if (Object.keys(specSettings).length > 0)
358
+ customizations.spec_settings = specSettings;
359
+
360
+ if (Object.keys(customizations).length > 0)
361
+ body.render_customizations = customizations;
362
+
363
+ return await bitlyApiCall({
364
+ method: HttpMethod.POST,
365
+ auth: context.auth.props,
366
+ resourceUri: '/qr-codes',
367
+ body,
368
+ });
369
+ } catch (error: any) {
370
+ const errorMessage =
371
+ error.response?.data?.description ||
372
+ error.response?.data?.message ||
373
+ error.message;
374
+
375
+ if (error.response?.status === 429) {
376
+ throw new Error(
377
+ 'Rate limit exceeded. Please wait before trying again.'
378
+ );
379
+ }
380
+
381
+ if (error.response?.status === 422) {
382
+ throw new Error(
383
+ `Unprocessable Entity: ${errorMessage}. Please check the format of your Long URL or other inputs.`
384
+ );
385
+ }
386
+
387
+ if (error.response?.status === 401 || error.response?.status === 403) {
388
+ throw new Error(
389
+ `Authentication failed or forbidden: ${errorMessage}. Please check your Access Token and permissions.`
390
+ );
391
+ }
392
+
393
+ throw new Error(
394
+ `Failed to create QR Code: ${errorMessage || 'Unknown error occurred'}`
395
+ );
396
+ }
397
+ },
398
+ });
@@ -0,0 +1,54 @@
1
+ import { HttpMethod } from '@agnocon/pieces-common';
2
+ import { createAction, Property } from '@agnocon/pieces-framework';
3
+ import { bitlyApiCall } from '../common/client';
4
+ import { bitlyAuth } from '../common/auth';
5
+ import { bitlinkDropdown, groupGuid } from '../common/props';
6
+
7
+ export const getBitlinkDetailsAction = createAction({
8
+ auth: bitlyAuth,
9
+ name: 'get_bitlink_details',
10
+ displayName: 'Get Bitlink Details',
11
+ description: 'Retrieve metadata for a Bitlink.',
12
+ audience: 'both',
13
+ aiMetadata: { description: 'Retrieves metadata for a single Bitlink (title, long URL, tags, archive status, timestamps) identified by its Bitlink ID. Use to look up the current state of a known short link. Idempotent read-only lookup.', idempotent: true },
14
+ props: {
15
+ group_guid: groupGuid,
16
+ bitlink: bitlinkDropdown,
17
+ },
18
+ async run(context) {
19
+ const { bitlink } = context.propsValue;
20
+
21
+ try {
22
+ return await bitlyApiCall({
23
+ method: HttpMethod.GET,
24
+ auth: context.auth.props,
25
+ resourceUri: `/bitlinks/${bitlink}`,
26
+ });
27
+
28
+ } catch (error: any) {
29
+ const errorMessage = error.response?.data?.description || error.response?.data?.message || error.message;
30
+
31
+ if (error.response?.status === 429) {
32
+ throw new Error(
33
+ 'Rate limit exceeded. Please wait before trying again.'
34
+ );
35
+ }
36
+
37
+ if (error.response?.status === 404) {
38
+ throw new Error(
39
+ `Bitlink not found: ${errorMessage}. Please verify the link ID is correct.`
40
+ );
41
+ }
42
+
43
+ if (error.response?.status === 401 || error.response?.status === 403) {
44
+ throw new Error(
45
+ `Authentication failed or forbidden: ${errorMessage}. Please check your Access Token and permissions.`
46
+ );
47
+ }
48
+
49
+ throw new Error(
50
+ `Failed to get Bitlink details: ${errorMessage || 'Unknown error occurred'}`
51
+ );
52
+ }
53
+ },
54
+ });
@@ -0,0 +1,153 @@
1
+ import { HttpMethod } from '@agnocon/pieces-common';
2
+ import { createAction, Property } from '@agnocon/pieces-framework';
3
+ import { bitlyApiCall } from '../common/client';
4
+ import { bitlyAuth } from '../common/auth';
5
+ import { bitlinkDropdown, groupGuid } from '../common/props';
6
+
7
+ export const updateBitlinkAction = createAction({
8
+ auth: bitlyAuth,
9
+ name: 'update_bitlink',
10
+ displayName: 'Update Bitlink',
11
+ description: 'Modify properties of an existing Bitlink.',
12
+ audience: 'both',
13
+ aiMetadata: { description: 'Updates properties of an existing Bitlink identified by its Bitlink ID, including title, archive status, tags (overwrites existing), and mobile app deeplinks. Use to edit or re-tag a known short link; at least one field must be provided. Idempotent: applying the same values repeatedly yields the same final state.', idempotent: true },
14
+ props: {
15
+ group_guid: groupGuid,
16
+ bitlink: bitlinkDropdown,
17
+ title: Property.ShortText({
18
+ displayName: 'Title',
19
+ description: 'New title for the Bitlink.',
20
+ required: false,
21
+ }),
22
+ archived: Property.Checkbox({
23
+ displayName: 'Archived',
24
+ description: 'Archive or unarchive the Bitlink.',
25
+ required: false,
26
+ }),
27
+ tags: Property.Array({
28
+ displayName: 'Tags',
29
+ description: 'Tags to apply (overwrites existing tags).',
30
+ required: false,
31
+ }),
32
+ // Mobile App Deeplink Configuration
33
+ app_uri_path: Property.ShortText({
34
+ displayName: 'App URI Path',
35
+ description: 'Path within the mobile app (e.g., /product/123).',
36
+ required: false,
37
+ }),
38
+ install_url: Property.LongText({
39
+ displayName: 'App Install URL',
40
+ description: 'URL where users can install the mobile app.',
41
+ required: false,
42
+ }),
43
+ os: Property.StaticDropdown({
44
+ displayName: 'Mobile OS',
45
+ description: 'Target mobile operating system.',
46
+ required: false,
47
+ options: {
48
+ disabled: false,
49
+ options: [
50
+ { label: 'iOS', value: 'ios' },
51
+ { label: 'Android', value: 'android' },
52
+ ],
53
+ },
54
+ }),
55
+ install_type: Property.StaticDropdown({
56
+ displayName: 'Install Type',
57
+ description: 'How to handle app installation.',
58
+ required: false,
59
+ options: {
60
+ disabled: false,
61
+ options: [
62
+ { label: 'No Install', value: 'no_install' },
63
+ { label: 'Auto Install', value: 'auto_install' },
64
+ { label: 'Promote Install', value: 'promote_install' },
65
+ ],
66
+ },
67
+ }),
68
+ },
69
+ async run(context) {
70
+ const {
71
+ bitlink,
72
+ title,
73
+ archived,
74
+ tags,
75
+ app_uri_path,
76
+ install_url,
77
+ os,
78
+ install_type
79
+ } = context.propsValue;
80
+
81
+ try {
82
+ const body: Record<string, unknown> = {};
83
+
84
+ if (title !== undefined && title !== null) {
85
+ body['title'] = title;
86
+ }
87
+ if (archived !== undefined && archived !== null) {
88
+ body['archived'] = archived;
89
+ }
90
+ if (tags !== undefined && tags !== null && Array.isArray(tags)) {
91
+ body['tags'] = tags;
92
+ }
93
+
94
+ // Build deeplinks array if app configuration is provided
95
+ if (app_uri_path || install_url || os || install_type) {
96
+ const deeplink: Record<string, unknown> = {};
97
+
98
+ if (app_uri_path) deeplink['app_uri_path'] = app_uri_path;
99
+ if (install_url) deeplink['install_url'] = install_url;
100
+ if (os) deeplink['os'] = os;
101
+ if (install_type) deeplink['install_type'] = install_type;
102
+
103
+ if (Object.keys(deeplink).length > 0) {
104
+ body['deeplinks'] = [deeplink];
105
+ }
106
+ }
107
+
108
+ if (Object.keys(body).length === 0) {
109
+ throw new Error(
110
+ 'No fields were provided to update. Please provide a title, tags, archive status, or deeplinks.'
111
+ );
112
+ }
113
+
114
+ return await bitlyApiCall({
115
+ method: HttpMethod.PATCH,
116
+ auth: context.auth.props,
117
+ resourceUri: `/bitlinks/${bitlink}`,
118
+ body,
119
+ });
120
+ } catch (error: any) {
121
+ const errorMessage =
122
+ error.response?.data?.description ||
123
+ error.response?.data?.message ||
124
+ error.message;
125
+
126
+ if (error.response?.status === 429) {
127
+ throw new Error(
128
+ 'Rate limit exceeded. Please wait before trying again.'
129
+ );
130
+ }
131
+
132
+ if (error.response?.status === 404) {
133
+ throw new Error(
134
+ `Bitlink not found: ${errorMessage}. Please verify the link (e.g., 'bit.ly/xyz123') is correct.`
135
+ );
136
+ }
137
+
138
+ if (error.response?.status === 401 || error.response?.status === 403) {
139
+ throw new Error(
140
+ `Authentication failed or forbidden: ${errorMessage}. Please check your Access Token and permissions.`
141
+ );
142
+ }
143
+
144
+ if (error.message.includes('Invalid JSON format')) {
145
+ throw error;
146
+ }
147
+
148
+ throw new Error(
149
+ `Failed to update Bitlink: ${errorMessage || 'Unknown error occurred'}`
150
+ );
151
+ }
152
+ },
153
+ });
@@ -0,0 +1,37 @@
1
+ import { PieceAuth } from '@agnocon/pieces-framework';
2
+ import { bitlyApiCall } from './client';
3
+ import { HttpMethod } from '@agnocon/pieces-common';
4
+
5
+ export const bitlyAuth = PieceAuth.CustomAuth({
6
+ description: `
7
+ To get your Access Token:
8
+ 1. Log in to your Bitly account.
9
+ 2. Click your profile icon in the top right corner.
10
+ 3. Go to **Profile Settings**.
11
+ 4. Navigate to the **Developer settings** section.
12
+ 5. Click on **API**.
13
+ 6. Click the **Generate token** button and enter your password to get your access token.
14
+ `,
15
+ props: {
16
+ accessToken: PieceAuth.SecretText({
17
+ displayName: 'Access Token',
18
+ required: true,
19
+ }),
20
+ },
21
+ validate: async ({ auth }) => {
22
+ try {
23
+ await bitlyApiCall({
24
+ method: HttpMethod.GET,
25
+ auth,
26
+ resourceUri: '/user',
27
+ });
28
+ return { valid: true };
29
+ } catch (e) {
30
+ return {
31
+ valid: false,
32
+ error: 'Invalid Access Token',
33
+ };
34
+ }
35
+ },
36
+ required: true,
37
+ });