Compare commits

...

205 Commits

Author SHA1 Message Date
Bentlybro
7cfc0a0c4f update blocks.md 2025-01-13 11:05:15 +00:00
Bently
c869fd0119 Update block docs for: slant3d/slicing.md 2025-01-13 10:24:59 +00:00
Bently
097348e2ba Update block docs for: slant3d/order.md 2025-01-13 10:24:57 +00:00
Bently
2f97724ba5 Update block docs for: slant3d/filament.md 2025-01-13 10:24:54 +00:00
Bently
805a988e21 Update block docs for: slant3d/base.md 2025-01-13 10:24:51 +00:00
Bently
6166e31942 Update block docs for: nvidia/deepfake.md 2025-01-13 10:24:48 +00:00
Bently
fa56b68071 Update block docs for: jina/search.md 2025-01-13 10:24:46 +00:00
Bently
4207dc2bcf Update block docs for: jina/fact_checker.md 2025-01-13 10:24:44 +00:00
Bently
73d032a937 Update block docs for: jina/embeddings.md 2025-01-13 10:24:42 +00:00
Bently
2011322511 Update block docs for: jina/chunking.md 2025-01-13 10:24:40 +00:00
Bently
09e1a4081f Update block docs for: hubspot/engagement.md 2025-01-13 10:24:39 +00:00
Bently
3831aa99e7 Update block docs for: hubspot/contact.md 2025-01-13 10:24:36 +00:00
Bently
f15633833c Update block docs for: hubspot/company.md 2025-01-13 10:24:29 +00:00
Bently
2352c50433 Update block docs for: helpers/http.md 2025-01-13 10:24:27 +00:00
Bently
00add5738f Update block docs for: google/sheets.md 2025-01-13 10:24:24 +00:00
Bently
00b163b0a5 Update block docs for: google/gmail.md 2025-01-13 10:24:23 +00:00
Bently
3c23fd5b1a Update block docs for: github/triggers.md 2025-01-13 10:24:21 +00:00
Bently
7c719d6835 Update block docs for: github/repo.md 2025-01-13 10:24:20 +00:00
Bently
0b0c810861 Update block docs for: github/pull_requests.md 2025-01-13 10:24:18 +00:00
Bently
1c1dda57e8 Update block docs for: github/issues.md 2025-01-13 10:24:17 +00:00
Bently
20e93f32ff Update block docs for: fal/ai_video_generator.md 2025-01-13 10:24:10 +00:00
Bently
8fc00dfe02 Update block docs for: exa/similar.md 2025-01-13 10:24:02 +00:00
Bently
185d103371 Update block docs for: exa/search.md 2025-01-13 10:23:59 +00:00
Bently
dfc024acc3 Update block docs for: exa/contents.md 2025-01-13 10:23:58 +00:00
Bently
791ab5d671 Update block docs for: compass/triggers.md 2025-01-13 10:23:57 +00:00
Bently
6dc88e3f21 Update block docs for: youtube.md 2025-01-13 10:23:55 +00:00
Bently
d3f5718cac Update block docs for: time_blocks.md 2025-01-13 10:23:51 +00:00
Bently
aef7b5db7d Update block docs for: text.md 2025-01-13 10:23:50 +00:00
Bently
df071ca5a5 Update block docs for: talking_head.md 2025-01-13 10:23:49 +00:00
Bently
2b192a0d20 Update block docs for: search.md 2025-01-13 10:23:48 +00:00
Bently
9572415b74 Update block docs for: sampling.md 2025-01-13 10:23:46 +00:00
Bently
ad1bf2f27f Update block docs for: rss.md 2025-01-13 10:23:45 +00:00
Bently
67991f7c6d Update block docs for: replicate_flux_advanced.md 2025-01-13 10:23:44 +00:00
Bently
c2aad7d2d9 Update block docs for: reddit.md 2025-01-13 10:23:43 +00:00
Bently
504a0a1250 Update block docs for: pinecone.md 2025-01-13 10:23:42 +00:00
Bently
69ae276bb8 Update block docs for: medium.md 2025-01-13 10:23:40 +00:00
Bently
66f2e2a77b Update block docs for: maths.md 2025-01-13 10:23:39 +00:00
Bently
c463d1022b Update block docs for: llm.md 2025-01-13 10:23:38 +00:00
Bently
88dc0bbe0b Update block docs for: iteration.md 2025-01-13 10:23:37 +00:00
Bently
5ea176e457 Update block docs for: ideogram.md 2025-01-13 10:23:35 +00:00
Bently
134163aa88 Update block docs for: http.md 2025-01-13 10:23:34 +00:00
Bently
ff494bee93 Update block docs for: google_maps.md 2025-01-13 10:23:33 +00:00
Bently
f02e2fd8bb Update block docs for: discord.md 2025-01-13 10:23:32 +00:00
Bently
2012213af5 Update block docs for: csv.md 2025-01-13 10:23:31 +00:00
Bently
94f4702f6b Update block docs for: code_executor.md 2025-01-13 10:23:29 +00:00
Bently
abf05d6407 Update block docs for: branching.md 2025-01-13 10:23:28 +00:00
Bently
58a8b0ddeb Update block docs for: basic.md 2025-01-13 10:23:27 +00:00
Bently
6a4a8f5a46 Update block docs for: ai_music_generator.md 2025-01-13 10:23:26 +00:00
Bentlybro
240ad756aa rm files 2025-01-13 10:10:59 +00:00
Bently
6c62a6a558 Update block docs for: slant3d/base.md 2025-01-13 10:06:26 +00:00
Bently
d36ac1471c Update block docs for: nvidia/deepfake.md 2025-01-13 10:06:25 +00:00
Bently
a2b34739c9 Update block docs for: jina/search.md 2025-01-13 10:06:24 +00:00
Bently
7d4775e3b7 Update block docs for: jina/fact_checker.md 2025-01-13 10:06:23 +00:00
Bently
d133b474a8 Update block docs for: jina/embeddings.md 2025-01-13 10:06:21 +00:00
Bently
14ef675784 Update block docs for: jina/chunking.md 2025-01-13 10:06:19 +00:00
Bently
2c9baa6966 Update block docs for: hubspot/engagement.md 2025-01-13 10:06:18 +00:00
Bently
43ca817a85 Update block docs for: hubspot/contact.md 2025-01-13 10:06:17 +00:00
Bently
5cf97dd296 Update block docs for: hubspot/company.md 2025-01-13 10:06:15 +00:00
Bently
c577758b4a Update block docs for: helpers/http.md 2025-01-13 10:06:14 +00:00
Bently
ed9d0b85b4 Update block docs for: google/sheets.md 2025-01-13 10:06:13 +00:00
Bently
cfb92dd4f9 Update block docs for: google/gmail.md 2025-01-13 10:06:12 +00:00
Bently
b892c2b272 Update block docs for: github/triggers.md 2025-01-13 10:06:10 +00:00
Bently
d4679bbae8 Update block docs for: github/repo.md 2025-01-13 10:06:09 +00:00
Bently
34954d8df3 Update block docs for: github/pull_requests.md 2025-01-13 10:06:08 +00:00
Bently
780c893c91 Update block docs for: github/issues.md 2025-01-13 10:06:07 +00:00
Bently
a113fdb134 Update block docs for: fal/ai_video_generator.md 2025-01-13 10:06:05 +00:00
Bently
b73cb9fc98 Update block docs for: exa/similar.md 2025-01-13 10:06:04 +00:00
Bently
223e3de073 Update block docs for: exa/search.md 2025-01-13 10:06:03 +00:00
Bently
abfbdd0934 Update block docs for: exa/helpers.md 2025-01-13 10:06:01 +00:00
Bently
d8e38b505c Update block docs for: exa/contents.md 2025-01-13 10:06:00 +00:00
Bently
4ebc34da62 Update block docs for: compass/triggers.md 2025-01-13 10:05:59 +00:00
Bently
056c539bde Update block docs for: youtube.md 2025-01-13 10:05:57 +00:00
Bently
7e2e8843c0 Update block docs for: time_blocks.md 2025-01-13 10:05:55 +00:00
Bently
a8cde7c3c5 Update block docs for: text_to_speech_block.md 2025-01-13 10:05:53 +00:00
Bently
64e59c5324 Update block docs for: text.md 2025-01-13 10:05:52 +00:00
Bently
0da1461a79 Update block docs for: talking_head.md 2025-01-13 10:05:51 +00:00
Bently
192ff65bbf Update block docs for: search.md 2025-01-13 10:05:50 +00:00
Bently
388003ff2d Update block docs for: sampling.md 2025-01-13 10:05:49 +00:00
Bently
1081b15c91 Update block docs for: rss.md 2025-01-13 10:05:48 +00:00
Bently
0e17f5757b Update block docs for: replicate_flux_advanced.md 2025-01-13 10:05:47 +00:00
Bently
80de45c610 Update block docs for: reddit.md 2025-01-13 10:05:45 +00:00
Bently
bd237c5b52 Update block docs for: pinecone.md 2025-01-13 10:05:44 +00:00
Bently
6f8f7ac716 Update block docs for: medium.md 2025-01-13 10:05:43 +00:00
Bently
cca71f99b9 Update block docs for: maths.md 2025-01-13 10:05:42 +00:00
Bently
e54a999ff4 Update block docs for: llm.md 2025-01-13 10:05:40 +00:00
Bently
36360b3ade Update block docs for: iteration.md 2025-01-13 10:05:39 +00:00
Bently
a1607a3b21 Update block docs for: ideogram.md 2025-01-13 10:05:38 +00:00
Bently
8804417c72 Update block docs for: http.md 2025-01-13 10:05:37 +00:00
Bently
6aeec36a3c Update block docs for: google_maps.md 2025-01-13 10:05:36 +00:00
Bently
6d09a46652 Update block docs for: email_block.md 2025-01-13 10:05:34 +00:00
Bently
be4b2b1ba1 Update block docs for: discord.md 2025-01-13 10:05:33 +00:00
Bently
d2d6346f59 Update block docs for: decoder_block.md 2025-01-13 10:05:32 +00:00
Bently
d83984bf38 Update block docs for: csv.md 2025-01-13 10:05:31 +00:00
Bently
146bf8e692 Update block docs for: count_words_and_char_block.md 2025-01-13 10:05:29 +00:00
Bently
015e7d2b10 Update block docs for: code_extraction_block.md 2025-01-13 10:05:28 +00:00
Bently
dac7e4aa57 Update block docs for: code_executor.md 2025-01-13 10:05:27 +00:00
Bently
1764cf9837 Update block docs for: branching.md 2025-01-13 10:05:26 +00:00
Bently
fbd1e26524 Update block docs for: 45e78db5-03e9-447f-9395-308d712f5f08.md 2025-01-13 10:05:25 +00:00
Bently
fb10bacfda Update block docs for: basic.md 2025-01-13 10:05:23 +00:00
Bently
f6a12828f0 Update block docs for: ai_shortform_video_block.md 2025-01-13 10:05:22 +00:00
Bently
fba186e5e1 Update block docs for: ai_music_generator.md 2025-01-13 10:05:21 +00:00
Bently
91b88b840e Update block docs for: ai_image_generator_block.md 2025-01-13 10:05:19 +00:00
Bentlybro
f6b00f07ce rm 2025-01-12 12:42:45 +00:00
Bently
a4bd7c9b58 Update block docs for: google_maps.md 2025-01-12 12:41:05 +00:00
Bently
bc643492e8 Update block docs for: email_block.md 2025-01-12 12:41:04 +00:00
Bently
ba64e05803 Update block docs for: discord.md 2025-01-12 12:41:03 +00:00
Bently
01c0284fd2 Update block docs for: text_decoder.md 2025-01-12 12:41:02 +00:00
Bently
a08a7bd1e1 Update block docs for: read_csv.md 2025-01-12 12:41:00 +00:00
Bently
25bab0eaa5 Update block docs for: word_character_count_block.md 2025-01-12 12:40:59 +00:00
Bently
bb646e865e Update block docs for: code_extraction_block.md 2025-01-12 12:40:57 +00:00
Bently
dc777ce89a Update block docs for: code_executor.md 2025-01-12 12:40:56 +00:00
Bently
a3955385ec Update block docs for: condition_block.md 2025-01-12 12:40:55 +00:00
Bently
88b03563f5 Update block docs for: block_installation.md 2025-01-12 12:40:54 +00:00
Bently
f57f6fc1c0 Update block docs for: basic.md 2025-01-12 12:40:53 +00:00
Bently
2f7ad767c2 Update block docs for: ai_shortform_video_block.md 2025-01-12 12:40:52 +00:00
Bently
e3e4bc8c96 Update block docs for: ai_music_generator.md 2025-01-12 12:40:51 +00:00
Bently
d3cb3c73d1 Update block docs for: ai_image_generator_block.md 2025-01-12 12:40:50 +00:00
Bently
cc75eea402 Update block docs for: agent.md 2025-01-12 12:40:49 +00:00
Bentlybro
40961cb9f1 rm 2025-01-12 12:23:46 +00:00
Bently
13be1a03b7 Update block docs for: youtube.md 2025-01-12 11:53:52 +00:00
Bently
757381c889 Update block docs for: time_blocks.md 2025-01-12 11:53:50 +00:00
Bently
154f0a2b83 Update block docs for: unreal_text_to_speech.md 2025-01-12 11:53:49 +00:00
Bently
bf91cc507f Update block docs for: text.md 2025-01-12 11:53:48 +00:00
Bently
940fef027a Update block docs for: talking_avatar_video.md 2025-01-12 11:53:47 +00:00
Bently
d1b51ad09b Update block docs for: search.md 2025-01-12 11:53:46 +00:00
Bently
4cb0bbe67e Update block docs for: sampling.md 2025-01-12 11:53:45 +00:00
Bently
ebbb7b07cf Update block docs for: rss.md 2025-01-12 11:53:44 +00:00
Bently
db3d86bce6 Update block docs for: replicate_flux_advanced.md 2025-01-12 11:53:43 +00:00
Bently
f1f6f87b25 Update block docs for: reddit.md 2025-01-12 11:53:42 +00:00
Bently
0d5ab20f14 Update block docs for: pinecone.md 2025-01-12 11:53:41 +00:00
Bently
4017c5be70 Update block docs for: medium.md 2025-01-12 11:53:40 +00:00
Bently
b7cc83854a Update block docs for: maths.md 2025-01-12 11:53:39 +00:00
Bently
0599083e0e Update block docs for: llm.md 2025-01-12 11:53:38 +00:00
Bently
9e36144b40 Update block docs for: step_through_items.md 2025-01-12 11:53:36 +00:00
Bently
ce3c86cb1d Update block docs for: ideogram.md 2025-01-12 11:53:35 +00:00
Bently
e33263c9fc Update block docs for: http.md 2025-01-12 11:53:34 +00:00
Bently
04f0f64fbb Update block docs for: google_maps.md 2025-01-12 11:53:33 +00:00
Bently
755a7b620d Update block docs for: email_block.md 2025-01-12 11:53:32 +00:00
Bently
d54c9d4e7e Update block docs for: discord.md 2025-01-12 11:53:31 +00:00
Bently
852af17294 Update block docs for: text_decoder.md 2025-01-12 11:53:29 +00:00
Bently
8889d029d4 Update block docs for: read_csv.md 2025-01-12 11:53:28 +00:00
Bently
e93c7dc89e Update block docs for: word_character_count_block.md 2025-01-12 11:53:27 +00:00
Bently
7791281b90 Update block docs for: code_extraction_block.md 2025-01-12 11:53:26 +00:00
Bently
cc9ff9e2bb Update block docs for: code_executor.md 2025-01-12 11:53:25 +00:00
Bently
6b82b9e73b Update block docs for: condition_block.md 2025-01-12 11:53:24 +00:00
Bently
61bfbeeb01 Update block docs for: block_installation.md 2025-01-12 11:53:23 +00:00
Bently
9518ff4f02 Update block docs for: basic.md 2025-01-12 11:53:21 +00:00
Bently
49490e899e Update block docs for: ai_shortform_video_block.md 2025-01-12 11:53:20 +00:00
Bently
d19866bdf5 Update block docs for: ai_music_generator.md 2025-01-12 11:53:19 +00:00
Bently
b6c946ac4f Update block docs for: ai_image_generator_block.md 2025-01-12 11:53:18 +00:00
Bently
d6f5dcd717 Update block docs for: agent.md 2025-01-12 11:53:17 +00:00
Bently
99779f52b2 Update block docs for:
blocks_init.md
2025-01-12 11:53:15 +00:00
Bently
d4e5a48163 Update block docs for: code_execution_block.md 2025-01-12 11:07:29 +00:00
Bentlybro
a40bb6f6d6 rm file 2025-01-12 11:07:08 +00:00
Bently
a393f8bf9f Update block docs for: code_executor.md 2025-01-12 11:05:08 +00:00
Bentlybro
d56931f4cb rm files 2025-01-10 13:30:37 +00:00
Bently
03691329be Update block docs for: text_to_speech_block.md 2025-01-10 12:55:02 +00:00
Bently
9813012c12 Update block docs for: text.md 2025-01-10 12:55:01 +00:00
Bently
8f74e58ecc Update block docs for: talking_head.md 2025-01-10 12:55:00 +00:00
Bently
b1f5413dab Update block docs for: search.md 2025-01-10 12:54:59 +00:00
Bently
cc41b2f4ab Update block docs for: sampling.md 2025-01-10 12:54:58 +00:00
Bently
f45b4cc243 Update block docs for: rss.md 2025-01-10 12:54:56 +00:00
Bently
ed2e5e813d Update block docs for: replicate_flux_advanced.md 2025-01-10 12:54:55 +00:00
Bently
ab33d079e2 Update block docs for: reddit.md 2025-01-10 12:54:54 +00:00
Bently
a24c869a44 Update block docs for: pinecone.md 2025-01-10 12:54:52 +00:00
Bently
d6b1cf64ed Update block docs for: medium.md 2025-01-10 12:54:51 +00:00
Bently
e9982ba9bd Update block docs for: maths.md 2025-01-10 12:54:50 +00:00
Bently
58c1e050f2 Update block docs for: llm.md 2025-01-10 12:54:49 +00:00
Bently
f125bb658c Update block docs for: iteration.md 2025-01-10 12:54:48 +00:00
Bently
ba9c91d0b7 Update block docs for: ideogram.md 2025-01-10 12:54:46 +00:00
Bently
e6254f0e83 Update block docs for: http.md 2025-01-10 12:54:45 +00:00
Bently
3c8cf8bd1e Update block docs for: google_maps.md 2025-01-10 12:54:44 +00:00
Bently
79f3888d61 Update block docs for: email_block.md 2025-01-10 12:54:43 +00:00
Bently
ace5d34cc6 Update block docs for: discord.md 2025-01-10 12:54:42 +00:00
Bently
100ab90f44 Update block docs for: decoder_block.md 2025-01-10 12:54:40 +00:00
Bently
a0ad796432 Update block docs for: csv.md 2025-01-10 12:54:39 +00:00
Bently
0cc625ca15 Update block docs for: count_words_and_char_block.md 2025-01-10 12:54:38 +00:00
Bently
a43b329132 Update block docs for: code_extraction_block.md 2025-01-10 12:54:37 +00:00
Bently
beeadd16f1 Update block docs for: code_executor.md 2025-01-10 12:54:36 +00:00
Bently
b2d5b9efb4 Update block docs for: branching.md 2025-01-10 12:54:35 +00:00
Bently
c77f32b23f Update block docs for: block.md 2025-01-10 12:54:33 +00:00
Bently
b92223cf7b Update block docs for: basic.md 2025-01-10 12:54:32 +00:00
Bently
0d47b0ce38 Update block docs for: ai_shortform_video_block.md 2025-01-10 12:54:31 +00:00
Bently
c0409ba0b1 Update block docs for: ai_music_generator.md 2025-01-10 12:54:29 +00:00
Bently
41c8504bdc Update block docs for: ai_image_generator_block.md 2025-01-10 12:54:28 +00:00
Bently
c4d38e4ff3 Update block docs for: agent.md 2025-01-10 12:54:27 +00:00
Bently
b10a275676 Update block docs for: __init__.md 2025-01-10 12:54:26 +00:00
Bentlybro
8baabb0379 remove files 2025-01-10 12:47:54 +00:00
Bently
8bf977958a Update block docs for: pinecone.md 2025-01-10 11:10:33 +00:00
Bently
c73da1f79c Update block docs for: count_words_and_char_block.md 2025-01-10 11:10:29 +00:00
Bently
3c3c1ce90a Update block docs for: code_extraction_block.md 2025-01-10 11:10:27 +00:00
Bently
e9a198f5da Update block docs for: code_executor.md 2025-01-10 11:10:26 +00:00
Bently
706bf0578e Update block docs for: block.md 2025-01-10 11:10:25 +00:00
Bently
610b613367 Update block docs for: ai_music_generator.md 2025-01-10 11:10:22 +00:00
Bently
5f4a411b15 Update block docs for: ai_image_generator_block.md 2025-01-10 11:10:21 +00:00
Bently
7b004f07e7 Update block docs for: agent.md 2025-01-10 11:10:20 +00:00
Bently
c16d2f94a6 Update block docs for: __init__.md 2025-01-10 11:10:19 +00:00
Zamil Majdy
9d1bc25ffa hotfix(backend): Increase statement timeout for the double brace migration (#9245)
### Changes 🏗️


https://github.com/Significant-Gravitas/AutoGPT/actions/runs/12696734339/job/35391431786

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [ ] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my
changes
- [ ] I have included a list of my configuration changes in the PR
description (under **Changes**)

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
2025-01-09 21:59:22 +00:00
Zamil Majdy
3a3ee994c2 hotfix(backend): Increase statement timeout for the double brace migration (#9244)
### Changes 🏗️


https://github.com/Significant-Gravitas/AutoGPT/actions/runs/12696734339/job/35391431786

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [ ] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my
changes
- [ ] I have included a list of my configuration changes in the PR
description (under **Changes**)

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
2025-01-09 19:44:37 +00:00
Aarushi
0d44f5be13 feat(backend/blocks/nvidia): Provide Nvidia by default (#9235)
We want to allow users to use Nvidia without their own keys

### Changes 🏗️

Added nvidia api key to credentials store.

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [x] `.env.example` is updated or already compatible with my changes

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
2025-01-09 18:12:05 +00:00
Zamil Majdy
1670579a61 fix(block): Remove Python.format & Jinja templating format backward compatibility (#9229)
Python format uses `{Variable}` as the variable placeholder, while Jinja
uses `{{Variable}}` as its default.
Jinja is used as the main templating engine on the system, but the
Python format version is still maintained for backward compatibility.

However, the backward compatibility support can cause a side effect
while passing JSON string value into the block that uses it:
https://github.com/Significant-Gravitas/AutoGPT/issues/9194

### Changes 🏗️

* Use `{{Variable}}` place holder format and removed `{Variable}`
support in these blocks:
 - '363ae599-353e-4804-937e-b2ee3cef3da4', -- AgentOutputBlock
 - 'db7d8f02-2f44-4c55-ab7a-eae0941f0c30', -- FillTextTemplateBlock
 - '1f292d4a-41a4-4977-9684-7c8d560b9f91', -- AITextGeneratorBlock
- 'ed55ac19-356e-4243-a6cb-bc599e9b716f' --
AIStructuredResponseGeneratorBlock
* Add Jinja templating support on `AITextGeneratorBlock` &
`AIStructuredResponseGeneratorBlock`
* Migrated the existing database content to prevent breaking changes.

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [ ] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my
changes
- [ ] I have included a list of my configuration changes in the PR
description (under **Changes**)

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
2025-01-09 16:29:16 +00:00
Bently
a1889e6212 docs(Ollama): Update Ollama docs (#9234)
The Ollama docs where very out of date and needed updating so I have
updated them and added some screenshots so its easier to follow.

I have also added a new Ollama model to the platform, "llama3.2" as that
is what i based the tutorial off and its name is easy to find in the
list of models

I also added a new folder in the "imgs" dir to store the Ollama related
photo just to keep things tidy
2025-01-09 15:19:37 +00:00
Abhimanyu Yadav
9c702516fd feat(platform): fix carousel on store page (#9230)
- resolves #8973 

Adding smooth scrolling and solving some weird interaction on carousal

### Changes

- Update `CarouselPrevious`, `CarouselPrevious` and add
`CarouselIndicator` in `carousel.tsx`
- Add `CarouselPrevious`, `CarouselPrevious` and `CarouselIndicator`
support in `FeaturedSection.tsx`

### Demo 


https://github.com/user-attachments/assets/ba9a22fa-ddf2-469f-ba8a-aee1a7fc5f78
2025-01-09 13:48:53 +00:00
Aarushi
32c908ae13 fix(backend): Add default credentials for Fal, Exa, E2B (#9233)
We want to provide certain providers by default on our platform. These
three were not added previously, so fixing that.

### Changes 🏗️

If api keys for Fal Exa or E2B exist in environment variables, load them
by default as credentials that are usable by our users.

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [x] `.env.example` is updated or already compatible with my changes

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
2025-01-09 13:47:54 +00:00
Abhimanyu Yadav
b4a0100c22 feat(platform): Add Twitter integration (#8754)
- Resolves #8326  

Create a Twitter integration with some small frontend changes.

### Changes
1. Add Twitter OAuth 2.0 with PKCE support for authentication.
2. Add a way to multi-select from a list of enums by creating a
multi-select on the frontend.
3. Add blocks for Twitter integration.
4. `_types.py` for repetitive enums and input types.
5. `_builders.py` for creating parameters without repeating the same
logic.
6. `_serializer.py` to serialize the Tweepy enums into dictionaries so
they can travel easily from Pyro5.
7. `_mappers.py` to map the frontend values to the correct request
values.

> I have added a new multi-select feature because my list contains many
items, and selecting all of them makes the block cluttered. This new
block displays only the first two items and then show something like "2
more" . It works only for list of enums.


### Blocks

Block Name | What It Does | Error Reason | Manual Testing
-- | -- | -- | --
`TwitterBookmarkTweetBlock` | Bookmark a tweet on Twitter | No error | 
`TwitterGetBookmarkedTweetsBlock` | Get all your bookmarked tweets from
Twitter | No error | 
`TwitterRemoveBookmarkTweetBlock` | Remove a bookmark for a tweet on
Twitter | No error | 
`TwitterHideReplyBlock` | Hides a reply of one of your tweets | No error
| 
`TwitterUnhideReplyBlock` | Unhides a reply to a tweet | No error | 
`TwitterLikeTweetBlock` | Likes a tweet | No error | 
`TwitterGetLikingUsersBlock` | Gets information about users who liked
one of your tweets | No error | 
`TwitterGetLikedTweetsBlock` | Gets information about tweets liked by
you | No error | 
`TwitterUnlikeTweetBlock` | Unlikes a tweet that was previously liked |
No error | 
`TwitterPostTweetBlock` | Create a tweet on Twitter with the option to
include one additional element such as media, quote, or deep link. | No
error | 
`TwitterDeleteTweetBlock` | Deletes a tweet on Twitter using Twitter ID
| No error | 
`TwitterSearchRecentTweetsBlock` | Searches all public Tweets in Twitter
history | No error | 
`TwitterGetQuoteTweetsBlock` | Gets quote tweets for a specified tweet
ID | No error | 
`TwitterRetweetBlock` | Retweets a tweet on Twitter | No error | 
`TwitterRemoveRetweetBlock` | Removes a retweet on Twitter | No error |

`TwitterGetRetweetersBlock` | Gets information about who has retweeted a
tweet | No error | 
`TwitterGetUserMentionsBlock` | Returns Tweets where a single user is
mentioned, just put that user ID | No error | 
`TwitterGetHomeTimelineBlock` | Returns a collection of the most recent
Tweets and Retweets posted by you and users you follow | No error | 
`TwitterGetUserTweetsBlock` | Returns Tweets composed by a single user,
specified by the requested user ID | No error | 
`TwitterGetTweetBlock` | Returns information about a single Tweet
specified by the requested ID | No error | 
`TwitterGetTweetsBlock` | Returns information about multiple Tweets
specified by the requested IDs | No error | 
`TwitterUnblockUserBlock` | Unblock a specific user on Twitter | No
error | 
`TwitterGetBlockedUsersBlock` | Get a list of users who are blocked by
the authenticating user | No error | 
`TwitterBlockUserBlock` | Block a specific user on Twitter | No error |

`TwitterUnfollowUserBlock` | Allows a user to unfollow another user
specified by target user ID | No error | 
`TwitterFollowUserBlock` | Allows a user to follow another user
specified by target user ID | No error | 
`TwitterGetFollowersBlock` | Retrieves a list of followers for a
specified Twitter user ID | Need Enterprise level access | 
`TwitterGetFollowingBlock` | Retrieves a list of users that a specified
Twitter user ID is following | Need Enterprise level access | 
`TwitterUnmuteUserBlock` | Allows a user to unmute another user
specified by target user ID | No error | 
`TwitterGetMutedUsersBlock` | Returns a list of users who are muted by
the authenticating user | No error | 
`TwitterMuteUserBlock` | Allows a user to mute another user specified by
target user ID | No error | 
`TwitterGetUserBlock` | Gets information about a single Twitter user
specified by ID or username | No error | 
`TwitterGetUsersBlock` | Gets information about multiple Twitter users
specified by IDs or usernames | No error | 
`TwitterSearchSpacesBlock` | Returns live or scheduled Spaces matching
specified search terms [for a week only] | No error | 
`TwitterGetSpacesBlock` | Gets information about multiple Twitter Spaces
specified by Space IDs or creator user IDs | No error | 
`TwitterGetSpaceByIdBlock` | Gets information about a single Twitter
Space specified by Space ID | No error | 
`TwitterGetSpaceBuyersBlock` | Gets list of users who purchased a ticket
to the requested Space | I do not have a monetized account for this | 
`TwitterGetSpaceTweetsBlock` | Gets list of Tweets shared in the
requested Space | No error | 
`TwitterUnfollowListBlock` | Unfollows a Twitter list for the
authenticated user | No error | 
`TwitterFollowListBlock` | Follows a Twitter list for the authenticated
user | No error | 
`TwitterListGetFollowersBlock` | Gets followers of a specified Twitter
list | Enterprise level access | 
`TwitterGetFollowedListsBlock` | Gets lists followed by a specified
Twitter user | Enterprise level access | 
`TwitterGetListBlock` | Gets information about a Twitter List specified
by ID | No error | 
`TwitterGetOwnedListsBlock` | Gets all Lists owned by the specified user
| No error | 
`TwitterRemoveListMemberBlock` | Removes a member from a Twitter List
that the authenticated user owns | No error | 
`TwitterAddListMemberBlock` | Adds a member to a Twitter List that the
authenticated user owns | No error | 
`TwitterGetListMembersBlock` | Gets the members of a specified Twitter
List | No error | 
`TwitterGetListMembershipsBlock` | Gets all Lists that a specified user
is a member of | No error | 
`TwitterGetListTweetsBlock` | Gets tweets from a specified Twitter list
| No error | 
`TwitterDeleteListBlock` | Deletes a Twitter List owned by the
authenticated user | No error | 
`TwitterUpdateListBlock` | Updates a Twitter List owned by the
authenticated user | No error | 
`TwitterCreateListBlock` | Creates a Twitter List owned by the
authenticated user | No error | 
`TwitterUnpinListBlock` | Enables the authenticated user to unpin a
List. | No error | 
`TwitterPinListBlock` | Enables the authenticated user to pin a List. |
No error | 
`TwitterGetPinnedListsBlock` | Returns the Lists pinned by the
authenticated user. | No error | 
`TwitterGetDMEventsBlock` | Gets a list of Direct Message events for the
authenticated user | Need Enterprise level access | 
`TwitterSendDirectMessageBlock` | Sends a direct message to a Twitter
user | Need Enterprise level access | 
`TwitterCreateDMConversationBlock` | Creates a new group direct message
| Need Enterprise level access | 

### Need to add more stuff
1. A normal input to select date and time.
2. Some more enterprise-level blocks, especially webhook triggers.

Supported triggers 


Event Name | Description
-- | --
Posts (by user) | User creates a new post.
Post deletes (by user) | User deletes an existing post.
@mentions (of user) | User is mentioned in a post.
Replies (to or from user) | User replies to a post or receives a reply
from another user.
Retweets (by user or of user) | User retweets a post or someone retweets
the user's post.
Quote Tweets (by user or of user) | User quote tweets a post or someone
quote tweets the user's post.
Retweets of Quoted Tweets (by user or of user) | Retweets of quote
tweets by the user or of the user.
Likes (by user or of user) | User likes a post or someone likes the
user's post.
Follows (by user or of user) | User follows another user or another user
follows the user.
Unfollows (by user) | User unfollows another user.
Blocks (by user) | User blocks another user.
Unblocks (by user) | User unblocks a previously blocked user.
Mutes (by user) | User mutes another user.
Unmutes (by user) | User unmutes a previously muted user.
Direct Messages sent (by user) | User sends direct messages to other
users.
Direct Messages received (by user) | User receives direct messages from
other users.
Typing indicators (to user) | Indicators showing when someone is typing
a message to the user.
Read receipts (to user) | Indicators showing when the user has read a
message.
Subscription revokes (by user) | User revokes a subscription to a
service or content.

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
Co-authored-by: Nicholas Tindle <nicktindle@outlook.com>
2025-01-08 19:47:00 +00:00
116 changed files with 16048 additions and 1017 deletions

View File

@@ -0,0 +1,76 @@
from typing import Annotated, Any, Literal, Optional, TypedDict
from uuid import uuid4
from pydantic import BaseModel, Field, SecretStr, field_serializer
class _BaseCredentials(BaseModel):
id: str = Field(default_factory=lambda: str(uuid4()))
provider: str
title: Optional[str]
@field_serializer("*")
def dump_secret_strings(value: Any, _info):
if isinstance(value, SecretStr):
return value.get_secret_value()
return value
class OAuth2Credentials(_BaseCredentials):
type: Literal["oauth2"] = "oauth2"
username: Optional[str]
"""Username of the third-party service user that these credentials belong to"""
access_token: SecretStr
access_token_expires_at: Optional[int]
"""Unix timestamp (seconds) indicating when the access token expires (if at all)"""
refresh_token: Optional[SecretStr]
refresh_token_expires_at: Optional[int]
"""Unix timestamp (seconds) indicating when the refresh token expires (if at all)"""
scopes: list[str]
metadata: dict[str, Any] = Field(default_factory=dict)
def bearer(self) -> str:
return f"Bearer {self.access_token.get_secret_value()}"
class APIKeyCredentials(_BaseCredentials):
type: Literal["api_key"] = "api_key"
api_key: SecretStr
expires_at: Optional[int]
"""Unix timestamp (seconds) indicating when the API key expires (if at all)"""
def bearer(self) -> str:
return f"Bearer {self.api_key.get_secret_value()}"
Credentials = Annotated[
OAuth2Credentials | APIKeyCredentials,
Field(discriminator="type"),
]
CredentialsType = Literal["api_key", "oauth2"]
class OAuthState(BaseModel):
token: str
provider: str
expires_at: int
code_verifier: Optional[str] = None
scopes: list[str]
"""Unix timestamp (seconds) indicating when this OAuth state expires"""
class UserMetadata(BaseModel):
integration_credentials: list[Credentials] = Field(default_factory=list)
integration_oauth_states: list[OAuthState] = Field(default_factory=list)
class UserMetadataRaw(TypedDict, total=False):
integration_credentials: list[dict]
integration_oauth_states: list[dict]
class UserIntegrations(BaseModel):
credentials: list[Credentials] = Field(default_factory=list)
oauth_states: list[OAuthState] = Field(default_factory=list)

View File

@@ -58,6 +58,21 @@ GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Twitter (X) OAuth 2.0 with PKCE Configuration
# 1. Create a Twitter Developer Account:
# - Visit https://developer.x.com/en and sign up
# 2. Set up your application:
# - Navigate to Developer Portal > Projects > Create Project
# - Add a new app to your project
# 3. Configure app settings:
# - App Permissions: Read + Write + Direct Messages
# - App Type: Web App, Automated App or Bot
# - OAuth 2.0 Callback URL: http://localhost:3000/auth/integrations/oauth_callback
# - Save your Client ID and Client Secret below
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
## ===== OPTIONAL API KEYS ===== ##
# LLM
@@ -106,6 +121,18 @@ REPLICATE_API_KEY=
# Ideogram
IDEOGRAM_API_KEY=
# Fal
FAL_API_KEY=
# Exa
EXA_API_KEY=
# E2B
E2B_API_KEY=
# Nvidia
NVIDIA_API_KEY=
# Logging Configuration
LOG_LEVEL=INFO
ENABLE_CLOUD_LOGGING=false

View File

@@ -241,7 +241,7 @@ class AgentOutputBlock(Block):
advanced=True,
)
format: str = SchemaField(
description="The format string to be used to format the recorded_value.",
description="The format string to be used to format the recorded_value. Use Jinja2 syntax.",
default="",
advanced=True,
)

View File

@@ -26,8 +26,10 @@ from backend.data.model import (
)
from backend.util import json
from backend.util.settings import BehaveAs, Settings
from backend.util.text import TextFormatter
logger = logging.getLogger(__name__)
fmt = TextFormatter()
LLMProviderName = Literal[
ProviderName.ANTHROPIC,
@@ -109,6 +111,7 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
LLAMA3_1_70B = "llama-3.1-70b-versatile"
LLAMA3_1_8B = "llama-3.1-8b-instant"
# Ollama models
OLLAMA_LLAMA3_2 = "llama3.2"
OLLAMA_LLAMA3_8B = "llama3"
OLLAMA_LLAMA3_405B = "llama3.1:405b"
OLLAMA_DOLPHIN = "dolphin-mistral:latest"
@@ -163,6 +166,7 @@ MODEL_METADATA = {
# Limited to 16k during preview
LlmModel.LLAMA3_1_70B: ModelMetadata("groq", 131072),
LlmModel.LLAMA3_1_8B: ModelMetadata("groq", 131072),
LlmModel.OLLAMA_LLAMA3_2: ModelMetadata("ollama", 8192),
LlmModel.OLLAMA_LLAMA3_8B: ModelMetadata("ollama", 8192),
LlmModel.OLLAMA_LLAMA3_405B: ModelMetadata("ollama", 8192),
LlmModel.OLLAMA_DOLPHIN: ModelMetadata("ollama", 32768),
@@ -234,7 +238,9 @@ class AIStructuredResponseGeneratorBlock(Block):
description="Number of times to retry the LLM call if the response does not match the expected format.",
)
prompt_values: dict[str, str] = SchemaField(
advanced=False, default={}, description="Values used to fill in the prompt."
advanced=False,
default={},
description="Values used to fill in the prompt. The values can be used in the prompt by putting them in a double curly braces, e.g. {{variable_name}}.",
)
max_tokens: int | None = SchemaField(
advanced=True,
@@ -448,8 +454,8 @@ class AIStructuredResponseGeneratorBlock(Block):
values = input_data.prompt_values
if values:
input_data.prompt = input_data.prompt.format(**values)
input_data.sys_prompt = input_data.sys_prompt.format(**values)
input_data.prompt = fmt.format_string(input_data.prompt, values)
input_data.sys_prompt = fmt.format_string(input_data.sys_prompt, values)
if input_data.sys_prompt:
prompt.append({"role": "system", "content": input_data.sys_prompt})
@@ -576,7 +582,9 @@ class AITextGeneratorBlock(Block):
description="Number of times to retry the LLM call if the response does not match the expected format.",
)
prompt_values: dict[str, str] = SchemaField(
advanced=False, default={}, description="Values used to fill in the prompt."
advanced=False,
default={},
description="Values used to fill in the prompt. The values can be used in the prompt by putting them in a double curly braces, e.g. {{variable_name}}.",
)
ollama_host: str = SchemaField(
advanced=True,

View File

@@ -141,10 +141,10 @@ class ExtractTextInformationBlock(Block):
class FillTextTemplateBlock(Block):
class Input(BlockSchema):
values: dict[str, Any] = SchemaField(
description="Values (dict) to be used in format"
description="Values (dict) to be used in format. These values can be used by putting them in double curly braces in the format template. e.g. {{value_name}}.",
)
format: str = SchemaField(
description="Template to format the text using `values`"
description="Template to format the text using `values`. Use Jinja2 syntax."
)
class Output(BlockSchema):
@@ -160,7 +160,7 @@ class FillTextTemplateBlock(Block):
test_input=[
{
"values": {"name": "Alice", "hello": "Hello", "world": "World!"},
"format": "{hello}, {world} {{name}}",
"format": "{{hello}}, {{ world }} {{name}}",
},
{
"values": {"list": ["Hello", " World!"]},

View File

@@ -0,0 +1,60 @@
from typing import Literal
from pydantic import SecretStr
from backend.data.model import (
CredentialsField,
CredentialsMetaInput,
OAuth2Credentials,
ProviderName,
)
from backend.integrations.oauth.twitter import TwitterOAuthHandler
from backend.util.settings import Secrets
# --8<-- [start:TwitterOAuthIsConfigured]
secrets = Secrets()
TWITTER_OAUTH_IS_CONFIGURED = bool(
secrets.twitter_client_id and secrets.twitter_client_secret
)
# --8<-- [end:TwitterOAuthIsConfigured]
TwitterCredentials = OAuth2Credentials
TwitterCredentialsInput = CredentialsMetaInput[
Literal[ProviderName.TWITTER], Literal["oauth2"]
]
# Currently, We are getting all the permission from the Twitter API initally
# In future, If we need to add incremental permission, we can use these requested_scopes
def TwitterCredentialsField(scopes: list[str]) -> TwitterCredentialsInput:
"""
Creates a Twitter credentials input on a block.
Params:
scopes: The authorization scopes needed for the block to work.
"""
return CredentialsField(
# required_scopes=set(scopes),
required_scopes=set(TwitterOAuthHandler.DEFAULT_SCOPES + scopes),
description="The Twitter integration requires OAuth2 authentication.",
)
TEST_CREDENTIALS = OAuth2Credentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="twitter",
access_token=SecretStr("mock-twitter-access-token"),
refresh_token=SecretStr("mock-twitter-refresh-token"),
access_token_expires_at=1234567890,
scopes=["tweet.read", "tweet.write", "users.read", "offline.access"],
title="Mock Twitter OAuth2 Credentials",
username="mock-twitter-username",
refresh_token_expires_at=1234567890,
)
TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.title,
}

View File

@@ -0,0 +1,418 @@
from datetime import datetime
from typing import Any, Dict
from backend.blocks.twitter._mappers import (
get_backend_expansion,
get_backend_field,
get_backend_list_expansion,
get_backend_list_field,
get_backend_media_field,
get_backend_place_field,
get_backend_poll_field,
get_backend_space_expansion,
get_backend_space_field,
get_backend_user_field,
)
from backend.blocks.twitter._types import ( # DMEventFieldFilter,
DMEventExpansionFilter,
DMEventTypeFilter,
DMMediaFieldFilter,
DMTweetFieldFilter,
ExpansionFilter,
ListExpansionsFilter,
ListFieldsFilter,
SpaceExpansionsFilter,
SpaceFieldsFilter,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetReplySettingsFilter,
TweetUserFieldsFilter,
UserExpansionsFilter,
)
# Common Builder
class TweetExpansionsBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_expansions(self, expansions: ExpansionFilter | None):
if expansions:
filtered_expansions = [
name for name, value in expansions.dict().items() if value is True
]
if filtered_expansions:
self.params["expansions"] = ",".join(
[get_backend_expansion(exp) for exp in filtered_expansions]
)
return self
def add_media_fields(self, media_fields: TweetMediaFieldsFilter | None):
if media_fields:
filtered_fields = [
name for name, value in media_fields.dict().items() if value is True
]
if filtered_fields:
self.params["media.fields"] = ",".join(
[get_backend_media_field(field) for field in filtered_fields]
)
return self
def add_place_fields(self, place_fields: TweetPlaceFieldsFilter | None):
if place_fields:
filtered_fields = [
name for name, value in place_fields.dict().items() if value is True
]
if filtered_fields:
self.params["place.fields"] = ",".join(
[get_backend_place_field(field) for field in filtered_fields]
)
return self
def add_poll_fields(self, poll_fields: TweetPollFieldsFilter | None):
if poll_fields:
filtered_fields = [
name for name, value in poll_fields.dict().items() if value is True
]
if filtered_fields:
self.params["poll.fields"] = ",".join(
[get_backend_poll_field(field) for field in filtered_fields]
)
return self
def add_tweet_fields(self, tweet_fields: TweetFieldsFilter | None):
if tweet_fields:
filtered_fields = [
name for name, value in tweet_fields.dict().items() if value is True
]
if filtered_fields:
self.params["tweet.fields"] = ",".join(
[get_backend_field(field) for field in filtered_fields]
)
return self
def add_user_fields(self, user_fields: TweetUserFieldsFilter | None):
if user_fields:
filtered_fields = [
name for name, value in user_fields.dict().items() if value is True
]
if filtered_fields:
self.params["user.fields"] = ",".join(
[get_backend_user_field(field) for field in filtered_fields]
)
return self
def build(self):
return self.params
class UserExpansionsBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_expansions(self, expansions: UserExpansionsFilter | None):
if expansions:
filtered_expansions = [
name for name, value in expansions.dict().items() if value is True
]
if filtered_expansions:
self.params["expansions"] = ",".join(filtered_expansions)
return self
def add_tweet_fields(self, tweet_fields: TweetFieldsFilter | None):
if tweet_fields:
filtered_fields = [
name for name, value in tweet_fields.dict().items() if value is True
]
if filtered_fields:
self.params["tweet.fields"] = ",".join(
[get_backend_field(field) for field in filtered_fields]
)
return self
def add_user_fields(self, user_fields: TweetUserFieldsFilter | None):
if user_fields:
filtered_fields = [
name for name, value in user_fields.dict().items() if value is True
]
if filtered_fields:
self.params["user.fields"] = ",".join(
[get_backend_user_field(field) for field in filtered_fields]
)
return self
def build(self):
return self.params
class ListExpansionsBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_expansions(self, expansions: ListExpansionsFilter | None):
if expansions:
filtered_expansions = [
name for name, value in expansions.dict().items() if value is True
]
if filtered_expansions:
self.params["expansions"] = ",".join(
[get_backend_list_expansion(exp) for exp in filtered_expansions]
)
return self
def add_list_fields(self, list_fields: ListFieldsFilter | None):
if list_fields:
filtered_fields = [
name for name, value in list_fields.dict().items() if value is True
]
if filtered_fields:
self.params["list.fields"] = ",".join(
[get_backend_list_field(field) for field in filtered_fields]
)
return self
def add_user_fields(self, user_fields: TweetUserFieldsFilter | None):
if user_fields:
filtered_fields = [
name for name, value in user_fields.dict().items() if value is True
]
if filtered_fields:
self.params["user.fields"] = ",".join(
[get_backend_user_field(field) for field in filtered_fields]
)
return self
def build(self):
return self.params
class SpaceExpansionsBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_expansions(self, expansions: SpaceExpansionsFilter | None):
if expansions:
filtered_expansions = [
name for name, value in expansions.dict().items() if value is True
]
if filtered_expansions:
self.params["expansions"] = ",".join(
[get_backend_space_expansion(exp) for exp in filtered_expansions]
)
return self
def add_space_fields(self, space_fields: SpaceFieldsFilter | None):
if space_fields:
filtered_fields = [
name for name, value in space_fields.dict().items() if value is True
]
if filtered_fields:
self.params["space.fields"] = ",".join(
[get_backend_space_field(field) for field in filtered_fields]
)
return self
def add_user_fields(self, user_fields: TweetUserFieldsFilter | None):
if user_fields:
filtered_fields = [
name for name, value in user_fields.dict().items() if value is True
]
if filtered_fields:
self.params["user.fields"] = ",".join(
[get_backend_user_field(field) for field in filtered_fields]
)
return self
def build(self):
return self.params
class TweetDurationBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_start_time(self, start_time: datetime | None):
if start_time:
self.params["start_time"] = start_time
return self
def add_end_time(self, end_time: datetime | None):
if end_time:
self.params["end_time"] = end_time
return self
def add_since_id(self, since_id: str | None):
if since_id:
self.params["since_id"] = since_id
return self
def add_until_id(self, until_id: str | None):
if until_id:
self.params["until_id"] = until_id
return self
def add_sort_order(self, sort_order: str | None):
if sort_order:
self.params["sort_order"] = sort_order
return self
def build(self):
return self.params
class DMExpansionsBuilder:
def __init__(self, param: Dict[str, Any]):
self.params: Dict[str, Any] = param
def add_expansions(self, expansions: DMEventExpansionFilter):
if expansions:
filtered_expansions = [
name for name, value in expansions.dict().items() if value is True
]
if filtered_expansions:
self.params["expansions"] = ",".join(filtered_expansions)
return self
def add_event_types(self, event_types: DMEventTypeFilter):
if event_types:
filtered_types = [
name for name, value in event_types.dict().items() if value is True
]
if filtered_types:
self.params["event_types"] = ",".join(filtered_types)
return self
def add_media_fields(self, media_fields: DMMediaFieldFilter):
if media_fields:
filtered_fields = [
name for name, value in media_fields.dict().items() if value is True
]
if filtered_fields:
self.params["media.fields"] = ",".join(filtered_fields)
return self
def add_tweet_fields(self, tweet_fields: DMTweetFieldFilter):
if tweet_fields:
filtered_fields = [
name for name, value in tweet_fields.dict().items() if value is True
]
if filtered_fields:
self.params["tweet.fields"] = ",".join(filtered_fields)
return self
def add_user_fields(self, user_fields: TweetUserFieldsFilter):
if user_fields:
filtered_fields = [
name for name, value in user_fields.dict().items() if value is True
]
if filtered_fields:
self.params["user.fields"] = ",".join(filtered_fields)
return self
def build(self):
return self.params
# Specific Builders
class TweetSearchBuilder:
def __init__(self):
self.params: Dict[str, Any] = {"user_auth": False}
def add_query(self, query: str):
if query:
self.params["query"] = query
return self
def add_pagination(self, max_results: int, pagination: str | None):
if max_results:
self.params["max_results"] = max_results
if pagination:
self.params["pagination_token"] = pagination
return self
def build(self):
return self.params
class TweetPostBuilder:
def __init__(self):
self.params: Dict[str, Any] = {"user_auth": False}
def add_text(self, text: str | None):
if text:
self.params["text"] = text
return self
def add_media(self, media_ids: list, tagged_user_ids: list):
if media_ids:
self.params["media_ids"] = media_ids
if tagged_user_ids:
self.params["media_tagged_user_ids"] = tagged_user_ids
return self
def add_deep_link(self, link: str):
if link:
self.params["direct_message_deep_link"] = link
return self
def add_super_followers(self, for_super_followers: bool):
if for_super_followers:
self.params["for_super_followers_only"] = for_super_followers
return self
def add_place(self, place_id: str):
if place_id:
self.params["place_id"] = place_id
return self
def add_poll_options(self, poll_options: list):
if poll_options:
self.params["poll_options"] = poll_options
return self
def add_poll_duration(self, poll_duration_minutes: int):
if poll_duration_minutes:
self.params["poll_duration_minutes"] = poll_duration_minutes
return self
def add_quote(self, quote_id: str):
if quote_id:
self.params["quote_tweet_id"] = quote_id
return self
def add_reply_settings(
self,
exclude_user_ids: list,
reply_to_id: str,
settings: TweetReplySettingsFilter,
):
if exclude_user_ids:
self.params["exclude_reply_user_ids"] = exclude_user_ids
if reply_to_id:
self.params["in_reply_to_tweet_id"] = reply_to_id
if settings.All_Users:
self.params["reply_settings"] = None
elif settings.Following_Users_Only:
self.params["reply_settings"] = "following"
elif settings.Mentioned_Users_Only:
self.params["reply_settings"] = "mentionedUsers"
return self
def build(self):
return self.params
class TweetGetsBuilder:
def __init__(self):
self.params: Dict[str, Any] = {"user_auth": False}
def add_id(self, tweet_id: list[str]):
self.params["id"] = tweet_id
return self
def build(self):
return self.params

View File

@@ -0,0 +1,234 @@
# -------------- Tweets -----------------
# Tweet Expansions
EXPANSION_FRONTEND_TO_BACKEND_MAPPING = {
"Poll_IDs": "attachments.poll_ids",
"Media_Keys": "attachments.media_keys",
"Author_User_ID": "author_id",
"Edit_History_Tweet_IDs": "edit_history_tweet_ids",
"Mentioned_Usernames": "entities.mentions.username",
"Place_ID": "geo.place_id",
"Reply_To_User_ID": "in_reply_to_user_id",
"Referenced_Tweet_ID": "referenced_tweets.id",
"Referenced_Tweet_Author_ID": "referenced_tweets.id.author_id",
}
def get_backend_expansion(frontend_key: str) -> str:
result = EXPANSION_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid expansion key: {frontend_key}")
return result
# TweetReplySettings
REPLY_SETTINGS_FRONTEND_TO_BACKEND_MAPPING = {
"Mentioned_Users_Only": "mentionedUsers",
"Following_Users_Only": "following",
"All_Users": "all",
}
# TweetUserFields
def get_backend_reply_setting(frontend_key: str) -> str:
result = REPLY_SETTINGS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid reply setting key: {frontend_key}")
return result
USER_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Account_Creation_Date": "created_at",
"User_Bio": "description",
"User_Entities": "entities",
"User_ID": "id",
"User_Location": "location",
"Latest_Tweet_ID": "most_recent_tweet_id",
"Display_Name": "name",
"Pinned_Tweet_ID": "pinned_tweet_id",
"Profile_Picture_URL": "profile_image_url",
"Is_Protected_Account": "protected",
"Account_Statistics": "public_metrics",
"Profile_URL": "url",
"Username": "username",
"Is_Verified": "verified",
"Verification_Type": "verified_type",
"Content_Withholding_Info": "withheld",
}
def get_backend_user_field(frontend_key: str) -> str:
result = USER_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid user field key: {frontend_key}")
return result
# TweetFields
FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Tweet_Attachments": "attachments",
"Author_ID": "author_id",
"Context_Annotations": "context_annotations",
"Conversation_ID": "conversation_id",
"Creation_Time": "created_at",
"Edit_Controls": "edit_controls",
"Tweet_Entities": "entities",
"Geographic_Location": "geo",
"Tweet_ID": "id",
"Reply_To_User_ID": "in_reply_to_user_id",
"Language": "lang",
"Public_Metrics": "public_metrics",
"Sensitive_Content_Flag": "possibly_sensitive",
"Referenced_Tweets": "referenced_tweets",
"Reply_Settings": "reply_settings",
"Tweet_Source": "source",
"Tweet_Text": "text",
"Withheld_Content": "withheld",
}
def get_backend_field(frontend_key: str) -> str:
result = FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid field key: {frontend_key}")
return result
# TweetPollFields
POLL_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Duration_Minutes": "duration_minutes",
"End_DateTime": "end_datetime",
"Poll_ID": "id",
"Poll_Options": "options",
"Voting_Status": "voting_status",
}
def get_backend_poll_field(frontend_key: str) -> str:
result = POLL_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid poll field key: {frontend_key}")
return result
PLACE_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Contained_Within_Places": "contained_within",
"Country": "country",
"Country_Code": "country_code",
"Full_Location_Name": "full_name",
"Geographic_Coordinates": "geo",
"Place_ID": "id",
"Place_Name": "name",
"Place_Type": "place_type",
}
def get_backend_place_field(frontend_key: str) -> str:
result = PLACE_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid place field key: {frontend_key}")
return result
# TweetMediaFields
MEDIA_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Duration_in_Milliseconds": "duration_ms",
"Height": "height",
"Media_Key": "media_key",
"Preview_Image_URL": "preview_image_url",
"Media_Type": "type",
"Media_URL": "url",
"Width": "width",
"Public_Metrics": "public_metrics",
"Non_Public_Metrics": "non_public_metrics",
"Organic_Metrics": "organic_metrics",
"Promoted_Metrics": "promoted_metrics",
"Alternative_Text": "alt_text",
"Media_Variants": "variants",
}
def get_backend_media_field(frontend_key: str) -> str:
result = MEDIA_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid media field key: {frontend_key}")
return result
# -------------- Spaces -----------------
# SpaceExpansions
EXPANSION_FRONTEND_TO_BACKEND_MAPPING_SPACE = {
"Invited_Users": "invited_user_ids",
"Speakers": "speaker_ids",
"Creator": "creator_id",
"Hosts": "host_ids",
"Topics": "topic_ids",
}
def get_backend_space_expansion(frontend_key: str) -> str:
result = EXPANSION_FRONTEND_TO_BACKEND_MAPPING_SPACE.get(frontend_key)
if result is None:
raise KeyError(f"Invalid expansion key: {frontend_key}")
return result
# SpaceFields
SPACE_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"Space_ID": "id",
"Space_State": "state",
"Creation_Time": "created_at",
"End_Time": "ended_at",
"Host_User_IDs": "host_ids",
"Language": "lang",
"Is_Ticketed": "is_ticketed",
"Invited_User_IDs": "invited_user_ids",
"Participant_Count": "participant_count",
"Subscriber_Count": "subscriber_count",
"Scheduled_Start_Time": "scheduled_start",
"Speaker_User_IDs": "speaker_ids",
"Start_Time": "started_at",
"Space_Title": "title",
"Topic_IDs": "topic_ids",
"Last_Updated_Time": "updated_at",
}
def get_backend_space_field(frontend_key: str) -> str:
result = SPACE_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid space field key: {frontend_key}")
return result
# -------------- List Expansions -----------------
# ListExpansions
LIST_EXPANSION_FRONTEND_TO_BACKEND_MAPPING = {"List_Owner_ID": "owner_id"}
def get_backend_list_expansion(frontend_key: str) -> str:
result = LIST_EXPANSION_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid list expansion key: {frontend_key}")
return result
LIST_FIELDS_FRONTEND_TO_BACKEND_MAPPING = {
"List_ID": "id",
"List_Name": "name",
"Creation_Date": "created_at",
"Description": "description",
"Follower_Count": "follower_count",
"Member_Count": "member_count",
"Is_Private": "private",
"Owner_ID": "owner_id",
}
def get_backend_list_field(frontend_key: str) -> str:
result = LIST_FIELDS_FRONTEND_TO_BACKEND_MAPPING.get(frontend_key)
if result is None:
raise KeyError(f"Invalid list field key: {frontend_key}")
return result

View File

@@ -0,0 +1,76 @@
from typing import Any, Dict, List
class BaseSerializer:
@staticmethod
def _serialize_value(value: Any) -> Any:
"""Helper method to serialize individual values"""
if hasattr(value, "data"):
return value.data
return value
class IncludesSerializer(BaseSerializer):
@classmethod
def serialize(cls, includes: Dict[str, Any]) -> Dict[str, Any]:
"""Serializes the includes dictionary"""
if not includes:
return {}
serialized_includes = {}
for key, value in includes.items():
if isinstance(value, list):
serialized_includes[key] = [
cls._serialize_value(item) for item in value
]
else:
serialized_includes[key] = cls._serialize_value(value)
return serialized_includes
class ResponseDataSerializer(BaseSerializer):
@classmethod
def serialize_dict(cls, item: Dict[str, Any]) -> Dict[str, Any]:
"""Serializes a single dictionary item"""
serialized_item = {}
if hasattr(item, "__dict__"):
items = item.__dict__.items()
else:
items = item.items()
for key, value in items:
if isinstance(value, list):
serialized_item[key] = [
cls._serialize_value(sub_item) for sub_item in value
]
else:
serialized_item[key] = cls._serialize_value(value)
return serialized_item
@classmethod
def serialize_list(cls, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Serializes a list of dictionary items"""
return [cls.serialize_dict(item) for item in data]
class ResponseSerializer:
@classmethod
def serialize(cls, response) -> Dict[str, Any]:
"""Main serializer that handles both data and includes"""
result = {"data": None, "included": {}}
# Handle response.data
if response.data:
if isinstance(response.data, list):
result["data"] = ResponseDataSerializer.serialize_list(response.data)
else:
result["data"] = ResponseDataSerializer.serialize_dict(response.data)
# Handle includes
if hasattr(response, "includes") and response.includes:
result["included"] = IncludesSerializer.serialize(response.includes)
return result

View File

@@ -0,0 +1,443 @@
from datetime import datetime
from enum import Enum
from pydantic import BaseModel
from backend.data.block import BlockSchema
from backend.data.model import SchemaField
# -------------- Tweets -----------------
class TweetReplySettingsFilter(BaseModel):
Mentioned_Users_Only: bool = False
Following_Users_Only: bool = False
All_Users: bool = False
class TweetUserFieldsFilter(BaseModel):
Account_Creation_Date: bool = False
User_Bio: bool = False
User_Entities: bool = False
User_ID: bool = False
User_Location: bool = False
Latest_Tweet_ID: bool = False
Display_Name: bool = False
Pinned_Tweet_ID: bool = False
Profile_Picture_URL: bool = False
Is_Protected_Account: bool = False
Account_Statistics: bool = False
Profile_URL: bool = False
Username: bool = False
Is_Verified: bool = False
Verification_Type: bool = False
Content_Withholding_Info: bool = False
class TweetFieldsFilter(BaseModel):
Tweet_Attachments: bool = False
Author_ID: bool = False
Context_Annotations: bool = False
Conversation_ID: bool = False
Creation_Time: bool = False
Edit_Controls: bool = False
Tweet_Entities: bool = False
Geographic_Location: bool = False
Tweet_ID: bool = False
Reply_To_User_ID: bool = False
Language: bool = False
Public_Metrics: bool = False
Sensitive_Content_Flag: bool = False
Referenced_Tweets: bool = False
Reply_Settings: bool = False
Tweet_Source: bool = False
Tweet_Text: bool = False
Withheld_Content: bool = False
class PersonalTweetFieldsFilter(BaseModel):
attachments: bool = False
author_id: bool = False
context_annotations: bool = False
conversation_id: bool = False
created_at: bool = False
edit_controls: bool = False
entities: bool = False
geo: bool = False
id: bool = False
in_reply_to_user_id: bool = False
lang: bool = False
non_public_metrics: bool = False
public_metrics: bool = False
organic_metrics: bool = False
promoted_metrics: bool = False
possibly_sensitive: bool = False
referenced_tweets: bool = False
reply_settings: bool = False
source: bool = False
text: bool = False
withheld: bool = False
class TweetPollFieldsFilter(BaseModel):
Duration_Minutes: bool = False
End_DateTime: bool = False
Poll_ID: bool = False
Poll_Options: bool = False
Voting_Status: bool = False
class TweetPlaceFieldsFilter(BaseModel):
Contained_Within_Places: bool = False
Country: bool = False
Country_Code: bool = False
Full_Location_Name: bool = False
Geographic_Coordinates: bool = False
Place_ID: bool = False
Place_Name: bool = False
Place_Type: bool = False
class TweetMediaFieldsFilter(BaseModel):
Duration_in_Milliseconds: bool = False
Height: bool = False
Media_Key: bool = False
Preview_Image_URL: bool = False
Media_Type: bool = False
Media_URL: bool = False
Width: bool = False
Public_Metrics: bool = False
Non_Public_Metrics: bool = False
Organic_Metrics: bool = False
Promoted_Metrics: bool = False
Alternative_Text: bool = False
Media_Variants: bool = False
class ExpansionFilter(BaseModel):
Poll_IDs: bool = False
Media_Keys: bool = False
Author_User_ID: bool = False
Edit_History_Tweet_IDs: bool = False
Mentioned_Usernames: bool = False
Place_ID: bool = False
Reply_To_User_ID: bool = False
Referenced_Tweet_ID: bool = False
Referenced_Tweet_Author_ID: bool = False
class TweetExcludesFilter(BaseModel):
retweets: bool = False
replies: bool = False
# -------------- Users -----------------
class UserExpansionsFilter(BaseModel):
pinned_tweet_id: bool = False
# -------------- DM's' -----------------
class DMEventFieldFilter(BaseModel):
id: bool = False
text: bool = False
event_type: bool = False
created_at: bool = False
dm_conversation_id: bool = False
sender_id: bool = False
participant_ids: bool = False
referenced_tweets: bool = False
attachments: bool = False
class DMEventTypeFilter(BaseModel):
MessageCreate: bool = False
ParticipantsJoin: bool = False
ParticipantsLeave: bool = False
class DMEventExpansionFilter(BaseModel):
attachments_media_keys: bool = False
referenced_tweets_id: bool = False
sender_id: bool = False
participant_ids: bool = False
class DMMediaFieldFilter(BaseModel):
duration_ms: bool = False
height: bool = False
media_key: bool = False
preview_image_url: bool = False
type: bool = False
url: bool = False
width: bool = False
public_metrics: bool = False
alt_text: bool = False
variants: bool = False
class DMTweetFieldFilter(BaseModel):
attachments: bool = False
author_id: bool = False
context_annotations: bool = False
conversation_id: bool = False
created_at: bool = False
edit_controls: bool = False
entities: bool = False
geo: bool = False
id: bool = False
in_reply_to_user_id: bool = False
lang: bool = False
public_metrics: bool = False
possibly_sensitive: bool = False
referenced_tweets: bool = False
reply_settings: bool = False
source: bool = False
text: bool = False
withheld: bool = False
# -------------- Spaces -----------------
class SpaceExpansionsFilter(BaseModel):
Invited_Users: bool = False
Speakers: bool = False
Creator: bool = False
Hosts: bool = False
Topics: bool = False
class SpaceFieldsFilter(BaseModel):
Space_ID: bool = False
Space_State: bool = False
Creation_Time: bool = False
End_Time: bool = False
Host_User_IDs: bool = False
Language: bool = False
Is_Ticketed: bool = False
Invited_User_IDs: bool = False
Participant_Count: bool = False
Subscriber_Count: bool = False
Scheduled_Start_Time: bool = False
Speaker_User_IDs: bool = False
Start_Time: bool = False
Space_Title: bool = False
Topic_IDs: bool = False
Last_Updated_Time: bool = False
class SpaceStatesFilter(str, Enum):
live = "live"
scheduled = "scheduled"
all = "all"
# -------------- List Expansions -----------------
class ListExpansionsFilter(BaseModel):
List_Owner_ID: bool = False
class ListFieldsFilter(BaseModel):
List_ID: bool = False
List_Name: bool = False
Creation_Date: bool = False
Description: bool = False
Follower_Count: bool = False
Member_Count: bool = False
Is_Private: bool = False
Owner_ID: bool = False
# --------- [Input Types] -------------
class TweetExpansionInputs(BlockSchema):
expansions: ExpansionFilter | None = SchemaField(
description="Choose what extra information you want to get with your tweets. For example:\n- Select 'Media_Keys' to get media details\n- Select 'Author_User_ID' to get user information\n- Select 'Place_ID' to get location details",
placeholder="Pick the extra information you want to see",
default=None,
advanced=True,
)
media_fields: TweetMediaFieldsFilter | None = SchemaField(
description="Select what media information you want to see (images, videos, etc). To use this, you must first select 'Media_Keys' in the expansions above.",
placeholder="Choose what media details you want to see",
default=None,
advanced=True,
)
place_fields: TweetPlaceFieldsFilter | None = SchemaField(
description="Select what location information you want to see (country, coordinates, etc). To use this, you must first select 'Place_ID' in the expansions above.",
placeholder="Choose what location details you want to see",
default=None,
advanced=True,
)
poll_fields: TweetPollFieldsFilter | None = SchemaField(
description="Select what poll information you want to see (options, voting status, etc). To use this, you must first select 'Poll_IDs' in the expansions above.",
placeholder="Choose what poll details you want to see",
default=None,
advanced=True,
)
tweet_fields: TweetFieldsFilter | None = SchemaField(
description="Select what tweet information you want to see. For referenced tweets (like retweets), select 'Referenced_Tweet_ID' in the expansions above.",
placeholder="Choose what tweet details you want to see",
default=None,
advanced=True,
)
user_fields: TweetUserFieldsFilter | None = SchemaField(
description="Select what user information you want to see. To use this, you must first select one of these in expansions above:\n- 'Author_User_ID' for tweet authors\n- 'Mentioned_Usernames' for mentioned users\n- 'Reply_To_User_ID' for users being replied to\n- 'Referenced_Tweet_Author_ID' for authors of referenced tweets",
placeholder="Choose what user details you want to see",
default=None,
advanced=True,
)
class DMEventExpansionInputs(BlockSchema):
expansions: DMEventExpansionFilter | None = SchemaField(
description="Select expansions to include related data objects in the 'includes' section.",
placeholder="Enter expansions",
default=None,
advanced=True,
)
event_types: DMEventTypeFilter | None = SchemaField(
description="Select DM event types to include in the response.",
placeholder="Enter event types",
default=None,
advanced=True,
)
media_fields: DMMediaFieldFilter | None = SchemaField(
description="Select media fields to include in the response (requires expansions=attachments.media_keys).",
placeholder="Enter media fields",
default=None,
advanced=True,
)
tweet_fields: DMTweetFieldFilter | None = SchemaField(
description="Select tweet fields to include in the response (requires expansions=referenced_tweets.id).",
placeholder="Enter tweet fields",
default=None,
advanced=True,
)
user_fields: TweetUserFieldsFilter | None = SchemaField(
description="Select user fields to include in the response (requires expansions=sender_id or participant_ids).",
placeholder="Enter user fields",
default=None,
advanced=True,
)
class UserExpansionInputs(BlockSchema):
expansions: UserExpansionsFilter | None = SchemaField(
description="Choose what extra information you want to get with user data. Currently only 'pinned_tweet_id' is available to see a user's pinned tweet.",
placeholder="Select extra user information to include",
default=None,
advanced=True,
)
tweet_fields: TweetFieldsFilter | None = SchemaField(
description="Select what tweet information you want to see in pinned tweets. This only works if you select 'pinned_tweet_id' in expansions above.",
placeholder="Choose what details to see in pinned tweets",
default=None,
advanced=True,
)
user_fields: TweetUserFieldsFilter | None = SchemaField(
description="Select what user information you want to see, like username, bio, profile picture, etc.",
placeholder="Choose what user details you want to see",
default=None,
advanced=True,
)
class SpaceExpansionInputs(BlockSchema):
expansions: SpaceExpansionsFilter | None = SchemaField(
description="Choose additional information you want to get with your Twitter Spaces:\n- Select 'Invited_Users' to see who was invited\n- Select 'Speakers' to see who can speak\n- Select 'Creator' to get details about who made the Space\n- Select 'Hosts' to see who's hosting\n- Select 'Topics' to see Space topics",
placeholder="Pick what extra information you want to see about the Space",
default=None,
advanced=True,
)
space_fields: SpaceFieldsFilter | None = SchemaField(
description="Choose what Space details you want to see, such as:\n- Title\n- Start/End times\n- Number of participants\n- Language\n- State (live/scheduled)\n- And more",
placeholder="Choose what Space information you want to get",
default=SpaceFieldsFilter(Space_Title=True, Host_User_IDs=True),
advanced=True,
)
user_fields: TweetUserFieldsFilter | None = SchemaField(
description="Choose what user information you want to see. This works when you select any of these in expansions above:\n- 'Creator' for Space creator details\n- 'Hosts' for host information\n- 'Speakers' for speaker details\n- 'Invited_Users' for invited user information",
placeholder="Pick what details you want to see about the users",
default=None,
advanced=True,
)
class ListExpansionInputs(BlockSchema):
expansions: ListExpansionsFilter | None = SchemaField(
description="Choose what extra information you want to get with your Twitter Lists:\n- Select 'List_Owner_ID' to get details about who owns the list\n\nThis will let you see more details about the list owner when you also select user fields below.",
placeholder="Pick what extra list information you want to see",
default=ListExpansionsFilter(List_Owner_ID=True),
advanced=True,
)
user_fields: TweetUserFieldsFilter | None = SchemaField(
description="Choose what information you want to see about list owners. This only works when you select 'List_Owner_ID' in expansions above.\n\nYou can see things like:\n- Their username\n- Profile picture\n- Account details\n- And more",
placeholder="Select what details you want to see about list owners",
default=TweetUserFieldsFilter(User_ID=True, Username=True),
advanced=True,
)
list_fields: ListFieldsFilter | None = SchemaField(
description="Choose what information you want to see about the Twitter Lists themselves, such as:\n- List name\n- Description\n- Number of followers\n- Number of members\n- Whether it's private\n- Creation date\n- And more",
placeholder="Pick what list details you want to see",
default=ListFieldsFilter(Owner_ID=True),
advanced=True,
)
class TweetTimeWindowInputs(BlockSchema):
start_time: datetime | None = SchemaField(
description="Start time in YYYY-MM-DDTHH:mm:ssZ format",
placeholder="Enter start time",
default=None,
advanced=False,
)
end_time: datetime | None = SchemaField(
description="End time in YYYY-MM-DDTHH:mm:ssZ format",
placeholder="Enter end time",
default=None,
advanced=False,
)
since_id: str | None = SchemaField(
description="Returns results with Tweet ID greater than this (more recent than), we give priority to since_id over start_time",
placeholder="Enter since ID",
default=None,
advanced=True,
)
until_id: str | None = SchemaField(
description="Returns results with Tweet ID less than this (that is, older than), and used with since_id",
placeholder="Enter until ID",
default=None,
advanced=True,
)
sort_order: str | None = SchemaField(
description="Order of returned tweets (recency or relevancy)",
placeholder="Enter sort order",
default=None,
advanced=True,
)

View File

@@ -0,0 +1,201 @@
# Todo : Add new Type support
# from typing import cast
# import tweepy
# from tweepy.client import Response
# from backend.blocks.twitter._serializer import IncludesSerializer, ResponseDataSerializer
# from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
# from backend.data.model import SchemaField
# from backend.blocks.twitter._builders import DMExpansionsBuilder
# from backend.blocks.twitter._types import DMEventExpansion, DMEventExpansionInputs, DMEventType, DMMediaField, DMTweetField, TweetUserFields
# from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
# from backend.blocks.twitter._auth import (
# TEST_CREDENTIALS,
# TEST_CREDENTIALS_INPUT,
# TwitterCredentials,
# TwitterCredentialsField,
# TwitterCredentialsInput,
# )
# Require Pro or Enterprise plan [Manual Testing Required]
# class TwitterGetDMEventsBlock(Block):
# """
# Gets a list of Direct Message events for the authenticated user
# """
# class Input(DMEventExpansionInputs):
# credentials: TwitterCredentialsInput = TwitterCredentialsField(
# ["dm.read", "offline.access", "user.read", "tweet.read"]
# )
# dm_conversation_id: str = SchemaField(
# description="The ID of the Direct Message conversation",
# placeholder="Enter conversation ID",
# required=True
# )
# max_results: int = SchemaField(
# description="Maximum number of results to return (1-100)",
# placeholder="Enter max results",
# advanced=True,
# default=10,
# )
# pagination_token: str = SchemaField(
# description="Token for pagination",
# placeholder="Enter pagination token",
# advanced=True,
# default=""
# )
# class Output(BlockSchema):
# # Common outputs
# event_ids: list[str] = SchemaField(description="DM Event IDs")
# event_texts: list[str] = SchemaField(description="DM Event text contents")
# event_types: list[str] = SchemaField(description="Types of DM events")
# next_token: str = SchemaField(description="Token for next page of results")
# # Complete outputs
# data: list[dict] = SchemaField(description="Complete DM events data")
# included: dict = SchemaField(description="Additional data requested via expansions")
# meta: dict = SchemaField(description="Metadata about the response")
# error: str = SchemaField(description="Error message if request failed")
# def __init__(self):
# super().__init__(
# id="dc37a6d4-a62e-11ef-a3a5-03061375737b",
# description="This block retrieves Direct Message events for the authenticated user.",
# categories={BlockCategory.SOCIAL},
# input_schema=TwitterGetDMEventsBlock.Input,
# output_schema=TwitterGetDMEventsBlock.Output,
# test_input={
# "dm_conversation_id": "1234567890",
# "max_results": 10,
# "credentials": TEST_CREDENTIALS_INPUT,
# "expansions": [],
# "event_types": [],
# "media_fields": [],
# "tweet_fields": [],
# "user_fields": []
# },
# test_credentials=TEST_CREDENTIALS,
# test_output=[
# ("event_ids", ["1346889436626259968"]),
# ("event_texts", ["Hello just you..."]),
# ("event_types", ["MessageCreate"]),
# ("next_token", None),
# ("data", [{"id": "1346889436626259968", "text": "Hello just you...", "event_type": "MessageCreate"}]),
# ("included", {}),
# ("meta", {}),
# ("error", "")
# ],
# test_mock={
# "get_dm_events": lambda *args, **kwargs: (
# [{"id": "1346889436626259968", "text": "Hello just you...", "event_type": "MessageCreate"}],
# {},
# {},
# ["1346889436626259968"],
# ["Hello just you..."],
# ["MessageCreate"],
# None
# )
# }
# )
# @staticmethod
# def get_dm_events(
# credentials: TwitterCredentials,
# dm_conversation_id: str,
# max_results: int,
# pagination_token: str,
# expansions: list[DMEventExpansion],
# event_types: list[DMEventType],
# media_fields: list[DMMediaField],
# tweet_fields: list[DMTweetField],
# user_fields: list[TweetUserFields]
# ):
# try:
# client = tweepy.Client(
# bearer_token=credentials.access_token.get_secret_value()
# )
# params = {
# "dm_conversation_id": dm_conversation_id,
# "max_results": max_results,
# "pagination_token": None if pagination_token == "" else pagination_token,
# "user_auth": False
# }
# params = (DMExpansionsBuilder(params)
# .add_expansions(expansions)
# .add_event_types(event_types)
# .add_media_fields(media_fields)
# .add_tweet_fields(tweet_fields)
# .add_user_fields(user_fields)
# .build())
# response = cast(Response, client.get_direct_message_events(**params))
# meta = {}
# event_ids = []
# event_texts = []
# event_types = []
# next_token = None
# if response.meta:
# meta = response.meta
# next_token = meta.get("next_token")
# included = IncludesSerializer.serialize(response.includes)
# data = ResponseDataSerializer.serialize_list(response.data)
# if response.data:
# event_ids = [str(item.id) for item in response.data]
# event_texts = [item.text if hasattr(item, "text") else None for item in response.data]
# event_types = [item.event_type for item in response.data]
# return data, included, meta, event_ids, event_texts, event_types, next_token
# raise Exception("No DM events found")
# except tweepy.TweepyException:
# raise
# def run(
# self,
# input_data: Input,
# *,
# credentials: TwitterCredentials,
# **kwargs,
# ) -> BlockOutput:
# try:
# event_data, included, meta, event_ids, event_texts, event_types, next_token = self.get_dm_events(
# credentials,
# input_data.dm_conversation_id,
# input_data.max_results,
# input_data.pagination_token,
# input_data.expansions,
# input_data.event_types,
# input_data.media_fields,
# input_data.tweet_fields,
# input_data.user_fields
# )
# if event_ids:
# yield "event_ids", event_ids
# if event_texts:
# yield "event_texts", event_texts
# if event_types:
# yield "event_types", event_types
# if next_token:
# yield "next_token", next_token
# if event_data:
# yield "data", event_data
# if included:
# yield "included", included
# if meta:
# yield "meta", meta
# except Exception as e:
# yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,260 @@
# Todo : Add new Type support
# from typing import cast
# import tweepy
# from tweepy.client import Response
# from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
# from backend.data.model import SchemaField
# from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
# from backend.blocks.twitter._auth import (
# TEST_CREDENTIALS,
# TEST_CREDENTIALS_INPUT,
# TwitterCredentials,
# TwitterCredentialsField,
# TwitterCredentialsInput,
# )
# Pro and Enterprise plan [Manual Testing Required]
# class TwitterSendDirectMessageBlock(Block):
# """
# Sends a direct message to a Twitter user
# """
# class Input(BlockSchema):
# credentials: TwitterCredentialsInput = TwitterCredentialsField(
# ["offline.access", "direct_messages.write"]
# )
# participant_id: str = SchemaField(
# description="The User ID of the account to send DM to",
# placeholder="Enter recipient user ID",
# default="",
# advanced=False
# )
# dm_conversation_id: str = SchemaField(
# description="The conversation ID to send message to",
# placeholder="Enter conversation ID",
# default="",
# advanced=False
# )
# text: str = SchemaField(
# description="Text of the Direct Message (up to 10,000 characters)",
# placeholder="Enter message text",
# default="",
# advanced=False
# )
# media_id: str = SchemaField(
# description="Media ID to attach to the message",
# placeholder="Enter media ID",
# default=""
# )
# class Output(BlockSchema):
# dm_event_id: str = SchemaField(description="ID of the sent direct message")
# dm_conversation_id_: str = SchemaField(description="ID of the conversation")
# error: str = SchemaField(description="Error message if sending failed")
# def __init__(self):
# super().__init__(
# id="f32f2786-a62e-11ef-a93d-a3ef199dde7f",
# description="This block sends a direct message to a specified Twitter user.",
# categories={BlockCategory.SOCIAL},
# input_schema=TwitterSendDirectMessageBlock.Input,
# output_schema=TwitterSendDirectMessageBlock.Output,
# test_input={
# "participant_id": "783214",
# "dm_conversation_id": "",
# "text": "Hello from Twitter API",
# "media_id": "",
# "credentials": TEST_CREDENTIALS_INPUT
# },
# test_credentials=TEST_CREDENTIALS,
# test_output=[
# ("dm_event_id", "0987654321"),
# ("dm_conversation_id_", "1234567890"),
# ("error", "")
# ],
# test_mock={
# "send_direct_message": lambda *args, **kwargs: (
# "0987654321",
# "1234567890"
# )
# },
# )
# @staticmethod
# def send_direct_message(
# credentials: TwitterCredentials,
# participant_id: str,
# dm_conversation_id: str,
# text: str,
# media_id: str
# ):
# try:
# client = tweepy.Client(
# bearer_token=credentials.access_token.get_secret_value()
# )
# response = cast(
# Response,
# client.create_direct_message(
# participant_id=None if participant_id == "" else participant_id,
# dm_conversation_id=None if dm_conversation_id == "" else dm_conversation_id,
# text=None if text == "" else text,
# media_id=None if media_id == "" else media_id,
# user_auth=False
# )
# )
# if not response.data:
# raise Exception("Failed to send direct message")
# return response.data["dm_event_id"], response.data["dm_conversation_id"]
# except tweepy.TweepyException:
# raise
# except Exception as e:
# print(f"Unexpected error: {str(e)}")
# raise
# def run(
# self,
# input_data: Input,
# *,
# credentials: TwitterCredentials,
# **kwargs,
# ) -> BlockOutput:
# try:
# dm_event_id, dm_conversation_id = self.send_direct_message(
# credentials,
# input_data.participant_id,
# input_data.dm_conversation_id,
# input_data.text,
# input_data.media_id
# )
# yield "dm_event_id", dm_event_id
# yield "dm_conversation_id", dm_conversation_id
# except Exception as e:
# yield "error", handle_tweepy_exception(e)
# class TwitterCreateDMConversationBlock(Block):
# """
# Creates a new group direct message conversation on Twitter
# """
# class Input(BlockSchema):
# credentials: TwitterCredentialsInput = TwitterCredentialsField(
# ["offline.access", "dm.write","dm.read","tweet.read","user.read"]
# )
# participant_ids: list[str] = SchemaField(
# description="Array of User IDs to create conversation with (max 50)",
# placeholder="Enter participant user IDs",
# default=[],
# advanced=False
# )
# text: str = SchemaField(
# description="Text of the Direct Message (up to 10,000 characters)",
# placeholder="Enter message text",
# default="",
# advanced=False
# )
# media_id: str = SchemaField(
# description="Media ID to attach to the message",
# placeholder="Enter media ID",
# default="",
# advanced=False
# )
# class Output(BlockSchema):
# dm_event_id: str = SchemaField(description="ID of the sent direct message")
# dm_conversation_id: str = SchemaField(description="ID of the conversation")
# error: str = SchemaField(description="Error message if sending failed")
# def __init__(self):
# super().__init__(
# id="ec11cabc-a62e-11ef-8c0e-3fe37ba2ec92",
# description="This block creates a new group DM conversation with specified Twitter users.",
# categories={BlockCategory.SOCIAL},
# input_schema=TwitterCreateDMConversationBlock.Input,
# output_schema=TwitterCreateDMConversationBlock.Output,
# test_input={
# "participant_ids": ["783214", "2244994945"],
# "text": "Hello from Twitter API",
# "media_id": "",
# "credentials": TEST_CREDENTIALS_INPUT
# },
# test_credentials=TEST_CREDENTIALS,
# test_output=[
# ("dm_event_id", "0987654321"),
# ("dm_conversation_id", "1234567890"),
# ("error", "")
# ],
# test_mock={
# "create_dm_conversation": lambda *args, **kwargs: (
# "0987654321",
# "1234567890"
# )
# },
# )
# @staticmethod
# def create_dm_conversation(
# credentials: TwitterCredentials,
# participant_ids: list[str],
# text: str,
# media_id: str
# ):
# try:
# client = tweepy.Client(
# bearer_token=credentials.access_token.get_secret_value()
# )
# response = cast(
# Response,
# client.create_direct_message_conversation(
# participant_ids=participant_ids,
# text=None if text == "" else text,
# media_id=None if media_id == "" else media_id,
# user_auth=False
# )
# )
# if not response.data:
# raise Exception("Failed to create DM conversation")
# return response.data["dm_event_id"], response.data["dm_conversation_id"]
# except tweepy.TweepyException:
# raise
# except Exception as e:
# print(f"Unexpected error: {str(e)}")
# raise
# def run(
# self,
# input_data: Input,
# *,
# credentials: TwitterCredentials,
# **kwargs,
# ) -> BlockOutput:
# try:
# dm_event_id, dm_conversation_id = self.create_dm_conversation(
# credentials,
# input_data.participant_ids,
# input_data.text,
# input_data.media_id
# )
# yield "dm_event_id", dm_event_id
# yield "dm_conversation_id", dm_conversation_id
# except Exception as e:
# yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,470 @@
# from typing import cast
import tweepy
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
# from backend.blocks.twitter._builders import UserExpansionsBuilder
# from backend.blocks.twitter._types import TweetFields, TweetUserFields, UserExpansionInputs, UserExpansions
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
# from tweepy.client import Response
class TwitterUnfollowListBlock(Block):
"""
Unfollows a Twitter list for the authenticated user
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["follows.write", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to unfollow",
placeholder="Enter list ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the unfollow was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="1f43310a-a62f-11ef-8276-2b06a1bbae1a",
description="This block unfollows a specified Twitter list for the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnfollowListBlock.Input,
output_schema=TwitterUnfollowListBlock.Output,
test_input={"list_id": "123456789", "credentials": TEST_CREDENTIALS_INPUT},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unfollow_list": lambda *args, **kwargs: True},
)
@staticmethod
def unfollow_list(credentials: TwitterCredentials, list_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unfollow_list(list_id=list_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unfollow_list(credentials, input_data.list_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterFollowListBlock(Block):
"""
Follows a Twitter list for the authenticated user
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "list.write", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to follow",
placeholder="Enter list ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the follow was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="03d8acf6-a62f-11ef-b17f-b72b04a09e79",
description="This block follows a specified Twitter list for the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterFollowListBlock.Input,
output_schema=TwitterFollowListBlock.Output,
test_input={"list_id": "123456789", "credentials": TEST_CREDENTIALS_INPUT},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"follow_list": lambda *args, **kwargs: True},
)
@staticmethod
def follow_list(credentials: TwitterCredentials, list_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.follow_list(list_id=list_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.follow_list(credentials, input_data.list_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
# Enterprise Level [Need to do Manual testing], There is a high possibility that we might get error in this
# Needs Type Input in this
# class TwitterListGetFollowersBlock(Block):
# """
# Gets followers of a specified Twitter list
# """
# class Input(UserExpansionInputs):
# credentials: TwitterCredentialsInput = TwitterCredentialsField(
# ["tweet.read","users.read", "list.read", "offline.access"]
# )
# list_id: str = SchemaField(
# description="The ID of the List to get followers for",
# placeholder="Enter list ID",
# required=True
# )
# max_results: int = SchemaField(
# description="Max number of results per page (1-100)",
# placeholder="Enter max results",
# default=10,
# advanced=True,
# )
# pagination_token: str = SchemaField(
# description="Token for pagination",
# placeholder="Enter pagination token",
# default="",
# advanced=True,
# )
# class Output(BlockSchema):
# user_ids: list[str] = SchemaField(description="List of user IDs of followers")
# usernames: list[str] = SchemaField(description="List of usernames of followers")
# next_token: str = SchemaField(description="Token for next page of results")
# data: list[dict] = SchemaField(description="Complete follower data")
# included: dict = SchemaField(description="Additional data requested via expansions")
# meta: dict = SchemaField(description="Metadata about the response")
# error: str = SchemaField(description="Error message if the request failed")
# def __init__(self):
# super().__init__(
# id="16b289b4-a62f-11ef-95d4-bb29b849eb99",
# description="This block retrieves followers of a specified Twitter list.",
# categories={BlockCategory.SOCIAL},
# input_schema=TwitterListGetFollowersBlock.Input,
# output_schema=TwitterListGetFollowersBlock.Output,
# test_input={
# "list_id": "123456789",
# "max_results": 10,
# "pagination_token": None,
# "credentials": TEST_CREDENTIALS_INPUT,
# "expansions": [],
# "tweet_fields": [],
# "user_fields": []
# },
# test_credentials=TEST_CREDENTIALS,
# test_output=[
# ("user_ids", ["2244994945"]),
# ("usernames", ["testuser"]),
# ("next_token", None),
# ("data", {"followers": [{"id": "2244994945", "username": "testuser"}]}),
# ("included", {}),
# ("meta", {}),
# ("error", "")
# ],
# test_mock={
# "get_list_followers": lambda *args, **kwargs: ({
# "followers": [{"id": "2244994945", "username": "testuser"}]
# }, {}, {}, ["2244994945"], ["testuser"], None)
# }
# )
# @staticmethod
# def get_list_followers(
# credentials: TwitterCredentials,
# list_id: str,
# max_results: int,
# pagination_token: str,
# expansions: list[UserExpansions],
# tweet_fields: list[TweetFields],
# user_fields: list[TweetUserFields]
# ):
# try:
# client = tweepy.Client(
# bearer_token=credentials.access_token.get_secret_value(),
# )
# params = {
# "id": list_id,
# "max_results": max_results,
# "pagination_token": None if pagination_token == "" else pagination_token,
# "user_auth": False
# }
# params = (UserExpansionsBuilder(params)
# .add_expansions(expansions)
# .add_tweet_fields(tweet_fields)
# .add_user_fields(user_fields)
# .build())
# response = cast(
# Response,
# client.get_list_followers(**params)
# )
# meta = {}
# user_ids = []
# usernames = []
# next_token = None
# if response.meta:
# meta = response.meta
# next_token = meta.get("next_token")
# included = IncludesSerializer.serialize(response.includes)
# data = ResponseDataSerializer.serialize_list(response.data)
# if response.data:
# user_ids = [str(item.id) for item in response.data]
# usernames = [item.username for item in response.data]
# return data, included, meta, user_ids, usernames, next_token
# raise Exception("No followers found")
# except tweepy.TweepyException:
# raise
# def run(
# self,
# input_data: Input,
# *,
# credentials: TwitterCredentials,
# **kwargs,
# ) -> BlockOutput:
# try:
# followers_data, included, meta, user_ids, usernames, next_token = self.get_list_followers(
# credentials,
# input_data.list_id,
# input_data.max_results,
# input_data.pagination_token,
# input_data.expansions,
# input_data.tweet_fields,
# input_data.user_fields
# )
# if user_ids:
# yield "user_ids", user_ids
# if usernames:
# yield "usernames", usernames
# if next_token:
# yield "next_token", next_token
# if followers_data:
# yield "data", followers_data
# if included:
# yield "included", included
# if meta:
# yield "meta", meta
# except Exception as e:
# yield "error", handle_tweepy_exception(e)
# class TwitterGetFollowedListsBlock(Block):
# """
# Gets lists followed by a specified Twitter user
# """
# class Input(UserExpansionInputs):
# credentials: TwitterCredentialsInput = TwitterCredentialsField(
# ["follows.read", "users.read", "list.read", "offline.access"]
# )
# user_id: str = SchemaField(
# description="The user ID whose followed Lists to retrieve",
# placeholder="Enter user ID",
# required=True
# )
# max_results: int = SchemaField(
# description="Max number of results per page (1-100)",
# placeholder="Enter max results",
# default=10,
# advanced=True,
# )
# pagination_token: str = SchemaField(
# description="Token for pagination",
# placeholder="Enter pagination token",
# default="",
# advanced=True,
# )
# class Output(BlockSchema):
# list_ids: list[str] = SchemaField(description="List of list IDs")
# list_names: list[str] = SchemaField(description="List of list names")
# data: list[dict] = SchemaField(description="Complete list data")
# includes: dict = SchemaField(description="Additional data requested via expansions")
# meta: dict = SchemaField(description="Metadata about the response")
# next_token: str = SchemaField(description="Token for next page of results")
# error: str = SchemaField(description="Error message if the request failed")
# def __init__(self):
# super().__init__(
# id="0e18bbfc-a62f-11ef-94fa-1f1e174b809e",
# description="This block retrieves all Lists a specified user follows.",
# categories={BlockCategory.SOCIAL},
# input_schema=TwitterGetFollowedListsBlock.Input,
# output_schema=TwitterGetFollowedListsBlock.Output,
# test_input={
# "user_id": "123456789",
# "max_results": 10,
# "pagination_token": None,
# "credentials": TEST_CREDENTIALS_INPUT,
# "expansions": [],
# "tweet_fields": [],
# "user_fields": []
# },
# test_credentials=TEST_CREDENTIALS,
# test_output=[
# ("list_ids", ["12345"]),
# ("list_names", ["Test List"]),
# ("data", {"followed_lists": [{"id": "12345", "name": "Test List"}]}),
# ("includes", {}),
# ("meta", {}),
# ("next_token", None),
# ("error", "")
# ],
# test_mock={
# "get_followed_lists": lambda *args, **kwargs: ({
# "followed_lists": [{"id": "12345", "name": "Test List"}]
# }, {}, {}, ["12345"], ["Test List"], None)
# }
# )
# @staticmethod
# def get_followed_lists(
# credentials: TwitterCredentials,
# user_id: str,
# max_results: int,
# pagination_token: str,
# expansions: list[UserExpansions],
# tweet_fields: list[TweetFields],
# user_fields: list[TweetUserFields]
# ):
# try:
# client = tweepy.Client(
# bearer_token=credentials.access_token.get_secret_value(),
# )
# params = {
# "id": user_id,
# "max_results": max_results,
# "pagination_token": None if pagination_token == "" else pagination_token,
# "user_auth": False
# }
# params = (UserExpansionsBuilder(params)
# .add_expansions(expansions)
# .add_tweet_fields(tweet_fields)
# .add_user_fields(user_fields)
# .build())
# response = cast(
# Response,
# client.get_followed_lists(**params)
# )
# meta = {}
# list_ids = []
# list_names = []
# next_token = None
# if response.meta:
# meta = response.meta
# next_token = meta.get("next_token")
# included = IncludesSerializer.serialize(response.includes)
# data = ResponseDataSerializer.serialize_list(response.data)
# if response.data:
# list_ids = [str(item.id) for item in response.data]
# list_names = [item.name for item in response.data]
# return data, included, meta, list_ids, list_names, next_token
# raise Exception("No followed lists found")
# except tweepy.TweepyException:
# raise
# def run(
# self,
# input_data: Input,
# *,
# credentials: TwitterCredentials,
# **kwargs,
# ) -> BlockOutput:
# try:
# lists_data, included, meta, list_ids, list_names, next_token = self.get_followed_lists(
# credentials,
# input_data.user_id,
# input_data.max_results,
# input_data.pagination_token,
# input_data.expansions,
# input_data.tweet_fields,
# input_data.user_fields
# )
# if list_ids:
# yield "list_ids", list_ids
# if list_names:
# yield "list_names", list_names
# if next_token:
# yield "next_token", next_token
# if lists_data:
# yield "data", lists_data
# if included:
# yield "includes", included
# if meta:
# yield "meta", meta
# except Exception as e:
# yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,348 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import ListExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ListExpansionInputs,
ListExpansionsFilter,
ListFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterGetListBlock(Block):
"""
Gets information about a Twitter List specified by ID
"""
class Input(ListExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to lookup",
placeholder="Enter list ID",
required=True,
)
class Output(BlockSchema):
# Common outputs
id: str = SchemaField(description="ID of the Twitter List")
name: str = SchemaField(description="Name of the Twitter List")
owner_id: str = SchemaField(description="ID of the List owner")
owner_username: str = SchemaField(description="Username of the List owner")
# Complete outputs
data: dict = SchemaField(description="Complete list data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata about the response")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="34ebc80a-a62f-11ef-9c2a-3fcab6c07079",
description="This block retrieves information about a specified Twitter List.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetListBlock.Input,
output_schema=TwitterGetListBlock.Output,
test_input={
"list_id": "84839422",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"list_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", "84839422"),
("name", "Official Twitter Accounts"),
("owner_id", "2244994945"),
("owner_username", "TwitterAPI"),
("data", {"id": "84839422", "name": "Official Twitter Accounts"}),
],
test_mock={
"get_list": lambda *args, **kwargs: (
{"id": "84839422", "name": "Official Twitter Accounts"},
{},
{},
"2244994945",
"TwitterAPI",
)
},
)
@staticmethod
def get_list(
credentials: TwitterCredentials,
list_id: str,
expansions: ListExpansionsFilter | None,
user_fields: TweetUserFieldsFilter | None,
list_fields: ListFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {"id": list_id, "user_auth": False}
params = (
ListExpansionsBuilder(params)
.add_expansions(expansions)
.add_user_fields(user_fields)
.add_list_fields(list_fields)
.build()
)
response = cast(Response, client.get_list(**params))
meta = {}
owner_id = ""
owner_username = ""
included = {}
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if "users" in included:
owner_id = str(included["users"][0]["id"])
owner_username = included["users"][0]["username"]
if response.meta:
meta = response.meta
if response.data:
data_dict = ResponseDataSerializer.serialize_dict(response.data)
return data_dict, included, meta, owner_id, owner_username
raise Exception("List not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
list_data, included, meta, owner_id, owner_username = self.get_list(
credentials,
input_data.list_id,
input_data.expansions,
input_data.user_fields,
input_data.list_fields,
)
yield "id", str(list_data["id"])
yield "name", list_data["name"]
if owner_id:
yield "owner_id", owner_id
if owner_username:
yield "owner_username", owner_username
yield "data", {"id": list_data["id"], "name": list_data["name"]}
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetOwnedListsBlock(Block):
"""
Gets all Lists owned by the specified user
"""
class Input(ListExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "list.read", "offline.access"]
)
user_id: str = SchemaField(
description="The user ID whose owned Lists to retrieve",
placeholder="Enter user ID",
required=True,
)
max_results: int | None = SchemaField(
description="Maximum number of results per page (1-100)",
placeholder="Enter max results (default 100)",
advanced=True,
default=10,
)
pagination_token: str | None = SchemaField(
description="Token for pagination",
placeholder="Enter pagination token",
advanced=True,
default="",
)
class Output(BlockSchema):
# Common outputs
list_ids: list[str] = SchemaField(description="List ids of the owned lists")
list_names: list[str] = SchemaField(description="List names of the owned lists")
next_token: str = SchemaField(description="Token for next page of results")
# Complete outputs
data: list[dict] = SchemaField(description="Complete owned lists data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata about the response")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="2b6bdb26-a62f-11ef-a9ce-ff89c2568726",
description="This block retrieves all Lists owned by a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetOwnedListsBlock.Input,
output_schema=TwitterGetOwnedListsBlock.Output,
test_input={
"user_id": "2244994945",
"max_results": 10,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"list_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("list_ids", ["84839422"]),
("list_names", ["Official Twitter Accounts"]),
("data", [{"id": "84839422", "name": "Official Twitter Accounts"}]),
],
test_mock={
"get_owned_lists": lambda *args, **kwargs: (
[{"id": "84839422", "name": "Official Twitter Accounts"}],
{},
{},
["84839422"],
["Official Twitter Accounts"],
None,
)
},
)
@staticmethod
def get_owned_lists(
credentials: TwitterCredentials,
user_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: ListExpansionsFilter | None,
user_fields: TweetUserFieldsFilter | None,
list_fields: ListFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": user_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
ListExpansionsBuilder(params)
.add_expansions(expansions)
.add_user_fields(user_fields)
.add_list_fields(list_fields)
.build()
)
response = cast(Response, client.get_owned_lists(**params))
meta = {}
included = {}
list_ids = []
list_names = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
list_ids = [
str(item.id) for item in response.data if hasattr(item, "id")
]
list_names = [
item.name for item in response.data if hasattr(item, "name")
]
return data, included, meta, list_ids, list_names, next_token
raise Exception("User have no owned list")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
list_data, included, meta, list_ids, list_names, next_token = (
self.get_owned_lists(
credentials,
input_data.user_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.user_fields,
input_data.list_fields,
)
)
if list_ids:
yield "list_ids", list_ids
if list_names:
yield "list_names", list_names
if next_token:
yield "next_token", next_token
if list_data:
yield "data", list_data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,527 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import (
ListExpansionsBuilder,
UserExpansionsBuilder,
)
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ListExpansionInputs,
ListExpansionsFilter,
ListFieldsFilter,
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterRemoveListMemberBlock(Block):
"""
Removes a member from a Twitter List that the authenticated user owns
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "users.read", "tweet.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to remove the member from",
placeholder="Enter list ID",
required=True,
)
user_id: str = SchemaField(
description="The ID of the user to remove from the List",
placeholder="Enter user ID to remove",
required=True,
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the member was successfully removed"
)
error: str = SchemaField(description="Error message if the removal failed")
def __init__(self):
super().__init__(
id="5a3d1320-a62f-11ef-b7ce-a79e7656bcb0",
description="This block removes a specified user from a Twitter List owned by the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterRemoveListMemberBlock.Input,
output_schema=TwitterRemoveListMemberBlock.Output,
test_input={
"list_id": "123456789",
"user_id": "987654321",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"remove_list_member": lambda *args, **kwargs: True},
)
@staticmethod
def remove_list_member(credentials: TwitterCredentials, list_id: str, user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.remove_list_member(id=list_id, user_id=user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.remove_list_member(
credentials, input_data.list_id, input_data.user_id
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterAddListMemberBlock(Block):
"""
Adds a member to a Twitter List that the authenticated user owns
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "users.read", "tweet.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to add the member to",
placeholder="Enter list ID",
required=True,
)
user_id: str = SchemaField(
description="The ID of the user to add to the List",
placeholder="Enter user ID to add",
required=True,
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the member was successfully added"
)
error: str = SchemaField(description="Error message if the addition failed")
def __init__(self):
super().__init__(
id="3ee8284e-a62f-11ef-84e4-8f6e2cbf0ddb",
description="This block adds a specified user to a Twitter List owned by the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterAddListMemberBlock.Input,
output_schema=TwitterAddListMemberBlock.Output,
test_input={
"list_id": "123456789",
"user_id": "987654321",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"add_list_member": lambda *args, **kwargs: True},
)
@staticmethod
def add_list_member(credentials: TwitterCredentials, list_id: str, user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.add_list_member(id=list_id, user_id=user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.add_list_member(
credentials, input_data.list_id, input_data.user_id
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetListMembersBlock(Block):
"""
Gets the members of a specified Twitter List
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to get members from",
placeholder="Enter list ID",
required=True,
)
max_results: int | None = SchemaField(
description="Maximum number of results per page (1-100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
ids: list[str] = SchemaField(description="List of member user IDs")
usernames: list[str] = SchemaField(description="List of member usernames")
next_token: str = SchemaField(description="Next token for pagination")
data: list[dict] = SchemaField(
description="Complete user data for list members"
)
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="4dba046e-a62f-11ef-b69a-87240c84b4c7",
description="This block retrieves the members of a specified Twitter List.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetListMembersBlock.Input,
output_schema=TwitterGetListMembersBlock.Output,
test_input={
"list_id": "123456789",
"max_results": 2,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["12345", "67890"]),
("usernames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "12345", "username": "testuser1"},
{"id": "67890", "username": "testuser2"},
],
),
],
test_mock={
"get_list_members": lambda *args, **kwargs: (
["12345", "67890"],
["testuser1", "testuser2"],
[
{"id": "12345", "username": "testuser1"},
{"id": "67890", "username": "testuser2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_list_members(
credentials: TwitterCredentials,
list_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": list_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_list_members(**params))
meta = {}
included = {}
next_token = None
user_ids = []
usernames = []
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
user_ids = [str(user.id) for user in response.data]
usernames = [user.username for user in response.data]
return user_ids, usernames, data, included, meta, next_token
raise Exception("List members not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, usernames, data, included, meta, next_token = self.get_list_members(
credentials,
input_data.list_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if usernames:
yield "usernames", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetListMembershipsBlock(Block):
"""
Gets all Lists that a specified user is a member of
"""
class Input(ListExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.read", "offline.access"]
)
user_id: str = SchemaField(
description="The ID of the user whose List memberships to retrieve",
placeholder="Enter user ID",
required=True,
)
max_results: int | None = SchemaField(
description="Maximum number of results per page (1-100)",
placeholder="Enter max results",
advanced=True,
default=10,
)
pagination_token: str | None = SchemaField(
description="Token for pagination of results",
placeholder="Enter pagination token",
advanced=True,
default="",
)
class Output(BlockSchema):
list_ids: list[str] = SchemaField(description="List of list IDs")
next_token: str = SchemaField(description="Next token for pagination")
data: list[dict] = SchemaField(description="List membership data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata about pagination")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="46e6429c-a62f-11ef-81c0-2b55bc7823ba",
description="This block retrieves all Lists that a specified user is a member of.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetListMembershipsBlock.Input,
output_schema=TwitterGetListMembershipsBlock.Output,
test_input={
"user_id": "123456789",
"max_results": 1,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"list_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("list_ids", ["84839422"]),
("data", [{"id": "84839422"}]),
],
test_mock={
"get_list_memberships": lambda *args, **kwargs: (
[{"id": "84839422"}],
{},
{},
["84839422"],
None,
)
},
)
@staticmethod
def get_list_memberships(
credentials: TwitterCredentials,
user_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: ListExpansionsFilter | None,
user_fields: TweetUserFieldsFilter | None,
list_fields: ListFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": user_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
ListExpansionsBuilder(params)
.add_expansions(expansions)
.add_user_fields(user_fields)
.add_list_fields(list_fields)
.build()
)
response = cast(Response, client.get_list_memberships(**params))
meta = {}
included = {}
next_token = None
list_ids = []
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
list_ids = [str(lst.id) for lst in response.data]
return data, included, meta, list_ids, next_token
raise Exception("List memberships not found")
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, meta, list_ids, next_token = self.get_list_memberships(
credentials,
input_data.user_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.user_fields,
input_data.list_fields,
)
if list_ids:
yield "list_ids", list_ids
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,217 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import TweetExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterGetListTweetsBlock(Block):
"""
Gets tweets from a specified Twitter list
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List whose Tweets you would like to retrieve",
placeholder="Enter list ID",
required=True,
)
max_results: int | None = SchemaField(
description="Maximum number of results per page (1-100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for paginating through results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
# Common outputs
tweet_ids: list[str] = SchemaField(description="List of tweet IDs")
texts: list[str] = SchemaField(description="List of tweet texts")
next_token: str = SchemaField(description="Token for next page of results")
# Complete outputs
data: list[dict] = SchemaField(description="Complete list tweets data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(
description="Response metadata including pagination tokens"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="6657edb0-a62f-11ef-8c10-0326d832467d",
description="This block retrieves tweets from a specified Twitter list.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetListTweetsBlock.Input,
output_schema=TwitterGetListTweetsBlock.Output,
test_input={
"list_id": "84839422",
"max_results": 1,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("tweet_ids", ["1234567890"]),
("texts", ["Test tweet"]),
("data", [{"id": "1234567890", "text": "Test tweet"}]),
],
test_mock={
"get_list_tweets": lambda *args, **kwargs: (
[{"id": "1234567890", "text": "Test tweet"}],
{},
{},
["1234567890"],
["Test tweet"],
None,
)
},
)
@staticmethod
def get_list_tweets(
credentials: TwitterCredentials,
list_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": list_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_list_tweets(**params))
meta = {}
included = {}
tweet_ids = []
texts = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
tweet_ids = [str(item.id) for item in response.data]
texts = [item.text for item in response.data]
return data, included, meta, tweet_ids, texts, next_token
raise Exception("No tweets found in this list")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
list_data, included, meta, tweet_ids, texts, next_token = (
self.get_list_tweets(
credentials,
input_data.list_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if tweet_ids:
yield "tweet_ids", tweet_ids
if texts:
yield "texts", texts
if next_token:
yield "next_token", next_token
if list_data:
yield "data", list_data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,278 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterDeleteListBlock(Block):
"""
Deletes a Twitter List owned by the authenticated user
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to be deleted",
placeholder="Enter list ID",
required=True,
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the deletion was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="843c6892-a62f-11ef-a5c8-b71239a78d3b",
description="This block deletes a specified Twitter List owned by the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterDeleteListBlock.Input,
output_schema=TwitterDeleteListBlock.Output,
test_input={"list_id": "1234567890", "credentials": TEST_CREDENTIALS_INPUT},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"delete_list": lambda *args, **kwargs: True},
)
@staticmethod
def delete_list(credentials: TwitterCredentials, list_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.delete_list(id=list_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.delete_list(credentials, input_data.list_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterUpdateListBlock(Block):
"""
Updates a Twitter List owned by the authenticated user
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to be updated",
placeholder="Enter list ID",
advanced=False,
)
name: str | None = SchemaField(
description="New name for the List",
placeholder="Enter list name",
default="",
advanced=False,
)
description: str | None = SchemaField(
description="New description for the List",
placeholder="Enter list description",
default="",
advanced=False,
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the update was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="7d12630a-a62f-11ef-90c9-8f5a996612c3",
description="This block updates a specified Twitter List owned by the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUpdateListBlock.Input,
output_schema=TwitterUpdateListBlock.Output,
test_input={
"list_id": "1234567890",
"name": "Updated List Name",
"description": "Updated List Description",
"private": True,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"update_list": lambda *args, **kwargs: True},
)
@staticmethod
def update_list(
credentials: TwitterCredentials,
list_id: str,
name: str | None,
description: str | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.update_list(
id=list_id,
name=None if name == "" else name,
description=None if description == "" else description,
user_auth=False,
)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.update_list(
credentials, input_data.list_id, input_data.name, input_data.description
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterCreateListBlock(Block):
"""
Creates a Twitter List owned by the authenticated user
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "offline.access"]
)
name: str = SchemaField(
description="The name of the List to be created",
placeholder="Enter list name",
advanced=False,
default="",
)
description: str | None = SchemaField(
description="Description of the List",
placeholder="Enter list description",
advanced=False,
default="",
)
private: bool = SchemaField(
description="Whether the List should be private",
advanced=False,
default=False,
)
class Output(BlockSchema):
url: str = SchemaField(description="URL of the created list")
list_id: str = SchemaField(description="ID of the created list")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="724148ba-a62f-11ef-89ba-5349b813ef5f",
description="This block creates a new Twitter List for the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterCreateListBlock.Input,
output_schema=TwitterCreateListBlock.Output,
test_input={
"name": "New List Name",
"description": "New List Description",
"private": True,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("list_id", "1234567890"),
("url", "https://twitter.com/i/lists/1234567890"),
],
test_mock={"create_list": lambda *args, **kwargs: ("1234567890")},
)
@staticmethod
def create_list(
credentials: TwitterCredentials,
name: str,
description: str | None,
private: bool,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
response = cast(
Response,
client.create_list(
name=None if name == "" else name,
description=None if description == "" else description,
private=private,
user_auth=False,
),
)
list_id = str(response.data["id"])
return list_id
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
list_id = self.create_list(
credentials, input_data.name, input_data.description, input_data.private
)
yield "list_id", list_id
yield "url", f"https://twitter.com/i/lists/{list_id}"
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,285 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import ListExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ListExpansionInputs,
ListExpansionsFilter,
ListFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterUnpinListBlock(Block):
"""
Enables the authenticated user to unpin a List.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "users.read", "tweet.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to unpin",
placeholder="Enter list ID",
required=True,
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the unpin was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="a099c034-a62f-11ef-9622-47d0ceb73555",
description="This block allows the authenticated user to unpin a specified List.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnpinListBlock.Input,
output_schema=TwitterUnpinListBlock.Output,
test_input={"list_id": "123456789", "credentials": TEST_CREDENTIALS_INPUT},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"unpin_list": lambda *args, **kwargs: True},
)
@staticmethod
def unpin_list(credentials: TwitterCredentials, list_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unpin_list(list_id=list_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unpin_list(credentials, input_data.list_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterPinListBlock(Block):
"""
Enables the authenticated user to pin a List.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["list.write", "users.read", "tweet.read", "offline.access"]
)
list_id: str = SchemaField(
description="The ID of the List to pin",
placeholder="Enter list ID",
required=True,
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the pin was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="8ec16e48-a62f-11ef-9f35-f3d6de43a802",
description="This block allows the authenticated user to pin a specified List.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterPinListBlock.Input,
output_schema=TwitterPinListBlock.Output,
test_input={"list_id": "123456789", "credentials": TEST_CREDENTIALS_INPUT},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"pin_list": lambda *args, **kwargs: True},
)
@staticmethod
def pin_list(credentials: TwitterCredentials, list_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.pin_list(list_id=list_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.pin_list(credentials, input_data.list_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetPinnedListsBlock(Block):
"""
Returns the Lists pinned by the authenticated user.
"""
class Input(ListExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["lists.read", "users.read", "offline.access"]
)
class Output(BlockSchema):
list_ids: list[str] = SchemaField(description="List IDs of the pinned lists")
list_names: list[str] = SchemaField(
description="List names of the pinned lists"
)
data: list[dict] = SchemaField(
description="Response data containing pinned lists"
)
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata about the response")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="97e03aae-a62f-11ef-bc53-5b89cb02888f",
description="This block returns the Lists pinned by the authenticated user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetPinnedListsBlock.Input,
output_schema=TwitterGetPinnedListsBlock.Output,
test_input={
"expansions": None,
"list_fields": None,
"user_fields": None,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("list_ids", ["84839422"]),
("list_names", ["Twitter List"]),
("data", [{"id": "84839422", "name": "Twitter List"}]),
],
test_mock={
"get_pinned_lists": lambda *args, **kwargs: (
[{"id": "84839422", "name": "Twitter List"}],
{},
{},
["84839422"],
["Twitter List"],
)
},
)
@staticmethod
def get_pinned_lists(
credentials: TwitterCredentials,
expansions: ListExpansionsFilter | None,
user_fields: TweetUserFieldsFilter | None,
list_fields: ListFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {"user_auth": False}
params = (
ListExpansionsBuilder(params)
.add_expansions(expansions)
.add_user_fields(user_fields)
.add_list_fields(list_fields)
.build()
)
response = cast(Response, client.get_pinned_lists(**params))
meta = {}
included = {}
list_ids = []
list_names = []
if response.meta:
meta = response.meta
if response.includes:
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
list_ids = [str(item.id) for item in response.data]
list_names = [item.name for item in response.data]
return data, included, meta, list_ids, list_names
raise Exception("Lists not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
list_data, included, meta, list_ids, list_names = self.get_pinned_lists(
credentials,
input_data.expansions,
input_data.user_fields,
input_data.list_fields,
)
if list_ids:
yield "list_ids", list_ids
if list_names:
yield "list_names", list_names
if list_data:
yield "data", list_data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,195 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import SpaceExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
SpaceExpansionInputs,
SpaceExpansionsFilter,
SpaceFieldsFilter,
SpaceStatesFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterSearchSpacesBlock(Block):
"""
Returns live or scheduled Spaces matching specified search terms [for a week only]
"""
class Input(SpaceExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["spaces.read", "users.read", "tweet.read", "offline.access"]
)
query: str = SchemaField(
description="Search term to find in Space titles",
placeholder="Enter search query",
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
state: SpaceStatesFilter = SchemaField(
description="Type of Spaces to return (live, scheduled, or all)",
placeholder="Enter state filter",
default=SpaceStatesFilter.all,
)
class Output(BlockSchema):
# Common outputs that user commonly uses
ids: list[str] = SchemaField(description="List of space IDs")
titles: list[str] = SchemaField(description="List of space titles")
host_ids: list = SchemaField(description="List of host IDs")
next_token: str = SchemaField(description="Next token for pagination")
# Complete outputs for advanced use
data: list[dict] = SchemaField(description="Complete space data")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="aaefdd48-a62f-11ef-a73c-3f44df63e276",
description="This block searches for Twitter Spaces based on specified terms.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterSearchSpacesBlock.Input,
output_schema=TwitterSearchSpacesBlock.Output,
test_input={
"query": "tech",
"max_results": 1,
"state": "live",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"space_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1234"]),
("titles", ["Tech Talk"]),
("host_ids", ["5678"]),
("data", [{"id": "1234", "title": "Tech Talk", "host_ids": ["5678"]}]),
],
test_mock={
"search_spaces": lambda *args, **kwargs: (
[{"id": "1234", "title": "Tech Talk", "host_ids": ["5678"]}],
{},
{},
["1234"],
["Tech Talk"],
["5678"],
None,
)
},
)
@staticmethod
def search_spaces(
credentials: TwitterCredentials,
query: str,
max_results: int | None,
state: SpaceStatesFilter,
expansions: SpaceExpansionsFilter | None,
space_fields: SpaceFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {"query": query, "max_results": max_results, "state": state.value}
params = (
SpaceExpansionsBuilder(params)
.add_expansions(expansions)
.add_space_fields(space_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.search_spaces(**params))
meta = {}
next_token = ""
if response.meta:
meta = response.meta
if "next_token" in meta:
next_token = meta["next_token"]
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
ids = [str(space["id"]) for space in response.data if "id" in space]
titles = [space["title"] for space in data if "title" in space]
host_ids = [space["host_ids"] for space in data if "host_ids" in space]
return data, included, meta, ids, titles, host_ids, next_token
raise Exception("Spaces not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, meta, ids, titles, host_ids, next_token = (
self.search_spaces(
credentials,
input_data.query,
input_data.max_results,
input_data.state,
input_data.expansions,
input_data.space_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if titles:
yield "titles", titles
if host_ids:
yield "host_ids", host_ids
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "includes", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,651 @@
from typing import Literal, Union, cast
import tweepy
from pydantic import BaseModel
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import (
SpaceExpansionsBuilder,
TweetExpansionsBuilder,
UserExpansionsBuilder,
)
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
SpaceExpansionInputs,
SpaceExpansionsFilter,
SpaceFieldsFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class SpaceList(BaseModel):
discriminator: Literal["space_list"]
space_ids: list[str] = SchemaField(
description="List of Space IDs to lookup (up to 100)",
placeholder="Enter Space IDs",
default=[],
advanced=False,
)
class UserList(BaseModel):
discriminator: Literal["user_list"]
user_ids: list[str] = SchemaField(
description="List of user IDs to lookup their Spaces (up to 100)",
placeholder="Enter user IDs",
default=[],
advanced=False,
)
class TwitterGetSpacesBlock(Block):
"""
Gets information about multiple Twitter Spaces specified by Space IDs or creator user IDs
"""
class Input(SpaceExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["spaces.read", "users.read", "offline.access"]
)
identifier: Union[SpaceList, UserList] = SchemaField(
discriminator="discriminator",
description="Choose whether to lookup spaces by their IDs or by creator user IDs",
advanced=False,
)
class Output(BlockSchema):
# Common outputs
ids: list[str] = SchemaField(description="List of space IDs")
titles: list[str] = SchemaField(description="List of space titles")
# Complete outputs for advanced use
data: list[dict] = SchemaField(description="Complete space data")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="d75bd7d8-a62f-11ef-b0d8-c7a9496f617f",
description="This block retrieves information about multiple Twitter Spaces.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetSpacesBlock.Input,
output_schema=TwitterGetSpacesBlock.Output,
test_input={
"identifier": {
"discriminator": "space_list",
"space_ids": ["1DXxyRYNejbKM"],
},
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"space_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1DXxyRYNejbKM"]),
("titles", ["Test Space"]),
(
"data",
[
{
"id": "1DXxyRYNejbKM",
"title": "Test Space",
"host_id": "1234567",
}
],
),
],
test_mock={
"get_spaces": lambda *args, **kwargs: (
[
{
"id": "1DXxyRYNejbKM",
"title": "Test Space",
"host_id": "1234567",
}
],
{},
["1DXxyRYNejbKM"],
["Test Space"],
)
},
)
@staticmethod
def get_spaces(
credentials: TwitterCredentials,
identifier: Union[SpaceList, UserList],
expansions: SpaceExpansionsFilter | None,
space_fields: SpaceFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"ids": (
identifier.space_ids if isinstance(identifier, SpaceList) else None
),
"user_ids": (
identifier.user_ids if isinstance(identifier, UserList) else None
),
}
params = (
SpaceExpansionsBuilder(params)
.add_expansions(expansions)
.add_space_fields(space_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_spaces(**params))
ids = []
titles = []
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
ids = [space["id"] for space in data if "id" in space]
titles = [space["title"] for space in data if "title" in space]
return data, included, ids, titles
raise Exception("No spaces found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, ids, titles = self.get_spaces(
credentials,
input_data.identifier,
input_data.expansions,
input_data.space_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if titles:
yield "titles", titles
if data:
yield "data", data
if included:
yield "includes", included
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetSpaceByIdBlock(Block):
"""
Gets information about a single Twitter Space specified by Space ID
"""
class Input(SpaceExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["spaces.read", "users.read", "offline.access"]
)
space_id: str = SchemaField(
description="Space ID to lookup",
placeholder="Enter Space ID",
required=True,
)
class Output(BlockSchema):
# Common outputs
id: str = SchemaField(description="Space ID")
title: str = SchemaField(description="Space title")
host_ids: list[str] = SchemaField(description="Host ID")
# Complete outputs for advanced use
data: dict = SchemaField(description="Complete space data")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="c79700de-a62f-11ef-ab20-fb32bf9d5a9d",
description="This block retrieves information about a single Twitter Space.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetSpaceByIdBlock.Input,
output_schema=TwitterGetSpaceByIdBlock.Output,
test_input={
"space_id": "1DXxyRYNejbKM",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"space_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", "1DXxyRYNejbKM"),
("title", "Test Space"),
("host_ids", ["1234567"]),
(
"data",
{
"id": "1DXxyRYNejbKM",
"title": "Test Space",
"host_ids": ["1234567"],
},
),
],
test_mock={
"get_space": lambda *args, **kwargs: (
{
"id": "1DXxyRYNejbKM",
"title": "Test Space",
"host_ids": ["1234567"],
},
{},
)
},
)
@staticmethod
def get_space(
credentials: TwitterCredentials,
space_id: str,
expansions: SpaceExpansionsFilter | None,
space_fields: SpaceFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": space_id,
}
params = (
SpaceExpansionsBuilder(params)
.add_expansions(expansions)
.add_space_fields(space_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_space(**params))
includes = {}
if response.includes:
for key, value in response.includes.items():
if isinstance(value, list):
includes[key] = [
item.data if hasattr(item, "data") else item
for item in value
]
else:
includes[key] = value.data if hasattr(value, "data") else value
data = {}
if response.data:
for key, value in response.data.items():
if isinstance(value, list):
data[key] = [
item.data if hasattr(item, "data") else item
for item in value
]
else:
data[key] = value.data if hasattr(value, "data") else value
return data, includes
raise Exception("Space not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
space_data, includes = self.get_space(
credentials,
input_data.space_id,
input_data.expansions,
input_data.space_fields,
input_data.user_fields,
)
# Common outputs
if space_data:
if "id" in space_data:
yield "id", space_data.get("id")
if "title" in space_data:
yield "title", space_data.get("title")
if "host_ids" in space_data:
yield "host_ids", space_data.get("host_ids")
if space_data:
yield "data", space_data
if includes:
yield "includes", includes
except Exception as e:
yield "error", handle_tweepy_exception(e)
# Not tested yet, might have some problem
class TwitterGetSpaceBuyersBlock(Block):
"""
Gets list of users who purchased a ticket to the requested Space
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["spaces.read", "users.read", "offline.access"]
)
space_id: str = SchemaField(
description="Space ID to lookup buyers for",
placeholder="Enter Space ID",
required=True,
)
class Output(BlockSchema):
# Common outputs
buyer_ids: list[str] = SchemaField(description="List of buyer IDs")
usernames: list[str] = SchemaField(description="List of buyer usernames")
# Complete outputs for advanced use
data: list[dict] = SchemaField(description="Complete space buyers data")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="c1c121a8-a62f-11ef-8b0e-d7b85f96a46f",
description="This block retrieves a list of users who purchased tickets to a Twitter Space.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetSpaceBuyersBlock.Input,
output_schema=TwitterGetSpaceBuyersBlock.Output,
test_input={
"space_id": "1DXxyRYNejbKM",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("buyer_ids", ["2244994945"]),
("usernames", ["testuser"]),
(
"data",
[{"id": "2244994945", "username": "testuser", "name": "Test User"}],
),
],
test_mock={
"get_space_buyers": lambda *args, **kwargs: (
[{"id": "2244994945", "username": "testuser", "name": "Test User"}],
{},
["2244994945"],
["testuser"],
)
},
)
@staticmethod
def get_space_buyers(
credentials: TwitterCredentials,
space_id: str,
expansions: UserExpansionsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": space_id,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_space_buyers(**params))
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
buyer_ids = [buyer["id"] for buyer in data]
usernames = [buyer["username"] for buyer in data]
return data, included, buyer_ids, usernames
raise Exception("No buyers found for this Space")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
buyers_data, included, buyer_ids, usernames = self.get_space_buyers(
credentials,
input_data.space_id,
input_data.expansions,
input_data.user_fields,
)
if buyer_ids:
yield "buyer_ids", buyer_ids
if usernames:
yield "usernames", usernames
if buyers_data:
yield "data", buyers_data
if included:
yield "includes", included
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetSpaceTweetsBlock(Block):
"""
Gets list of Tweets shared in the requested Space
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["spaces.read", "users.read", "offline.access"]
)
space_id: str = SchemaField(
description="Space ID to lookup tweets for",
placeholder="Enter Space ID",
required=True,
)
class Output(BlockSchema):
# Common outputs
tweet_ids: list[str] = SchemaField(description="List of tweet IDs")
texts: list[str] = SchemaField(description="List of tweet texts")
# Complete outputs for advanced use
data: list[dict] = SchemaField(description="Complete space tweets data")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Response metadata")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="b69731e6-a62f-11ef-b2d4-1bf14dd6aee4",
description="This block retrieves tweets shared in a Twitter Space.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetSpaceTweetsBlock.Input,
output_schema=TwitterGetSpaceTweetsBlock.Output,
test_input={
"space_id": "1DXxyRYNejbKM",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("tweet_ids", ["1234567890"]),
("texts", ["Test tweet"]),
("data", [{"id": "1234567890", "text": "Test tweet"}]),
],
test_mock={
"get_space_tweets": lambda *args, **kwargs: (
[{"id": "1234567890", "text": "Test tweet"}], # data
{},
["1234567890"],
["Test tweet"],
{},
)
},
)
@staticmethod
def get_space_tweets(
credentials: TwitterCredentials,
space_id: str,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": space_id,
}
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_space_tweets(**params))
included = IncludesSerializer.serialize(response.includes)
if response.data:
data = ResponseDataSerializer.serialize_list(response.data)
tweet_ids = [str(tweet["id"]) for tweet in data]
texts = [tweet["text"] for tweet in data]
meta = response.meta or {}
return data, included, tweet_ids, texts, meta
raise Exception("No tweets found for this Space")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
tweets_data, included, tweet_ids, texts, meta = self.get_space_tweets(
credentials,
input_data.space_id,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
if tweet_ids:
yield "tweet_ids", tweet_ids
if texts:
yield "texts", texts
if tweets_data:
yield "data", tweets_data
if included:
yield "includes", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,20 @@
import tweepy
def handle_tweepy_exception(e: Exception) -> str:
if isinstance(e, tweepy.BadRequest):
return f"Bad Request (400): {str(e)}"
elif isinstance(e, tweepy.Unauthorized):
return f"Unauthorized (401): {str(e)}"
elif isinstance(e, tweepy.Forbidden):
return f"Forbidden (403): {str(e)}"
elif isinstance(e, tweepy.NotFound):
return f"Not Found (404): {str(e)}"
elif isinstance(e, tweepy.TooManyRequests):
return f"Too Many Requests (429): {str(e)}"
elif isinstance(e, tweepy.TwitterServerError):
return f"Twitter Server Error (5xx): {str(e)}"
elif isinstance(e, tweepy.TweepyException):
return f"Tweepy Error: {str(e)}"
else:
return f"Unexpected error: {str(e)}"

View File

@@ -0,0 +1,372 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import TweetExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterBookmarkTweetBlock(Block):
"""
Bookmark a tweet on Twitter
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "bookmark.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to bookmark",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the bookmark was successful")
error: str = SchemaField(description="Error message if the bookmark failed")
def __init__(self):
super().__init__(
id="f33d67be-a62f-11ef-a797-ff83ec29ee8e",
description="This block bookmarks a tweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterBookmarkTweetBlock.Input,
output_schema=TwitterBookmarkTweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"bookmark_tweet": lambda *args, **kwargs: True},
)
@staticmethod
def bookmark_tweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.bookmark(tweet_id)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.bookmark_tweet(credentials, input_data.tweet_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetBookmarkedTweetsBlock(Block):
"""
Get All your bookmarked tweets from Twitter
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "bookmark.read", "users.read", "offline.access"]
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
id: list[str] = SchemaField(description="All Tweet IDs")
text: list[str] = SchemaField(description="All Tweet texts")
userId: list[str] = SchemaField(description="IDs of the tweet authors")
userName: list[str] = SchemaField(description="Usernames of the tweet authors")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
next_token: str = SchemaField(description="Next token for pagination")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="ed26783e-a62f-11ef-9a21-c77c57dd8a1f",
description="This block retrieves bookmarked tweets from Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetBookmarkedTweetsBlock.Input,
output_schema=TwitterGetBookmarkedTweetsBlock.Output,
test_input={
"max_results": 2,
"pagination_token": None,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", ["1234567890"]),
("text", ["Test tweet"]),
("userId", ["12345"]),
("userName", ["testuser"]),
("data", [{"id": "1234567890", "text": "Test tweet"}]),
],
test_mock={
"get_bookmarked_tweets": lambda *args, **kwargs: (
["1234567890"],
["Test tweet"],
["12345"],
["testuser"],
[{"id": "1234567890", "text": "Test tweet"}],
{},
{},
None,
)
},
)
@staticmethod
def get_bookmarked_tweets(
credentials: TwitterCredentials,
max_results: int | None,
pagination_token: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
}
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(
Response,
client.get_bookmarks(**params),
)
meta = {}
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if "users" in included:
for user in included["users"]:
user_ids.append(str(user["id"]))
user_names.append(user["username"])
return (
tweet_ids,
tweet_texts,
user_ids,
user_names,
data,
included,
meta,
next_token,
)
raise Exception("No bookmarked tweets found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta, next_token = (
self.get_bookmarked_tweets(
credentials,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "id", ids
if texts:
yield "text", texts
if user_ids:
yield "userId", user_ids
if user_names:
yield "userName", user_names
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
if next_token:
yield "next_token", next_token
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterRemoveBookmarkTweetBlock(Block):
"""
Remove a bookmark for a tweet on Twitter
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "bookmark.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to remove bookmark from",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the bookmark was successfully removed"
)
error: str = SchemaField(
description="Error message if the bookmark removal failed"
)
def __init__(self):
super().__init__(
id="e4100684-a62f-11ef-9be9-770cb41a2616",
description="This block removes a bookmark from a tweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterRemoveBookmarkTweetBlock.Input,
output_schema=TwitterRemoveBookmarkTweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"remove_bookmark_tweet": lambda *args, **kwargs: True},
)
@staticmethod
def remove_bookmark_tweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.remove_bookmark(tweet_id)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.remove_bookmark_tweet(credentials, input_data.tweet_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,154 @@
import tweepy
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterHideReplyBlock(Block):
"""
Hides a reply of one of your tweets
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.moderate.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet reply to hide",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the operation was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="07d58b3e-a630-11ef-a030-93701d1a465e",
description="This block hides a reply to a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterHideReplyBlock.Input,
output_schema=TwitterHideReplyBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"hide_reply": lambda *args, **kwargs: True},
)
@staticmethod
def hide_reply(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.hide_reply(id=tweet_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.hide_reply(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterUnhideReplyBlock(Block):
"""
Unhides a reply to a tweet
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.moderate.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet reply to unhide",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the operation was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="fcf9e4e4-a62f-11ef-9d85-57d3d06b616a",
description="This block unhides a reply to a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnhideReplyBlock.Input,
output_schema=TwitterUnhideReplyBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unhide_reply": lambda *args, **kwargs: True},
)
@staticmethod
def unhide_reply(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unhide_reply(id=tweet_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unhide_reply(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,576 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import (
TweetExpansionsBuilder,
UserExpansionsBuilder,
)
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterLikeTweetBlock(Block):
"""
Likes a tweet
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "like.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to like",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the operation was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="4d0b4c5c-a630-11ef-8e08-1b14c507b347",
description="This block likes a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterLikeTweetBlock.Input,
output_schema=TwitterLikeTweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"like_tweet": lambda *args, **kwargs: True},
)
@staticmethod
def like_tweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.like(tweet_id=tweet_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.like_tweet(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetLikingUsersBlock(Block):
"""
Gets information about users who liked a one of your tweet
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "like.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to get liking users for",
placeholder="Enter tweet ID",
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for getting next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
id: list[str] = SchemaField(description="All User IDs who liked the tweet")
username: list[str] = SchemaField(
description="All User usernames who liked the tweet"
)
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="34275000-a630-11ef-b01e-5f00d9077c08",
description="This block gets information about users who liked a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetLikingUsersBlock.Input,
output_schema=TwitterGetLikingUsersBlock.Output,
test_input={
"tweet_id": "1234567890",
"max_results": 1,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", ["1234567890"]),
("username", ["testuser"]),
("data", [{"id": "1234567890", "username": "testuser"}]),
],
test_mock={
"get_liking_users": lambda *args, **kwargs: (
["1234567890"],
["testuser"],
[{"id": "1234567890", "username": "testuser"}],
{},
{},
None,
)
},
)
@staticmethod
def get_liking_users(
credentials: TwitterCredentials,
tweet_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": tweet_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_liking_users(**params))
if not response.data and not response.meta:
raise Exception("No liking users found")
meta = {}
user_ids = []
usernames = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
user_ids = [str(user.id) for user in response.data]
usernames = [user.username for user in response.data]
return user_ids, usernames, data, included, meta, next_token
raise Exception("No liking users found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, usernames, data, included, meta, next_token = self.get_liking_users(
credentials,
input_data.tweet_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "id", ids
if usernames:
yield "username", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetLikedTweetsBlock(Block):
"""
Gets information about tweets liked by you
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "like.read", "offline.access"]
)
user_id: str = SchemaField(
description="ID of the user to get liked tweets for",
placeholder="Enter user ID",
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (5-100)",
placeholder="100",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for getting next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list[str] = SchemaField(description="All Tweet IDs")
texts: list[str] = SchemaField(description="All Tweet texts")
userIds: list[str] = SchemaField(
description="List of user ids that authored the tweets"
)
userNames: list[str] = SchemaField(
description="List of user names that authored the tweets"
)
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="292e7c78-a630-11ef-9f40-df5dffaca106",
description="This block gets information about tweets liked by a user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetLikedTweetsBlock.Input,
output_schema=TwitterGetLikedTweetsBlock.Output,
test_input={
"user_id": "1234567890",
"max_results": 2,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["12345", "67890"]),
("texts", ["Tweet 1", "Tweet 2"]),
("userIds", ["67890", "67891"]),
("userNames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "12345", "text": "Tweet 1"},
{"id": "67890", "text": "Tweet 2"},
],
),
],
test_mock={
"get_liked_tweets": lambda *args, **kwargs: (
["12345", "67890"],
["Tweet 1", "Tweet 2"],
["67890", "67891"],
["testuser1", "testuser2"],
[
{"id": "12345", "text": "Tweet 1"},
{"id": "67890", "text": "Tweet 2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_liked_tweets(
credentials: TwitterCredentials,
user_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": user_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_liked_tweets(**params))
if not response.data and not response.meta:
raise Exception("No liked tweets found")
meta = {}
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if "users" in response.includes:
user_ids = [str(user["id"]) for user in response.includes["users"]]
user_names = [
user["username"] for user in response.includes["users"]
]
return (
tweet_ids,
tweet_texts,
user_ids,
user_names,
data,
included,
meta,
next_token,
)
raise Exception("No liked tweets found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta, next_token = (
self.get_liked_tweets(
credentials,
input_data.user_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if user_ids:
yield "userIds", user_ids
if user_names:
yield "userNames", user_names
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterUnlikeTweetBlock(Block):
"""
Unlikes a tweet that was previously liked
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "like.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to unlike",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the operation was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="1ed5eab8-a630-11ef-8e21-cbbbc80cbb85",
description="This block unlikes a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnlikeTweetBlock.Input,
output_schema=TwitterUnlikeTweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unlike_tweet": lambda *args, **kwargs: True},
)
@staticmethod
def unlike_tweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unlike(tweet_id=tweet_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unlike_tweet(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,545 @@
from datetime import datetime
from typing import List, Literal, Optional, Union, cast
import tweepy
from pydantic import BaseModel
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import (
TweetDurationBuilder,
TweetExpansionsBuilder,
TweetPostBuilder,
TweetSearchBuilder,
)
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetReplySettingsFilter,
TweetTimeWindowInputs,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class Media(BaseModel):
discriminator: Literal["media"]
media_ids: Optional[List[str]] = None
media_tagged_user_ids: Optional[List[str]] = None
class DeepLink(BaseModel):
discriminator: Literal["deep_link"]
direct_message_deep_link: Optional[str] = None
class Poll(BaseModel):
discriminator: Literal["poll"]
poll_options: Optional[List[str]] = None
poll_duration_minutes: Optional[int] = None
class Place(BaseModel):
discriminator: Literal["place"]
place_id: Optional[str] = None
class Quote(BaseModel):
discriminator: Literal["quote"]
quote_tweet_id: Optional[str] = None
class TwitterPostTweetBlock(Block):
"""
Create a tweet on Twitter with the option to include one additional element such as a media, quote, or deep link.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.write", "users.read", "offline.access"]
)
tweet_text: str | None = SchemaField(
description="Text of the tweet to post",
placeholder="Enter your tweet",
default=None,
advanced=False,
)
for_super_followers_only: bool = SchemaField(
description="Tweet exclusively for Super Followers",
placeholder="Enter for super followers only",
advanced=True,
default=False,
)
attachment: Union[Media, DeepLink, Poll, Place, Quote] | None = SchemaField(
discriminator="discriminator",
description="Additional tweet data (media, deep link, poll, place or quote)",
advanced=True,
)
exclude_reply_user_ids: Optional[List[str]] = SchemaField(
description="User IDs to exclude from reply Tweet thread. [ex - 6253282]",
placeholder="Enter user IDs to exclude",
advanced=True,
default=None,
)
in_reply_to_tweet_id: Optional[str] = SchemaField(
description="Tweet ID being replied to. Please note that in_reply_to_tweet_id needs to be in the request if exclude_reply_user_ids is present",
default=None,
placeholder="Enter in reply to tweet ID",
advanced=True,
)
reply_settings: TweetReplySettingsFilter = SchemaField(
description="Who can reply to the Tweet (mentionedUsers or following)",
placeholder="Enter reply settings",
advanced=True,
default=TweetReplySettingsFilter(All_Users=True),
)
class Output(BlockSchema):
tweet_id: str = SchemaField(description="ID of the created tweet")
tweet_url: str = SchemaField(description="URL to the tweet")
error: str = SchemaField(
description="Error message if the tweet posting failed"
)
def __init__(self):
super().__init__(
id="7bb0048a-a630-11ef-aeb8-abc0dadb9b12",
description="This block posts a tweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterPostTweetBlock.Input,
output_schema=TwitterPostTweetBlock.Output,
test_input={
"tweet_text": "This is a test tweet.",
"credentials": TEST_CREDENTIALS_INPUT,
"attachment": {
"discriminator": "deep_link",
"direct_message_deep_link": "https://twitter.com/messages/compose",
},
"for_super_followers_only": False,
"exclude_reply_user_ids": [],
"in_reply_to_tweet_id": "",
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("tweet_id", "1234567890"),
("tweet_url", "https://twitter.com/user/status/1234567890"),
],
test_mock={
"post_tweet": lambda *args, **kwargs: (
"1234567890",
"https://twitter.com/user/status/1234567890",
)
},
)
def post_tweet(
self,
credentials: TwitterCredentials,
input_txt: str | None,
attachment: Union[Media, DeepLink, Poll, Place, Quote] | None,
for_super_followers_only: bool,
exclude_reply_user_ids: Optional[List[str]],
in_reply_to_tweet_id: Optional[str],
reply_settings: TweetReplySettingsFilter,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = (
TweetPostBuilder()
.add_text(input_txt)
.add_super_followers(for_super_followers_only)
.add_reply_settings(
exclude_reply_user_ids or [],
in_reply_to_tweet_id or "",
reply_settings,
)
)
if isinstance(attachment, Media):
params.add_media(
attachment.media_ids or [], attachment.media_tagged_user_ids or []
)
elif isinstance(attachment, DeepLink):
params.add_deep_link(attachment.direct_message_deep_link or "")
elif isinstance(attachment, Poll):
params.add_poll_options(attachment.poll_options or [])
params.add_poll_duration(attachment.poll_duration_minutes or 0)
elif isinstance(attachment, Place):
params.add_place(attachment.place_id or "")
elif isinstance(attachment, Quote):
params.add_quote(attachment.quote_tweet_id or "")
tweet = cast(Response, client.create_tweet(**params.build()))
if not tweet.data:
raise Exception("Failed to create tweet")
tweet_id = tweet.data["id"]
tweet_url = f"https://twitter.com/user/status/{tweet_id}"
return str(tweet_id), tweet_url
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
tweet_id, tweet_url = self.post_tweet(
credentials,
input_data.tweet_text,
input_data.attachment,
input_data.for_super_followers_only,
input_data.exclude_reply_user_ids,
input_data.in_reply_to_tweet_id,
input_data.reply_settings,
)
yield "tweet_id", tweet_id
yield "tweet_url", tweet_url
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterDeleteTweetBlock(Block):
"""
Deletes a tweet on Twitter using twitter Id
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to delete",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the tweet was successfully deleted"
)
error: str = SchemaField(
description="Error message if the tweet deletion failed"
)
def __init__(self):
super().__init__(
id="761babf0-a630-11ef-a03d-abceb082f58f",
description="This block deletes a tweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterDeleteTweetBlock.Input,
output_schema=TwitterDeleteTweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"delete_tweet": lambda *args, **kwargs: True},
)
@staticmethod
def delete_tweet(credentials: TwitterCredentials, tweet_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.delete_tweet(id=tweet_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
except Exception:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.delete_tweet(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterSearchRecentTweetsBlock(Block):
"""
Searches all public Tweets in Twitter history
"""
class Input(TweetExpansionInputs, TweetTimeWindowInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
query: str = SchemaField(
description="Search query (up to 1024 characters)",
placeholder="Enter search query",
)
max_results: int = SchemaField(
description="Maximum number of results per page (10-500)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination: str | None = SchemaField(
description="Token for pagination",
default="",
placeholder="Enter pagination token",
advanced=True,
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
tweet_ids: list[str] = SchemaField(description="All Tweet IDs")
tweet_texts: list[str] = SchemaField(description="All Tweet texts")
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="53e5cf8e-a630-11ef-ba85-df6d666fa5d5",
description="This block searches all public Tweets in Twitter history.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterSearchRecentTweetsBlock.Input,
output_schema=TwitterSearchRecentTweetsBlock.Output,
test_input={
"query": "from:twitterapi #twitterapi",
"credentials": TEST_CREDENTIALS_INPUT,
"max_results": 2,
"start_time": "2024-12-14T18:30:00.000Z",
"end_time": "2024-12-17T18:30:00.000Z",
"since_id": None,
"until_id": None,
"sort_order": None,
"pagination": None,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("tweet_ids", ["1373001119480344583", "1372627771717869568"]),
(
"tweet_texts",
[
"Looking to get started with the Twitter API but new to APIs in general?",
"Thanks to everyone who joined and made today a great session!",
],
),
(
"data",
[
{
"id": "1373001119480344583",
"text": "Looking to get started with the Twitter API but new to APIs in general?",
},
{
"id": "1372627771717869568",
"text": "Thanks to everyone who joined and made today a great session!",
},
],
),
],
test_mock={
"search_tweets": lambda *args, **kwargs: (
["1373001119480344583", "1372627771717869568"],
[
"Looking to get started with the Twitter API but new to APIs in general?",
"Thanks to everyone who joined and made today a great session!",
],
[
{
"id": "1373001119480344583",
"text": "Looking to get started with the Twitter API but new to APIs in general?",
},
{
"id": "1372627771717869568",
"text": "Thanks to everyone who joined and made today a great session!",
},
],
{},
{},
None,
)
},
)
@staticmethod
def search_tweets(
credentials: TwitterCredentials,
query: str,
max_results: int,
start_time: datetime | None,
end_time: datetime | None,
since_id: str | None,
until_id: str | None,
sort_order: str | None,
pagination: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
# Building common params
params = (
TweetSearchBuilder()
.add_query(query)
.add_pagination(max_results, pagination)
.build()
)
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
# Adding time window to params If required by the user
params = (
TweetDurationBuilder(params)
.add_start_time(start_time)
.add_end_time(end_time)
.add_since_id(since_id)
.add_until_id(until_id)
.add_sort_order(sort_order)
.build()
)
response = cast(Response, client.search_recent_tweets(**params))
if not response.data and not response.meta:
raise Exception("No tweets found")
meta = {}
tweet_ids = []
tweet_texts = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
return tweet_ids, tweet_texts, data, included, meta, next_token
raise Exception("No tweets found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, data, included, meta, next_token = self.search_tweets(
credentials,
input_data.query,
input_data.max_results,
input_data.start_time,
input_data.end_time,
input_data.since_id,
input_data.until_id,
input_data.sort_order,
input_data.pagination,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "tweet_ids", ids
if texts:
yield "tweet_texts", texts
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,222 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import TweetExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExcludesFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterGetQuoteTweetsBlock(Block):
"""
Gets quote tweets for a specified tweet ID
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to get quotes for",
placeholder="Enter tweet ID",
)
max_results: int | None = SchemaField(
description="Number of results to return (max 100)",
default=10,
advanced=True,
)
exclude: TweetExcludesFilter | None = SchemaField(
description="Types of tweets to exclude", advanced=True, default=None
)
pagination_token: str | None = SchemaField(
description="Token for pagination",
advanced=True,
default="",
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list = SchemaField(description="All Tweet IDs ")
texts: list = SchemaField(description="All Tweet texts")
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="9fbdd208-a630-11ef-9b97-ab7a3a695ca3",
description="This block gets quote tweets for a specific tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetQuoteTweetsBlock.Input,
output_schema=TwitterGetQuoteTweetsBlock.Output,
test_input={
"tweet_id": "1234567890",
"max_results": 2,
"pagination_token": None,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["12345", "67890"]),
("texts", ["Tweet 1", "Tweet 2"]),
(
"data",
[
{"id": "12345", "text": "Tweet 1"},
{"id": "67890", "text": "Tweet 2"},
],
),
],
test_mock={
"get_quote_tweets": lambda *args, **kwargs: (
["12345", "67890"],
["Tweet 1", "Tweet 2"],
[
{"id": "12345", "text": "Tweet 1"},
{"id": "67890", "text": "Tweet 2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_quote_tweets(
credentials: TwitterCredentials,
tweet_id: str,
max_results: int | None,
exclude: TweetExcludesFilter | None,
pagination_token: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": tweet_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"exclude": None if exclude == TweetExcludesFilter() else exclude,
"user_auth": False,
}
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_quote_tweets(**params))
meta = {}
tweet_ids = []
tweet_texts = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
return tweet_ids, tweet_texts, data, included, meta, next_token
raise Exception("No quote tweets found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, data, included, meta, next_token = self.get_quote_tweets(
credentials,
input_data.tweet_id,
input_data.max_results,
input_data.exclude,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,363 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import UserExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterRetweetBlock(Block):
"""
Retweets a tweet on Twitter
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to retweet",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the retweet was successful")
error: str = SchemaField(description="Error message if the retweet failed")
def __init__(self):
super().__init__(
id="bd7b8d3a-a630-11ef-be96-6f4aa4c3c4f4",
description="This block retweets a tweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterRetweetBlock.Input,
output_schema=TwitterRetweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"retweet": lambda *args, **kwargs: True},
)
@staticmethod
def retweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.retweet(
tweet_id=tweet_id,
user_auth=False,
)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.retweet(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterRemoveRetweetBlock(Block):
"""
Removes a retweet on Twitter
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "tweet.write", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to remove retweet",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the retweet was successfully removed"
)
error: str = SchemaField(description="Error message if the removal failed")
def __init__(self):
super().__init__(
id="b6e663f0-a630-11ef-a7f0-8b9b0c542ff8",
description="This block removes a retweet on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterRemoveRetweetBlock.Input,
output_schema=TwitterRemoveRetweetBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"remove_retweet": lambda *args, **kwargs: True},
)
@staticmethod
def remove_retweet(
credentials: TwitterCredentials,
tweet_id: str,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unretweet(
source_tweet_id=tweet_id,
user_auth=False,
)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.remove_retweet(
credentials,
input_data.tweet_id,
)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetRetweetersBlock(Block):
"""
Gets information about who has retweeted a tweet
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="ID of the tweet to get retweeters for",
placeholder="Enter tweet ID",
)
max_results: int | None = SchemaField(
description="Maximum number of results per page (1-100)",
default=10,
placeholder="Enter max results",
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination",
placeholder="Enter pagination token",
default="",
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list = SchemaField(description="List of user ids who retweeted")
names: list = SchemaField(description="List of user names who retweeted")
usernames: list = SchemaField(
description="List of user usernames who retweeted"
)
next_token: str = SchemaField(description="Token for next page of results")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="ad7aa6fa-a630-11ef-a6b0-e7ca640aa030",
description="This block gets information about who has retweeted a tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetRetweetersBlock.Input,
output_schema=TwitterGetRetweetersBlock.Output,
test_input={
"tweet_id": "1234567890",
"credentials": TEST_CREDENTIALS_INPUT,
"max_results": 1,
"pagination_token": "",
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["12345"]),
("names", ["Test User"]),
("usernames", ["testuser"]),
(
"data",
[{"id": "12345", "name": "Test User", "username": "testuser"}],
),
],
test_mock={
"get_retweeters": lambda *args, **kwargs: (
[{"id": "12345", "name": "Test User", "username": "testuser"}],
{},
{},
["12345"],
["Test User"],
["testuser"],
None,
)
},
)
@staticmethod
def get_retweeters(
credentials: TwitterCredentials,
tweet_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": tweet_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_retweeters(**params))
meta = {}
ids = []
names = []
usernames = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
ids = [str(user.id) for user in response.data]
names = [user.name for user in response.data]
usernames = [user.username for user in response.data]
return data, included, meta, ids, names, usernames, next_token
raise Exception("No retweeters found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, meta, ids, names, usernames, next_token = (
self.get_retweeters(
credentials,
input_data.tweet_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if names:
yield "names", names
if usernames:
yield "usernames", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,757 @@
from datetime import datetime
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import (
TweetDurationBuilder,
TweetExpansionsBuilder,
)
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetTimeWindowInputs,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterGetUserMentionsBlock(Block):
"""
Returns Tweets where a single user is mentioned, just put that user id
"""
class Input(TweetExpansionInputs, TweetTimeWindowInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
user_id: str = SchemaField(
description="Unique identifier of the user for whom to return Tweets mentioning the user",
placeholder="Enter user ID",
)
max_results: int | None = SchemaField(
description="Number of tweets to retrieve (5-100)",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination", default="", advanced=True
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list[str] = SchemaField(description="List of Tweet IDs")
texts: list[str] = SchemaField(description="All Tweet texts")
userIds: list[str] = SchemaField(
description="List of user ids that mentioned the user"
)
userNames: list[str] = SchemaField(
description="List of user names that mentioned the user"
)
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="e01c890c-a630-11ef-9e20-37da24888bd0",
description="This block retrieves Tweets mentioning a specific user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetUserMentionsBlock.Input,
output_schema=TwitterGetUserMentionsBlock.Output,
test_input={
"user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
"max_results": 2,
"start_time": "2024-12-14T18:30:00.000Z",
"end_time": "2024-12-17T18:30:00.000Z",
"since_id": "",
"until_id": "",
"sort_order": None,
"pagination_token": None,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1373001119480344583", "1372627771717869568"]),
("texts", ["Test mention 1", "Test mention 2"]),
("userIds", ["67890", "67891"]),
("userNames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "1373001119480344583", "text": "Test mention 1"},
{"id": "1372627771717869568", "text": "Test mention 2"},
],
),
],
test_mock={
"get_mentions": lambda *args, **kwargs: (
["1373001119480344583", "1372627771717869568"],
["Test mention 1", "Test mention 2"],
["67890", "67891"],
["testuser1", "testuser2"],
[
{"id": "1373001119480344583", "text": "Test mention 1"},
{"id": "1372627771717869568", "text": "Test mention 2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_mentions(
credentials: TwitterCredentials,
user_id: str,
max_results: int | None,
start_time: datetime | None,
end_time: datetime | None,
since_id: str | None,
until_id: str | None,
sort_order: str | None,
pagination: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": user_id,
"max_results": max_results,
"pagination_token": None if pagination == "" else pagination,
"user_auth": False,
}
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
# Adding time window to params If required by the user
params = (
TweetDurationBuilder(params)
.add_start_time(start_time)
.add_end_time(end_time)
.add_since_id(since_id)
.add_until_id(until_id)
.add_sort_order(sort_order)
.build()
)
response = cast(
Response,
client.get_users_mentions(**params),
)
if not response.data and not response.meta:
raise Exception("No tweets found")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
meta = response.meta or {}
next_token = meta.get("next_token", "")
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if "users" in included:
user_ids = [str(user["id"]) for user in included["users"]]
user_names = [user["username"] for user in included["users"]]
return (
tweet_ids,
tweet_texts,
user_ids,
user_names,
data,
included,
meta,
next_token,
)
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta, next_token = (
self.get_mentions(
credentials,
input_data.user_id,
input_data.max_results,
input_data.start_time,
input_data.end_time,
input_data.since_id,
input_data.until_id,
input_data.sort_order,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if user_ids:
yield "userIds", user_ids
if user_names:
yield "userNames", user_names
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetHomeTimelineBlock(Block):
"""
Returns a collection of the most recent Tweets and Retweets posted by you and users you follow
"""
class Input(TweetExpansionInputs, TweetTimeWindowInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
max_results: int | None = SchemaField(
description="Number of tweets to retrieve (5-100)",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination", default="", advanced=True
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list[str] = SchemaField(description="List of Tweet IDs")
texts: list[str] = SchemaField(description="All Tweet texts")
userIds: list[str] = SchemaField(
description="List of user ids that authored the tweets"
)
userNames: list[str] = SchemaField(
description="List of user names that authored the tweets"
)
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="d222a070-a630-11ef-a18a-3f52f76c6962",
description="This block retrieves the authenticated user's home timeline.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetHomeTimelineBlock.Input,
output_schema=TwitterGetHomeTimelineBlock.Output,
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"max_results": 2,
"start_time": "2024-12-14T18:30:00.000Z",
"end_time": "2024-12-17T18:30:00.000Z",
"since_id": None,
"until_id": None,
"sort_order": None,
"pagination_token": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1373001119480344583", "1372627771717869568"]),
("texts", ["Test tweet 1", "Test tweet 2"]),
("userIds", ["67890", "67891"]),
("userNames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "1373001119480344583", "text": "Test tweet 1"},
{"id": "1372627771717869568", "text": "Test tweet 2"},
],
),
],
test_mock={
"get_timeline": lambda *args, **kwargs: (
["1373001119480344583", "1372627771717869568"],
["Test tweet 1", "Test tweet 2"],
["67890", "67891"],
["testuser1", "testuser2"],
[
{"id": "1373001119480344583", "text": "Test tweet 1"},
{"id": "1372627771717869568", "text": "Test tweet 2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_timeline(
credentials: TwitterCredentials,
max_results: int | None,
start_time: datetime | None,
end_time: datetime | None,
since_id: str | None,
until_id: str | None,
sort_order: str | None,
pagination: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"max_results": max_results,
"pagination_token": None if pagination == "" else pagination,
"user_auth": False,
}
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
# Adding time window to params If required by the user
params = (
TweetDurationBuilder(params)
.add_start_time(start_time)
.add_end_time(end_time)
.add_since_id(since_id)
.add_until_id(until_id)
.add_sort_order(sort_order)
.build()
)
response = cast(
Response,
client.get_home_timeline(**params),
)
if not response.data and not response.meta:
raise Exception("No tweets found")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
meta = response.meta or {}
next_token = meta.get("next_token", "")
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if "users" in included:
user_ids = [str(user["id"]) for user in included["users"]]
user_names = [user["username"] for user in included["users"]]
return (
tweet_ids,
tweet_texts,
user_ids,
user_names,
data,
included,
meta,
next_token,
)
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta, next_token = (
self.get_timeline(
credentials,
input_data.max_results,
input_data.start_time,
input_data.end_time,
input_data.since_id,
input_data.until_id,
input_data.sort_order,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if user_ids:
yield "userIds", user_ids
if user_names:
yield "userNames", user_names
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetUserTweetsBlock(Block):
"""
Returns Tweets composed by a single user, specified by the requested user ID
"""
class Input(TweetExpansionInputs, TweetTimeWindowInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
user_id: str = SchemaField(
description="Unique identifier of the Twitter account (user ID) for whom to return results",
placeholder="Enter user ID",
)
max_results: int | None = SchemaField(
description="Number of tweets to retrieve (5-100)",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for pagination", default="", advanced=True
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list[str] = SchemaField(description="List of Tweet IDs")
texts: list[str] = SchemaField(description="All Tweet texts")
userIds: list[str] = SchemaField(
description="List of user ids that authored the tweets"
)
userNames: list[str] = SchemaField(
description="List of user names that authored the tweets"
)
next_token: str = SchemaField(description="Next token for pagination")
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(
description="Provides metadata such as pagination info (next_token) or result counts"
)
# error
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="c44c3ef2-a630-11ef-9ff7-eb7b5ea3a5cb",
description="This block retrieves Tweets composed by a single user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetUserTweetsBlock.Input,
output_schema=TwitterGetUserTweetsBlock.Output,
test_input={
"user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
"max_results": 2,
"start_time": "2024-12-14T18:30:00.000Z",
"end_time": "2024-12-17T18:30:00.000Z",
"since_id": None,
"until_id": None,
"sort_order": None,
"pagination_token": None,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1373001119480344583", "1372627771717869568"]),
("texts", ["Test tweet 1", "Test tweet 2"]),
("userIds", ["67890", "67891"]),
("userNames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "1373001119480344583", "text": "Test tweet 1"},
{"id": "1372627771717869568", "text": "Test tweet 2"},
],
),
],
test_mock={
"get_user_tweets": lambda *args, **kwargs: (
["1373001119480344583", "1372627771717869568"],
["Test tweet 1", "Test tweet 2"],
["67890", "67891"],
["testuser1", "testuser2"],
[
{"id": "1373001119480344583", "text": "Test tweet 1"},
{"id": "1372627771717869568", "text": "Test tweet 2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_user_tweets(
credentials: TwitterCredentials,
user_id: str,
max_results: int | None,
start_time: datetime | None,
end_time: datetime | None,
since_id: str | None,
until_id: str | None,
sort_order: str | None,
pagination: str | None,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": user_id,
"max_results": max_results,
"pagination_token": None if pagination == "" else pagination,
"user_auth": False,
}
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
# Adding time window to params If required by the user
params = (
TweetDurationBuilder(params)
.add_start_time(start_time)
.add_end_time(end_time)
.add_since_id(since_id)
.add_until_id(until_id)
.add_sort_order(sort_order)
.build()
)
response = cast(
Response,
client.get_users_tweets(**params),
)
if not response.data and not response.meta:
raise Exception("No tweets found")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
meta = response.meta or {}
next_token = meta.get("next_token", "")
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if "users" in included:
user_ids = [str(user["id"]) for user in included["users"]]
user_names = [user["username"] for user in included["users"]]
return (
tweet_ids,
tweet_texts,
user_ids,
user_names,
data,
included,
meta,
next_token,
)
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta, next_token = (
self.get_user_tweets(
credentials,
input_data.user_id,
input_data.max_results,
input_data.start_time,
input_data.end_time,
input_data.since_id,
input_data.until_id,
input_data.sort_order,
input_data.pagination_token,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if user_ids:
yield "userIds", user_ids
if user_names:
yield "userNames", user_names
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,361 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import TweetExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
ExpansionFilter,
TweetExpansionInputs,
TweetFieldsFilter,
TweetMediaFieldsFilter,
TweetPlaceFieldsFilter,
TweetPollFieldsFilter,
TweetUserFieldsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterGetTweetBlock(Block):
"""
Returns information about a single Tweet specified by the requested ID
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
tweet_id: str = SchemaField(
description="Unique identifier of the Tweet to request (ex: 1460323737035677698)",
placeholder="Enter tweet ID",
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
id: str = SchemaField(description="Tweet ID")
text: str = SchemaField(description="Tweet text")
userId: str = SchemaField(description="ID of the tweet author")
userName: str = SchemaField(description="Username of the tweet author")
# Complete Outputs for advanced use
data: dict = SchemaField(description="Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(description="Metadata about the tweet")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="f5155c3a-a630-11ef-9cc1-a309988b4d92",
description="This block retrieves information about a specific Tweet.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetTweetBlock.Input,
output_schema=TwitterGetTweetBlock.Output,
test_input={
"tweet_id": "1460323737035677698",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", "1460323737035677698"),
("text", "Test tweet content"),
("userId", "12345"),
("userName", "testuser"),
("data", {"id": "1460323737035677698", "text": "Test tweet content"}),
("included", {"users": [{"id": "12345", "username": "testuser"}]}),
("meta", {"result_count": 1}),
],
test_mock={
"get_tweet": lambda *args, **kwargs: (
{"id": "1460323737035677698", "text": "Test tweet content"},
{"users": [{"id": "12345", "username": "testuser"}]},
{"result_count": 1},
"12345",
"testuser",
)
},
)
@staticmethod
def get_tweet(
credentials: TwitterCredentials,
tweet_id: str,
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {"id": tweet_id, "user_auth": False}
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_tweet(**params))
meta = {}
user_id = ""
user_name = ""
if response.meta:
meta = response.meta
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_dict(response.data)
if included and "users" in included:
user_id = str(included["users"][0]["id"])
user_name = included["users"][0]["username"]
if response.data:
return data, included, meta, user_id, user_name
raise Exception("Tweet not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
tweet_data, included, meta, user_id, user_name = self.get_tweet(
credentials,
input_data.tweet_id,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
yield "id", str(tweet_data["id"])
yield "text", tweet_data["text"]
if user_id:
yield "userId", user_id
if user_name:
yield "userName", user_name
yield "data", tweet_data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetTweetsBlock(Block):
"""
Returns information about multiple Tweets specified by the requested IDs
"""
class Input(TweetExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["tweet.read", "users.read", "offline.access"]
)
tweet_ids: list[str] = SchemaField(
description="List of Tweet IDs to request (up to 100)",
placeholder="Enter tweet IDs",
)
class Output(BlockSchema):
# Common Outputs that user commonly uses
ids: list[str] = SchemaField(description="All Tweet IDs")
texts: list[str] = SchemaField(description="All Tweet texts")
userIds: list[str] = SchemaField(
description="List of user ids that authored the tweets"
)
userNames: list[str] = SchemaField(
description="List of user names that authored the tweets"
)
# Complete Outputs for advanced use
data: list[dict] = SchemaField(description="Complete Tweet data")
included: dict = SchemaField(
description="Additional data that you have requested (Optional) via Expansions field"
)
meta: dict = SchemaField(description="Metadata about the tweets")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="e7cc5420-a630-11ef-bfaf-13bdd8096a51",
description="This block retrieves information about multiple Tweets.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetTweetsBlock.Input,
output_schema=TwitterGetTweetsBlock.Output,
test_input={
"tweet_ids": ["1460323737035677698"],
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"media_fields": None,
"place_fields": None,
"poll_fields": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1460323737035677698"]),
("texts", ["Test tweet content"]),
("userIds", ["67890"]),
("userNames", ["testuser1"]),
("data", [{"id": "1460323737035677698", "text": "Test tweet content"}]),
("included", {"users": [{"id": "67890", "username": "testuser1"}]}),
("meta", {"result_count": 1}),
],
test_mock={
"get_tweets": lambda *args, **kwargs: (
["1460323737035677698"], # ids
["Test tweet content"], # texts
["67890"], # user_ids
["testuser1"], # user_names
[
{"id": "1460323737035677698", "text": "Test tweet content"}
], # data
{"users": [{"id": "67890", "username": "testuser1"}]}, # included
{"result_count": 1}, # meta
)
},
)
@staticmethod
def get_tweets(
credentials: TwitterCredentials,
tweet_ids: list[str],
expansions: ExpansionFilter | None,
media_fields: TweetMediaFieldsFilter | None,
place_fields: TweetPlaceFieldsFilter | None,
poll_fields: TweetPollFieldsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {"ids": tweet_ids, "user_auth": False}
# Adding expansions to params If required by the user
params = (
TweetExpansionsBuilder(params)
.add_expansions(expansions)
.add_media_fields(media_fields)
.add_place_fields(place_fields)
.add_poll_fields(poll_fields)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_tweets(**params))
if not response.data and not response.meta:
raise Exception("No tweets found")
tweet_ids = []
tweet_texts = []
user_ids = []
user_names = []
meta = {}
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
tweet_ids = [str(tweet.id) for tweet in response.data]
tweet_texts = [tweet.text for tweet in response.data]
if included and "users" in included:
for user in included["users"]:
user_ids.append(str(user["id"]))
user_names.append(user["username"])
if response.meta:
meta = response.meta
return tweet_ids, tweet_texts, user_ids, user_names, data, included, meta
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, texts, user_ids, user_names, data, included, meta = self.get_tweets(
credentials,
input_data.tweet_ids,
input_data.expansions,
input_data.media_fields,
input_data.place_fields,
input_data.poll_fields,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if texts:
yield "texts", texts
if user_ids:
yield "userIds", user_ids
if user_names:
yield "userNames", user_names
if data:
yield "data", data
if included:
yield "included", included
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,305 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import UserExpansionsBuilder
from backend.blocks.twitter._serializer import IncludesSerializer
from backend.blocks.twitter._types import (
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterUnblockUserBlock(Block):
"""
Unblock a specific user on Twitter. The request succeeds with no action when the user sends a request to a user they're not blocking or have already unblocked.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["block.write", "users.read", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to unblock",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the unblock was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="0f1b6570-a631-11ef-a3ea-230cbe9650dd",
description="This block unblocks a specific user on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnblockUserBlock.Input,
output_schema=TwitterUnblockUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unblock_user": lambda *args, **kwargs: True},
)
@staticmethod
def unblock_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unblock(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unblock_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetBlockedUsersBlock(Block):
"""
Get a list of users who are blocked by the authenticating user
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access", "block.read"]
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-1000, default 100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for retrieving next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
user_ids: list[str] = SchemaField(description="List of blocked user IDs")
usernames_: list[str] = SchemaField(description="List of blocked usernames")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
next_token: str = SchemaField(description="Next token for pagination")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="05f409e8-a631-11ef-ae89-93de863ee30d",
description="This block retrieves a list of users blocked by the authenticating user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetBlockedUsersBlock.Input,
output_schema=TwitterGetBlockedUsersBlock.Output,
test_input={
"max_results": 10,
"pagination_token": "",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("user_ids", ["12345", "67890"]),
("usernames_", ["testuser1", "testuser2"]),
],
test_mock={
"get_blocked_users": lambda *args, **kwargs: (
{}, # included
{}, # meta
["12345", "67890"], # user_ids
["testuser1", "testuser2"], # usernames
None, # next_token
)
},
)
@staticmethod
def get_blocked_users(
credentials: TwitterCredentials,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_blocked(**params))
meta = {}
user_ids = []
usernames = []
next_token = None
included = IncludesSerializer.serialize(response.includes)
if response.data:
for user in response.data:
user_ids.append(str(user.id))
usernames.append(user.username)
if response.meta:
meta = response.meta
if "next_token" in meta:
next_token = meta["next_token"]
if user_ids and usernames:
return included, meta, user_ids, usernames, next_token
else:
raise tweepy.TweepyException("No blocked users found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
included, meta, user_ids, usernames, next_token = self.get_blocked_users(
credentials,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if user_ids:
yield "user_ids", user_ids
if usernames:
yield "usernames_", usernames
if included:
yield "included", included
if meta:
yield "meta", meta
if next_token:
yield "next_token", next_token
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterBlockUserBlock(Block):
"""
Block a specific user on Twitter
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["block.write", "users.read", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to block",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(description="Whether the block was successful")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="fc258b94-a630-11ef-abc3-df050b75b816",
description="This block blocks a specific user on Twitter.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterBlockUserBlock.Input,
output_schema=TwitterBlockUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"block_user": lambda *args, **kwargs: True},
)
@staticmethod
def block_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.block(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.block_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,510 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import UserExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterUnfollowUserBlock(Block):
"""
Allows a user to unfollow another user specified by target user ID.
The request succeeds with no action when the authenticated user sends a request to a user they're not following or have already unfollowed.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "users.write", "follows.write", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to unfollow",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the unfollow action was successful"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="37e386a4-a631-11ef-b7bd-b78204b35fa4",
description="This block unfollows a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnfollowUserBlock.Input,
output_schema=TwitterUnfollowUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unfollow_user": lambda *args, **kwargs: True},
)
@staticmethod
def unfollow_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unfollow_user(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unfollow_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterFollowUserBlock(Block):
"""
Allows a user to follow another user specified by target user ID. If the target user does not have public Tweets,
this endpoint will send a follow request. The request succeeds with no action when the authenticated user sends a
request to a user they're already following, or if they're sending a follower request to a user that does not have
public Tweets.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "users.write", "follows.write", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to follow",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the follow action was successful"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="1aae6a5e-a631-11ef-a090-435900c6d429",
description="This block follows a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterFollowUserBlock.Input,
output_schema=TwitterFollowUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[("success", True)],
test_mock={"follow_user": lambda *args, **kwargs: True},
)
@staticmethod
def follow_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.follow_user(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.follow_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetFollowersBlock(Block):
"""
Retrieves a list of followers for a specified Twitter user ID
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access", "follows.read"]
)
target_user_id: str = SchemaField(
description="The user ID whose followers you would like to retrieve",
placeholder="Enter target user ID",
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-1000, default 100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for retrieving next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
ids: list[str] = SchemaField(description="List of follower user IDs")
usernames: list[str] = SchemaField(description="List of follower usernames")
next_token: str = SchemaField(description="Next token for pagination")
data: list[dict] = SchemaField(description="Complete user data for followers")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="30f66410-a631-11ef-8fe7-d7f888b4f43c",
description="This block retrieves followers of a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetFollowersBlock.Input,
output_schema=TwitterGetFollowersBlock.Output,
test_input={
"target_user_id": "12345",
"max_results": 1,
"pagination_token": "",
"expansions": None,
"tweet_fields": None,
"user_fields": None,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1234567890"]),
("usernames", ["testuser"]),
("data", [{"id": "1234567890", "username": "testuser"}]),
],
test_mock={
"get_followers": lambda *args, **kwargs: (
["1234567890"],
["testuser"],
[{"id": "1234567890", "username": "testuser"}],
{},
{},
None,
)
},
)
@staticmethod
def get_followers(
credentials: TwitterCredentials,
target_user_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": target_user_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_users_followers(**params))
meta = {}
follower_ids = []
follower_usernames = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
follower_ids = [str(user.id) for user in response.data]
follower_usernames = [user.username for user in response.data]
return (
follower_ids,
follower_usernames,
data,
included,
meta,
next_token,
)
raise Exception("Followers not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, usernames, data, includes, meta, next_token = self.get_followers(
credentials,
input_data.target_user_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if usernames:
yield "usernames", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if includes:
yield "includes", includes
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetFollowingBlock(Block):
"""
Retrieves a list of users that a specified Twitter user ID is following
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access", "follows.read"]
)
target_user_id: str = SchemaField(
description="The user ID whose following you would like to retrieve",
placeholder="Enter target user ID",
)
max_results: int | None = SchemaField(
description="Maximum number of results to return (1-1000, default 100)",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token for retrieving next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
ids: list[str] = SchemaField(description="List of following user IDs")
usernames: list[str] = SchemaField(description="List of following usernames")
next_token: str = SchemaField(description="Next token for pagination")
data: list[dict] = SchemaField(description="Complete user data for following")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="264a399c-a631-11ef-a97d-bfde4ca91173",
description="This block retrieves the users that a specified Twitter user is following.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetFollowingBlock.Input,
output_schema=TwitterGetFollowingBlock.Output,
test_input={
"target_user_id": "12345",
"max_results": 1,
"pagination_token": None,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["1234567890"]),
("usernames", ["testuser"]),
("data", [{"id": "1234567890", "username": "testuser"}]),
],
test_mock={
"get_following": lambda *args, **kwargs: (
["1234567890"],
["testuser"],
[{"id": "1234567890", "username": "testuser"}],
{},
{},
None,
)
},
)
@staticmethod
def get_following(
credentials: TwitterCredentials,
target_user_id: str,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": target_user_id,
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_users_following(**params))
meta = {}
following_ids = []
following_usernames = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
following_ids = [str(user.id) for user in response.data]
following_usernames = [user.username for user in response.data]
return (
following_ids,
following_usernames,
data,
included,
meta,
next_token,
)
raise Exception("Following not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, usernames, data, includes, meta, next_token = self.get_following(
credentials,
input_data.target_user_id,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if usernames:
yield "usernames", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if includes:
yield "includes", includes
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,328 @@
from typing import cast
import tweepy
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import UserExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class TwitterUnmuteUserBlock(Block):
"""
Allows a user to unmute another user specified by target user ID.
The request succeeds with no action when the user sends a request to a user they're not muting or have already unmuted.
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "users.write", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to unmute",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the unmute action was successful"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="40458504-a631-11ef-940b-eff92be55422",
description="This block unmutes a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterUnmuteUserBlock.Input,
output_schema=TwitterUnmuteUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"unmute_user": lambda *args, **kwargs: True},
)
@staticmethod
def unmute_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.unmute(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.unmute_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterGetMutedUsersBlock(Block):
"""
Returns a list of users who are muted by the authenticating user
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access"]
)
max_results: int | None = SchemaField(
description="The maximum number of results to be returned per page (1-1000). Default is 100.",
placeholder="Enter max results",
default=10,
advanced=True,
)
pagination_token: str | None = SchemaField(
description="Token to request next/previous page of results",
placeholder="Enter pagination token",
default="",
advanced=True,
)
class Output(BlockSchema):
ids: list[str] = SchemaField(description="List of muted user IDs")
usernames: list[str] = SchemaField(description="List of muted usernames")
next_token: str = SchemaField(description="Next token for pagination")
data: list[dict] = SchemaField(description="Complete user data for muted users")
includes: dict = SchemaField(
description="Additional data requested via expansions"
)
meta: dict = SchemaField(description="Metadata including pagination info")
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="475024da-a631-11ef-9ccd-f724b8b03cda",
description="This block gets a list of users muted by the authenticating user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetMutedUsersBlock.Input,
output_schema=TwitterGetMutedUsersBlock.Output,
test_input={
"max_results": 2,
"pagination_token": "",
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["12345", "67890"]),
("usernames", ["testuser1", "testuser2"]),
(
"data",
[
{"id": "12345", "username": "testuser1"},
{"id": "67890", "username": "testuser2"},
],
),
],
test_mock={
"get_muted_users": lambda *args, **kwargs: (
["12345", "67890"],
["testuser1", "testuser2"],
[
{"id": "12345", "username": "testuser1"},
{"id": "67890", "username": "testuser2"},
],
{},
{},
None,
)
},
)
@staticmethod
def get_muted_users(
credentials: TwitterCredentials,
max_results: int | None,
pagination_token: str | None,
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"max_results": max_results,
"pagination_token": (
None if pagination_token == "" else pagination_token
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_muted(**params))
meta = {}
user_ids = []
usernames = []
next_token = None
if response.meta:
meta = response.meta
next_token = meta.get("next_token")
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
user_ids = [str(item.id) for item in response.data]
usernames = [item.username for item in response.data]
return user_ids, usernames, data, included, meta, next_token
raise Exception("Muted users not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
ids, usernames, data, includes, meta, next_token = self.get_muted_users(
credentials,
input_data.max_results,
input_data.pagination_token,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if usernames:
yield "usernames", usernames
if next_token:
yield "next_token", next_token
if data:
yield "data", data
if includes:
yield "includes", includes
if meta:
yield "meta", meta
except Exception as e:
yield "error", handle_tweepy_exception(e)
class TwitterMuteUserBlock(Block):
"""
Allows a user to mute another user specified by target user ID
"""
class Input(BlockSchema):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "users.write", "offline.access"]
)
target_user_id: str = SchemaField(
description="The user ID of the user that you would like to mute",
placeholder="Enter target user ID",
)
class Output(BlockSchema):
success: bool = SchemaField(
description="Whether the mute action was successful"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="4d1919d0-a631-11ef-90ab-3b73af9ce8f1",
description="This block mutes a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterMuteUserBlock.Input,
output_schema=TwitterMuteUserBlock.Output,
test_input={
"target_user_id": "12345",
"credentials": TEST_CREDENTIALS_INPUT,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("success", True),
],
test_mock={"mute_user": lambda *args, **kwargs: True},
)
@staticmethod
def mute_user(credentials: TwitterCredentials, target_user_id: str):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
client.mute(target_user_id=target_user_id, user_auth=False)
return True
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
success = self.mute_user(credentials, input_data.target_user_id)
yield "success", success
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -0,0 +1,383 @@
from typing import Literal, Union, cast
import tweepy
from pydantic import BaseModel
from tweepy.client import Response
from backend.blocks.twitter._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
TwitterCredentials,
TwitterCredentialsField,
TwitterCredentialsInput,
)
from backend.blocks.twitter._builders import UserExpansionsBuilder
from backend.blocks.twitter._serializer import (
IncludesSerializer,
ResponseDataSerializer,
)
from backend.blocks.twitter._types import (
TweetFieldsFilter,
TweetUserFieldsFilter,
UserExpansionInputs,
UserExpansionsFilter,
)
from backend.blocks.twitter.tweepy_exceptions import handle_tweepy_exception
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class UserId(BaseModel):
discriminator: Literal["user_id"]
user_id: str = SchemaField(description="The ID of the user to lookup", default="")
class Username(BaseModel):
discriminator: Literal["username"]
username: str = SchemaField(
description="The Twitter username (handle) of the user", default=""
)
class TwitterGetUserBlock(Block):
"""
Gets information about a single Twitter user specified by ID or username
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access"]
)
identifier: Union[UserId, Username] = SchemaField(
discriminator="discriminator",
description="Choose whether to identify the user by their unique Twitter ID or by their username",
advanced=False,
)
class Output(BlockSchema):
# Common outputs
id: str = SchemaField(description="User ID")
username_: str = SchemaField(description="User username")
name_: str = SchemaField(description="User name")
# Complete outputs
data: dict = SchemaField(description="Complete user data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="5446db8e-a631-11ef-812a-cf315d373ee9",
description="This block retrieves information about a specified Twitter user.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetUserBlock.Input,
output_schema=TwitterGetUserBlock.Output,
test_input={
"identifier": {"discriminator": "username", "username": "twitter"},
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("id", "783214"),
("username_", "twitter"),
("name_", "Twitter"),
(
"data",
{
"user": {
"id": "783214",
"username": "twitter",
"name": "Twitter",
}
},
),
],
test_mock={
"get_user": lambda *args, **kwargs: (
{
"user": {
"id": "783214",
"username": "twitter",
"name": "Twitter",
}
},
{},
"twitter",
"783214",
"Twitter",
)
},
)
@staticmethod
def get_user(
credentials: TwitterCredentials,
identifier: Union[UserId, Username],
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"id": identifier.user_id if isinstance(identifier, UserId) else None,
"username": (
identifier.username if isinstance(identifier, Username) else None
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_user(**params))
username = ""
id = ""
name = ""
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_dict(response.data)
if response.data:
username = response.data.username
id = str(response.data.id)
name = response.data.name
if username and id:
return data, included, username, id, name
else:
raise tweepy.TweepyException("User not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, username, id, name = self.get_user(
credentials,
input_data.identifier,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if id:
yield "id", id
if username:
yield "username_", username
if name:
yield "name_", name
if data:
yield "data", data
if included:
yield "included", included
except Exception as e:
yield "error", handle_tweepy_exception(e)
class UserIdList(BaseModel):
discriminator: Literal["user_id_list"]
user_ids: list[str] = SchemaField(
description="List of user IDs to lookup (max 100)",
placeholder="Enter user IDs",
default=[],
advanced=False,
)
class UsernameList(BaseModel):
discriminator: Literal["username_list"]
usernames: list[str] = SchemaField(
description="List of Twitter usernames/handles to lookup (max 100)",
placeholder="Enter usernames",
default=[],
advanced=False,
)
class TwitterGetUsersBlock(Block):
"""
Gets information about multiple Twitter users specified by IDs or usernames
"""
class Input(UserExpansionInputs):
credentials: TwitterCredentialsInput = TwitterCredentialsField(
["users.read", "offline.access"]
)
identifier: Union[UserIdList, UsernameList] = SchemaField(
discriminator="discriminator",
description="Choose whether to identify users by their unique Twitter IDs or by their usernames",
advanced=False,
)
class Output(BlockSchema):
# Common outputs
ids: list[str] = SchemaField(description="User IDs")
usernames_: list[str] = SchemaField(description="User usernames")
names_: list[str] = SchemaField(description="User names")
# Complete outputs
data: list[dict] = SchemaField(description="Complete users data")
included: dict = SchemaField(
description="Additional data requested via expansions"
)
error: str = SchemaField(description="Error message if the request failed")
def __init__(self):
super().__init__(
id="5abc857c-a631-11ef-8cfc-f7b79354f7a1",
description="This block retrieves information about multiple Twitter users.",
categories={BlockCategory.SOCIAL},
input_schema=TwitterGetUsersBlock.Input,
output_schema=TwitterGetUsersBlock.Output,
test_input={
"identifier": {
"discriminator": "username_list",
"usernames": ["twitter", "twitterdev"],
},
"credentials": TEST_CREDENTIALS_INPUT,
"expansions": None,
"tweet_fields": None,
"user_fields": None,
},
test_credentials=TEST_CREDENTIALS,
test_output=[
("ids", ["783214", "2244994945"]),
("usernames_", ["twitter", "twitterdev"]),
("names_", ["Twitter", "Twitter Dev"]),
(
"data",
[
{"id": "783214", "username": "twitter", "name": "Twitter"},
{
"id": "2244994945",
"username": "twitterdev",
"name": "Twitter Dev",
},
],
),
],
test_mock={
"get_users": lambda *args, **kwargs: (
[
{"id": "783214", "username": "twitter", "name": "Twitter"},
{
"id": "2244994945",
"username": "twitterdev",
"name": "Twitter Dev",
},
],
{},
["twitter", "twitterdev"],
["783214", "2244994945"],
["Twitter", "Twitter Dev"],
)
},
)
@staticmethod
def get_users(
credentials: TwitterCredentials,
identifier: Union[UserIdList, UsernameList],
expansions: UserExpansionsFilter | None,
tweet_fields: TweetFieldsFilter | None,
user_fields: TweetUserFieldsFilter | None,
):
try:
client = tweepy.Client(
bearer_token=credentials.access_token.get_secret_value()
)
params = {
"ids": (
",".join(identifier.user_ids)
if isinstance(identifier, UserIdList)
else None
),
"usernames": (
",".join(identifier.usernames)
if isinstance(identifier, UsernameList)
else None
),
"user_auth": False,
}
params = (
UserExpansionsBuilder(params)
.add_expansions(expansions)
.add_tweet_fields(tweet_fields)
.add_user_fields(user_fields)
.build()
)
response = cast(Response, client.get_users(**params))
usernames = []
ids = []
names = []
included = IncludesSerializer.serialize(response.includes)
data = ResponseDataSerializer.serialize_list(response.data)
if response.data:
for user in response.data:
usernames.append(user.username)
ids.append(str(user.id))
names.append(user.name)
if usernames and ids:
return data, included, usernames, ids, names
else:
raise tweepy.TweepyException("Users not found")
except tweepy.TweepyException:
raise
def run(
self,
input_data: Input,
*,
credentials: TwitterCredentials,
**kwargs,
) -> BlockOutput:
try:
data, included, usernames, ids, names = self.get_users(
credentials,
input_data.identifier,
input_data.expansions,
input_data.tweet_fields,
input_data.user_fields,
)
if ids:
yield "ids", ids
if usernames:
yield "usernames_", usernames
if names:
yield "names_", names
if data:
yield "data", data
if included:
yield "included", included
except Exception as e:
yield "error", handle_tweepy_exception(e)

View File

@@ -51,6 +51,7 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.LLAMA3_1_405B: 1,
LlmModel.LLAMA3_1_70B: 1,
LlmModel.LLAMA3_1_8B: 1,
LlmModel.OLLAMA_LLAMA3_2: 1,
LlmModel.OLLAMA_LLAMA3_8B: 1,
LlmModel.OLLAMA_LLAMA3_405B: 1,
LlmModel.OLLAMA_DOLPHIN: 1,

View File

@@ -226,6 +226,7 @@ class OAuthState(BaseModel):
token: str
provider: str
expires_at: int
code_verifier: Optional[str] = None
"""Unix timestamp (seconds) indicating when this OAuth state expires"""
scopes: list[str]

View File

@@ -1,6 +1,8 @@
import base64
import hashlib
import secrets
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from pydantic import SecretStr
@@ -91,6 +93,34 @@ open_router_credentials = APIKeyCredentials(
title="Use Credits for Open Router",
expires_at=None,
)
fal_credentials = APIKeyCredentials(
id="6c0f5bd0-9008-4638-9d79-4b40b631803e",
provider="fal",
api_key=SecretStr(settings.secrets.fal_api_key),
title="Use Credits for FAL",
expires_at=None,
)
exa_credentials = APIKeyCredentials(
id="96153e04-9c6c-4486-895f-5bb683b1ecec",
provider="exa",
api_key=SecretStr(settings.secrets.exa_api_key),
title="Use Credits for Exa search",
expires_at=None,
)
e2b_credentials = APIKeyCredentials(
id="78d19fd7-4d59-4a16-8277-3ce310acf2b7",
provider="e2b",
api_key=SecretStr(settings.secrets.e2b_api_key),
title="Use Credits for E2B",
expires_at=None,
)
nvidia_credentials = APIKeyCredentials(
id="96b83908-2789-4dec-9968-18f0ece4ceb3",
provider="nvidia",
api_key=SecretStr(settings.secrets.nvidia_api_key),
title="Use Credits for Nvidia",
expires_at=None,
)
DEFAULT_CREDENTIALS = [
@@ -104,6 +134,10 @@ DEFAULT_CREDENTIALS = [
jina_credentials,
unreal_credentials,
open_router_credentials,
fal_credentials,
exa_credentials,
e2b_credentials,
nvidia_credentials,
]
@@ -155,6 +189,14 @@ class IntegrationCredentialsStore:
all_credentials.append(unreal_credentials)
if settings.secrets.open_router_api_key:
all_credentials.append(open_router_credentials)
if settings.secrets.fal_api_key:
all_credentials.append(fal_credentials)
if settings.secrets.exa_api_key:
all_credentials.append(exa_credentials)
if settings.secrets.e2b_api_key:
all_credentials.append(e2b_credentials)
if settings.secrets.nvidia_api_key:
all_credentials.append(nvidia_credentials)
return all_credentials
def get_creds_by_id(self, user_id: str, credentials_id: str) -> Credentials | None:
@@ -210,18 +252,24 @@ class IntegrationCredentialsStore:
]
self._set_user_integration_creds(user_id, filtered_credentials)
def store_state_token(self, user_id: str, provider: str, scopes: list[str]) -> str:
def store_state_token(
self, user_id: str, provider: str, scopes: list[str], use_pkce: bool = False
) -> tuple[str, str]:
token = secrets.token_urlsafe(32)
expires_at = datetime.now(timezone.utc) + timedelta(minutes=10)
(code_challenge, code_verifier) = self._generate_code_challenge()
state = OAuthState(
token=token,
provider=provider,
code_verifier=code_verifier,
expires_at=int(expires_at.timestamp()),
scopes=scopes,
)
with self.locked_user_integrations(user_id):
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
oauth_states.append(state)
@@ -231,39 +279,21 @@ class IntegrationCredentialsStore:
user_id=user_id, data=user_integrations
)
return token
return token, code_challenge
def get_any_valid_scopes_from_state_token(
def _generate_code_challenge(self) -> tuple[str, str]:
"""
Generate code challenge using SHA256 from the code verifier.
Currently only SHA256 is supported.(In future if we want to support more methods we can add them here)
"""
code_verifier = secrets.token_urlsafe(128)
sha256_hash = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(sha256_hash).decode("utf-8")
return code_challenge.replace("=", ""), code_verifier
def verify_state_token(
self, user_id: str, token: str, provider: str
) -> list[str]:
"""
Get the valid scopes from the OAuth state token. This will return any valid scopes
from any OAuth state token for the given provider. If no valid scopes are found,
an empty list is returned. DO NOT RELY ON THIS TOKEN TO AUTHENTICATE A USER, AS IT
IS TO CHECK IF THE USER HAS GIVEN PERMISSIONS TO THE APPLICATION BEFORE EXCHANGING
THE CODE FOR TOKENS.
"""
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
now = datetime.now(timezone.utc)
valid_state = next(
(
state
for state in oauth_states
if state.token == token
and state.provider == provider
and state.expires_at > now.timestamp()
),
None,
)
if valid_state:
return valid_state.scopes
return []
def verify_state_token(self, user_id: str, token: str, provider: str) -> bool:
) -> Optional[OAuthState]:
with self.locked_user_integrations(user_id):
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
@@ -285,9 +315,9 @@ class IntegrationCredentialsStore:
oauth_states.remove(valid_state)
user_integrations.oauth_states = oauth_states
self.db_manager.update_user_integrations(user_id, user_integrations)
return True
return valid_state
return False
return None
def _set_user_integration_creds(
self, user_id: str, credentials: list[Credentials]

View File

@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from .github import GitHubOAuthHandler
from .google import GoogleOAuthHandler
from .notion import NotionOAuthHandler
from .twitter import TwitterOAuthHandler
if TYPE_CHECKING:
from ..providers import ProviderName
@@ -15,6 +16,7 @@ HANDLERS_BY_NAME: dict["ProviderName", type["BaseOAuthHandler"]] = {
GitHubOAuthHandler,
GoogleOAuthHandler,
NotionOAuthHandler,
TwitterOAuthHandler,
]
}
# --8<-- [end:HANDLERS_BY_NAMEExample]

View File

@@ -1,7 +1,7 @@
import logging
import time
from abc import ABC, abstractmethod
from typing import ClassVar
from typing import ClassVar, Optional
from backend.data.model import OAuth2Credentials
from backend.integrations.providers import ProviderName
@@ -23,7 +23,9 @@ class BaseOAuthHandler(ABC):
@abstractmethod
# --8<-- [start:BaseOAuthHandler3]
def get_login_url(self, scopes: list[str], state: str) -> str:
def get_login_url(
self, scopes: list[str], state: str, code_challenge: Optional[str]
) -> str:
# --8<-- [end:BaseOAuthHandler3]
"""Constructs a login URL that the user can be redirected to"""
...
@@ -31,7 +33,7 @@ class BaseOAuthHandler(ABC):
@abstractmethod
# --8<-- [start:BaseOAuthHandler4]
def exchange_code_for_tokens(
self, code: str, scopes: list[str]
self, code: str, scopes: list[str], code_verifier: Optional[str]
) -> OAuth2Credentials:
# --8<-- [end:BaseOAuthHandler4]
"""Exchanges the acquired authorization code from login for a set of tokens"""

View File

@@ -34,7 +34,9 @@ class GitHubOAuthHandler(BaseOAuthHandler):
self.token_url = "https://github.com/login/oauth/access_token"
self.revoke_url = "https://api.github.com/applications/{client_id}/token"
def get_login_url(self, scopes: list[str], state: str) -> str:
def get_login_url(
self, scopes: list[str], state: str, code_challenge: Optional[str]
) -> str:
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
@@ -44,7 +46,7 @@ class GitHubOAuthHandler(BaseOAuthHandler):
return f"{self.auth_base_url}?{urlencode(params)}"
def exchange_code_for_tokens(
self, code: str, scopes: list[str]
self, code: str, scopes: list[str], code_verifier: Optional[str]
) -> OAuth2Credentials:
return self._request_tokens({"code": code, "redirect_uri": self.redirect_uri})

View File

@@ -1,4 +1,5 @@
import logging
from typing import Optional
from google.auth.external_account_authorized_user import (
Credentials as ExternalAccountCredentials,
@@ -38,7 +39,9 @@ class GoogleOAuthHandler(BaseOAuthHandler):
self.token_uri = "https://oauth2.googleapis.com/token"
self.revoke_uri = "https://oauth2.googleapis.com/revoke"
def get_login_url(self, scopes: list[str], state: str) -> str:
def get_login_url(
self, scopes: list[str], state: str, code_challenge: Optional[str]
) -> str:
all_scopes = list(set(scopes + self.DEFAULT_SCOPES))
logger.debug(f"Setting up OAuth flow with scopes: {all_scopes}")
flow = self._setup_oauth_flow(all_scopes)
@@ -52,7 +55,7 @@ class GoogleOAuthHandler(BaseOAuthHandler):
return authorization_url
def exchange_code_for_tokens(
self, code: str, scopes: list[str]
self, code: str, scopes: list[str], code_verifier: Optional[str]
) -> OAuth2Credentials:
logger.debug(f"Exchanging code for tokens with scopes: {scopes}")

View File

@@ -1,4 +1,5 @@
from base64 import b64encode
from typing import Optional
from urllib.parse import urlencode
from backend.data.model import OAuth2Credentials
@@ -26,7 +27,9 @@ class NotionOAuthHandler(BaseOAuthHandler):
self.auth_base_url = "https://api.notion.com/v1/oauth/authorize"
self.token_url = "https://api.notion.com/v1/oauth/token"
def get_login_url(self, scopes: list[str], state: str) -> str:
def get_login_url(
self, scopes: list[str], state: str, code_challenge: Optional[str]
) -> str:
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
@@ -37,7 +40,7 @@ class NotionOAuthHandler(BaseOAuthHandler):
return f"{self.auth_base_url}?{urlencode(params)}"
def exchange_code_for_tokens(
self, code: str, scopes: list[str]
self, code: str, scopes: list[str], code_verifier: Optional[str]
) -> OAuth2Credentials:
request_body = {
"grant_type": "authorization_code",

View File

@@ -0,0 +1,171 @@
import time
import urllib.parse
from typing import ClassVar, Optional
import requests
from backend.data.model import OAuth2Credentials, ProviderName
from backend.integrations.oauth.base import BaseOAuthHandler
class TwitterOAuthHandler(BaseOAuthHandler):
PROVIDER_NAME = ProviderName.TWITTER
DEFAULT_SCOPES: ClassVar[list[str]] = [
"tweet.read",
"tweet.write",
"tweet.moderate.write",
"users.read",
"follows.read",
"follows.write",
"offline.access",
"space.read",
"mute.read",
"mute.write",
"like.read",
"like.write",
"list.read",
"list.write",
"block.read",
"block.write",
"bookmark.read",
"bookmark.write",
]
AUTHORIZE_URL = "https://twitter.com/i/oauth2/authorize"
TOKEN_URL = "https://api.x.com/2/oauth2/token"
USERNAME_URL = "https://api.x.com/2/users/me"
REVOKE_URL = "https://api.x.com/2/oauth2/revoke"
def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_login_url(
self, scopes: list[str], state: str, code_challenge: Optional[str]
) -> str:
"""Generate Twitter OAuth 2.0 authorization URL"""
# scopes = self.handle_default_scopes(scopes)
if code_challenge is None:
raise ValueError("code_challenge is required for Twitter OAuth")
params = {
"response_type": "code",
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"scope": " ".join(self.DEFAULT_SCOPES),
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
}
return f"{self.AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
def exchange_code_for_tokens(
self, code: str, scopes: list[str], code_verifier: Optional[str]
) -> OAuth2Credentials:
"""Exchange authorization code for access tokens"""
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"code": code,
"grant_type": "authorization_code",
"redirect_uri": self.redirect_uri,
"code_verifier": code_verifier,
}
auth = (self.client_id, self.client_secret)
response = requests.post(self.TOKEN_URL, headers=headers, data=data, auth=auth)
response.raise_for_status()
tokens = response.json()
username = self._get_username(tokens["access_token"])
return OAuth2Credentials(
provider=self.PROVIDER_NAME,
title=None,
username=username,
access_token=tokens["access_token"],
refresh_token=tokens.get("refresh_token"),
access_token_expires_at=int(time.time()) + tokens["expires_in"],
refresh_token_expires_at=None,
scopes=scopes,
)
def _get_username(self, access_token: str) -> str:
"""Get the username from the access token"""
headers = {"Authorization": f"Bearer {access_token}"}
params = {"user.fields": "username"}
response = requests.get(
f"{self.USERNAME_URL}?{urllib.parse.urlencode(params)}", headers=headers
)
response.raise_for_status()
return response.json()["data"]["username"]
def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials:
"""Refresh access tokens using refresh token"""
if not credentials.refresh_token:
raise ValueError("No refresh token available")
header = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "refresh_token",
"refresh_token": credentials.refresh_token.get_secret_value(),
}
auth = (self.client_id, self.client_secret)
response = requests.post(self.TOKEN_URL, headers=header, data=data, auth=auth)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print("HTTP Error:", e)
print("Response Content:", response.text)
raise
tokens = response.json()
username = self._get_username(tokens["access_token"])
return OAuth2Credentials(
id=credentials.id,
provider=self.PROVIDER_NAME,
title=None,
username=username,
access_token=tokens["access_token"],
refresh_token=tokens["refresh_token"],
access_token_expires_at=int(time.time()) + tokens["expires_in"],
scopes=credentials.scopes,
refresh_token_expires_at=None,
)
def revoke_tokens(self, credentials: OAuth2Credentials) -> bool:
"""Revoke the access token"""
header = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"token": credentials.access_token.get_secret_value(),
"token_type_hint": "access_token",
}
auth = (self.client_id, self.client_secret)
response = requests.post(self.REVOKE_URL, headers=header, data=data, auth=auth)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print("HTTP Error:", e)
print("Response Content:", response.text)
raise
return response.status_code == 200

View File

@@ -28,5 +28,6 @@ class ProviderName(str, Enum):
REPLICATE = "replicate"
REVID = "revid"
SLANT3D = "slant3d"
TWITTER = "twitter"
UNREAL_SPEECH = "unreal_speech"
# --8<-- [end:ProviderName]

View File

@@ -60,11 +60,12 @@ def login(
requested_scopes = scopes.split(",") if scopes else []
# Generate and store a secure random state token along with the scopes
state_token = creds_manager.store.store_state_token(
state_token, code_challenge = creds_manager.store.store_state_token(
user_id, provider, requested_scopes
)
login_url = handler.get_login_url(requested_scopes, state_token)
login_url = handler.get_login_url(
requested_scopes, state_token, code_challenge=code_challenge
)
return LoginResponse(login_url=login_url, state_token=state_token)
@@ -92,19 +93,21 @@ def callback(
handler = _get_provider_oauth_handler(request, provider)
# Verify the state token
if not creds_manager.store.verify_state_token(user_id, state_token, provider):
valid_state = creds_manager.store.verify_state_token(user_id, state_token, provider)
if not valid_state:
logger.warning(f"Invalid or expired state token for user {user_id}")
raise HTTPException(status_code=400, detail="Invalid or expired state token")
try:
scopes = creds_manager.store.get_any_valid_scopes_from_state_token(
user_id, state_token, provider
)
scopes = valid_state.scopes
logger.debug(f"Retrieved scopes from state token: {scopes}")
scopes = handler.handle_default_scopes(scopes)
credentials = handler.exchange_code_for_tokens(code, scopes)
credentials = handler.exchange_code_for_tokens(
code, scopes, valid_state.code_verifier
)
logger.debug(f"Received credentials with final scopes: {credentials.scopes}")
# Check if the granted scopes are sufficient for the requested scopes

View File

@@ -38,7 +38,7 @@ def create_test_graph() -> graph.Graph:
graph.Node(
block_id=FillTextTemplateBlock().id,
input_default={
"format": "{a}, {b}{c}",
"format": "{{a}}, {{b}}{{c}}",
"values_#_c": "!!!",
},
),

View File

@@ -264,6 +264,10 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
notion_client_secret: str = Field(
default="", description="Notion OAuth client secret"
)
twitter_client_id: str = Field(default="", description="Twitter/X OAuth client ID")
twitter_client_secret: str = Field(
default="", description="Twitter/X OAuth client secret"
)
openai_api_key: str = Field(default="", description="OpenAI API key")
anthropic_api_key: str = Field(default="", description="Anthropic API key")
@@ -300,7 +304,10 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
jina_api_key: str = Field(default="", description="Jina API Key")
unreal_speech_api_key: str = Field(default="", description="Unreal Speech API Key")
fal_key: str = Field(default="", description="FAL API key")
fal_api_key: str = Field(default="", description="FAL API key")
exa_api_key: str = Field(default="", description="Exa API key")
e2b_api_key: str = Field(default="", description="E2B API key")
nvidia_api_key: str = Field(default="", description="Nvidia API key")
# Add more secret fields as needed

View File

@@ -1,5 +1,3 @@
import re
from jinja2 import BaseLoader
from jinja2.sandbox import SandboxedEnvironment
@@ -15,8 +13,5 @@ class TextFormatter:
self.env.globals.clear()
def format_string(self, template_str: str, values=None, **kwargs) -> str:
# For python.format compatibility: replace all {...} with {{..}}.
# But avoid replacing {{...}} to {{{...}}}.
template_str = re.sub(r"(?<!{){[ a-zA-Z0-9_]+}", r"{\g<0>}", template_str)
template = self.env.from_string(template_str)
return template.render(values or {}, **kwargs)

View File

@@ -0,0 +1,86 @@
/*
Warnings:
- You are about replace a single brace string input format for the following blocks:
- AgentOutputBlock
- FillTextTemplateBlock
- AITextGeneratorBlock
- AIStructuredResponseGeneratorBlock
with a double brace format.
- This migration can be slow for a large updated AgentNode tables.
*/
BEGIN;
SET LOCAL statement_timeout = '10min';
WITH to_update AS (
SELECT
"id",
"agentBlockId",
"constantInput"::jsonb AS j
FROM "AgentNode"
WHERE
"agentBlockId" IN (
'363ae599-353e-4804-937e-b2ee3cef3da4', -- AgentOutputBlock
'db7d8f02-2f44-4c55-ab7a-eae0941f0c30', -- FillTextTemplateBlock
'1f292d4a-41a4-4977-9684-7c8d560b9f91', -- AITextGeneratorBlock
'ed55ac19-356e-4243-a6cb-bc599e9b716f' -- AIStructuredResponseGeneratorBlock
)
AND (
"constantInput"::jsonb->>'format' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
OR "constantInput"::jsonb->>'prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
OR "constantInput"::jsonb->>'sys_prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
)
),
updated_rows AS (
SELECT
"id",
"agentBlockId",
(
j
-- Update "format" if it has a single-brace placeholder
|| CASE WHEN j->>'format' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
THEN jsonb_build_object(
'format',
regexp_replace(
j->>'format',
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
'{{\1}}',
'g'
)
)
ELSE '{}'::jsonb
END
-- Update "prompt" if it has a single-brace placeholder
|| CASE WHEN j->>'prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
THEN jsonb_build_object(
'prompt',
regexp_replace(
j->>'prompt',
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
'{{\1}}',
'g'
)
)
ELSE '{}'::jsonb
END
-- Update "sys_prompt" if it has a single-brace placeholder
|| CASE WHEN j->>'sys_prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
THEN jsonb_build_object(
'sys_prompt',
regexp_replace(
j->>'sys_prompt',
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
'{{\1}}',
'g'
)
)
ELSE '{}'::jsonb
END
)::text AS "newConstantInput"
FROM to_update
)
UPDATE "AgentNode" AS an
SET "constantInput" = ur."newConstantInput"
FROM updated_rows ur
WHERE an."id" = ur."id";
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@@ -39,8 +39,9 @@ python-dotenv = "^1.0.1"
redis = "^5.2.0"
sentry-sdk = "2.19.2"
strenum = "^0.4.9"
supabase = "^2.10.0"
supabase = "2.11.0"
tenacity = "^9.0.0"
tweepy = "^4.14.0"
uvicorn = { extras = ["standard"], version = "^0.34.0" }
websockets = "^13.1"
youtube-transcript-api = "^0.6.2"

View File

@@ -102,7 +102,7 @@ async def assert_sample_graph_executions(
assert exec.graph_exec_id == graph_exec_id
assert exec.output_data == {"output": ["Hello, World!!!"]}
assert exec.input_data == {
"format": "{a}, {b}{c}",
"format": "{{a}}, {{b}}{{c}}",
"values": {"a": "Hello", "b": "World", "c": "!!!"},
"values_#_a": "Hello",
"values_#_b": "World",

View File

@@ -253,7 +253,13 @@ export function CustomNode({
!isHidden &&
(isRequired || isAdvancedOpen || isConnected || !isAdvanced) && (
<div key={propKey} data-id={`input-handle-${propKey}`}>
{isConnectable ? (
{isConnectable &&
!(
"oneOf" in propSchema &&
propSchema.oneOf &&
"discriminator" in propSchema &&
propSchema.discriminator
) ? (
<NodeHandle
keyName={propKey}
isConnected={isConnected}

View File

@@ -6,9 +6,11 @@ import {
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
CarouselIndicator,
} from "@/components/ui/carousel";
import { useCallback, useState } from "react";
import { IconLeftArrow, IconRightArrow } from "@/components/ui/icons";
import { useRouter } from "next/navigation";
const BACKGROUND_COLORS = [
@@ -63,27 +65,24 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
return (
<div className="flex w-full flex-col items-center justify-center">
<div className="w-full">
<h2 className="font-poppins mb-8 text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
<div className="w-[99vw]">
<h2 className="font-poppins mx-auto mb-8 max-w-[1360px] px-4 text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
Featured agents
</h2>
<div>
<div className="w-[99vw] pb-[60px]">
<Carousel
className="mx-auto pb-10"
opts={{
loop: true,
startIndex: currentSlide,
duration: 500,
align: "start",
align: "center",
containScroll: "trimSnaps",
}}
className="w-full overflow-x-hidden"
>
<CarouselContent className="transition-transform duration-500">
<CarouselContent className="ml-[calc(50vw-690px)]">
{featuredAgents.map((agent, index) => (
<CarouselItem
key={index}
className="max-w-[460px] flex-[0_0_auto] pr-8"
className="max-w-[460px] flex-[0_0_auto]"
>
<FeaturedStoreCard
agentName={agent.agent_name}
@@ -99,37 +98,13 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
</CarouselItem>
))}
</CarouselContent>
<div className="relative mx-auto w-full max-w-[1360px] pl-4">
<CarouselIndicator />
<CarouselPrevious afterClick={handlePrevSlide} />
<CarouselNext afterClick={handleNextSlide} />
</div>
</Carousel>
</div>
<div className="mt-8 flex w-full items-center justify-between">
<div className="flex h-3 items-center gap-2">
{featuredAgents.map((_, index) => (
<div
key={index}
className={`${
currentSlide === index
? "h-3 w-[52px] rounded-[39px] bg-neutral-800 transition-all duration-500 dark:bg-neutral-200"
: "h-3 w-3 rounded-full bg-neutral-300 transition-all duration-500 dark:bg-neutral-600"
}`}
/>
))}
</div>
<div className="mb-[60px] flex items-center gap-3">
<button
onClick={handlePrevSlide}
className="mb:h-12 mb:w-12 flex h-10 w-10 items-center justify-center rounded-full border border-neutral-400 bg-white dark:border-neutral-600 dark:bg-neutral-800"
>
<IconLeftArrow className="h-8 w-8 text-neutral-800 dark:text-neutral-200" />
</button>
<button
onClick={handleNextSlide}
className="mb:h-12 mb:w-12 flex h-10 w-10 items-center justify-center rounded-full border border-neutral-900 bg-white dark:border-neutral-600 dark:bg-neutral-800"
>
<IconRightArrow className="h-8 w-8 text-neutral-800 dark:text-neutral-200" />
</button>
</div>
</div>
</div>
</div>
);

View File

@@ -7,8 +7,15 @@ import SchemaTooltip from "@/components/SchemaTooltip";
import useCredentials from "@/hooks/useCredentials";
import { zodResolver } from "@hookform/resolvers/zod";
import { NotionLogoIcon } from "@radix-ui/react-icons";
import { FaDiscord, FaGithub, FaGoogle, FaMedium, FaKey } from "react-icons/fa";
import { FC, useState } from "react";
import {
FaDiscord,
FaGithub,
FaTwitter,
FaGoogle,
FaMedium,
FaKey,
} from "react-icons/fa";
import { FC, useMemo, useState } from "react";
import {
CredentialsMetaInput,
CredentialsProviderName,
@@ -69,6 +76,7 @@ export const providerIcons: Record<
replicate: fallbackIcon,
fal: fallbackIcon,
revid: fallbackIcon,
twitter: FaTwitter,
unreal_speech: fallbackIcon,
exa: fallbackIcon,
hubspot: fallbackIcon,

View File

@@ -38,6 +38,7 @@ const providerDisplayNames: Record<CredentialsProviderName, string> = {
replicate: "Replicate",
fal: "FAL",
revid: "Rev.ID",
twitter: "Twitter",
unreal_speech: "Unreal Speech",
exa: "Exa",
hubspot: "Hubspot",

View File

@@ -17,6 +17,7 @@ import {
BlockIOStringSubSchema,
BlockIONumberSubSchema,
BlockIOBooleanSubSchema,
BlockIOSimpleTypeSubSchema,
} from "@/lib/autogpt-server-api/types";
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { Button } from "./ui/button";
@@ -40,6 +41,7 @@ import { LocalValuedInput } from "./ui/input";
import NodeHandle from "./NodeHandle";
import { ConnectionData } from "./CustomNode";
import { CredentialsInput } from "./integrations/credentials-input";
import { MultiSelect } from "./ui/multiselect-input";
type NodeObjectInputTreeProps = {
nodeId: string;
@@ -311,6 +313,8 @@ export const NodeGenericInputField: FC<{
);
}
console.log("propSchema", propSchema);
if ("properties" in propSchema) {
// Render a multi-select for all-boolean sub-schemas with more than 3 properties
if (
@@ -376,12 +380,53 @@ export const NodeGenericInputField: FC<{
}
if ("anyOf" in propSchema) {
// Optional oneOf
if (
"oneOf" in propSchema.anyOf[0] &&
propSchema.anyOf[0].oneOf &&
"discriminator" in propSchema.anyOf[0] &&
propSchema.anyOf[0].discriminator
) {
return (
<NodeOneOfDiscriminatorField
nodeId={nodeId}
propKey={propKey}
propSchema={propSchema.anyOf[0]}
currentValue={currentValue}
errors={errors}
connections={connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
className={className}
displayName={displayName}
/>
);
}
// optional items
const types = propSchema.anyOf.map((s) =>
"type" in s ? s.type : undefined,
);
if (types.includes("string") && types.includes("null")) {
// optional string
// optional string and datetime
if (
"format" in propSchema.anyOf[0] &&
propSchema.anyOf[0].format === "date-time"
) {
return (
<NodeDateTimeInput
selfKey={propKey}
schema={propSchema.anyOf[0]}
value={currentValue}
error={errors[propKey]}
className={className}
displayName={displayName}
handleInputChange={handleInputChange}
/>
);
}
return (
<NodeStringInput
selfKey={propKey}
@@ -442,6 +487,42 @@ export const NodeGenericInputField: FC<{
/>
);
} else if (types.includes("object") && types.includes("null")) {
// rendering optional mutliselect
if (
Object.values(
(propSchema.anyOf[0] as BlockIOObjectSubSchema).properties,
).every(
(subSchema) => "type" in subSchema && subSchema.type == "boolean",
) &&
Object.keys((propSchema.anyOf[0] as BlockIOObjectSubSchema).properties)
.length >= 1
) {
const options = Object.keys(
(propSchema.anyOf[0] as BlockIOObjectSubSchema).properties,
);
const selectedKeys = Object.entries(currentValue || {})
.filter(([_, v]) => v)
.map(([k, _]) => k);
return (
<NodeMultiSelectInput
selfKey={propKey}
schema={propSchema.anyOf[0] as BlockIOObjectSubSchema}
selection={selectedKeys}
error={errors[propKey]}
className={className}
displayName={displayName}
handleInputChange={(key, selection) => {
handleInputChange(
key,
Object.fromEntries(
options.map((option) => [option, selection.includes(option)]),
),
);
}}
/>
);
}
return (
<NodeKeyValueInput
nodeId={nodeId}
@@ -622,7 +703,7 @@ const NodeOneOfDiscriminatorField: FC<{
propSchema: any;
currentValue?: any;
errors: { [key: string]: string | undefined };
connections: any;
connections: ConnectionData;
handleInputChange: (key: string, value: any) => void;
handleInputClick: (key: string) => void;
className?: string;
@@ -637,7 +718,6 @@ const NodeOneOfDiscriminatorField: FC<{
handleInputChange,
handleInputClick,
className,
displayName,
}) => {
const discriminator = propSchema.discriminator;
@@ -653,7 +733,7 @@ const NodeOneOfDiscriminatorField: FC<{
return {
value: variantDiscValue,
schema: variant,
schema: variant as BlockIOSubSchema,
};
})
.filter((v: any) => v.value != null);
@@ -684,8 +764,24 @@ const NodeOneOfDiscriminatorField: FC<{
(opt: any) => opt.value === chosenType,
)?.schema;
function getEntryKey(key: string): string {
// use someKey for handle purpose (not childKey)
return `${propKey}_#_${key}`;
}
function isConnected(key: string): boolean {
return connections.some(
(c) => c.targetHandle === getEntryKey(key) && c.target === nodeId,
);
}
return (
<div className={cn("flex flex-col space-y-2", className)}>
<div
className={cn(
"flex min-w-[400px] max-w-[95%] flex-col space-y-4",
className,
)}
>
<Select value={chosenType || ""} onValueChange={handleVariantChange}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a type..." />
@@ -706,32 +802,36 @@ const NodeOneOfDiscriminatorField: FC<{
if (someKey === "discriminator") {
return null;
}
const childKey = propKey ? `${propKey}.${someKey}` : someKey;
const childKey = propKey ? `${propKey}.${someKey}` : someKey; // for history redo/undo purpose
return (
<div
key={childKey}
className="flex w-full flex-row justify-between space-y-2"
className="mb-4 flex w-full flex-col justify-between space-y-2"
>
<span className="mr-2 mt-3 dark:text-gray-300">
{(childSchema as BlockIOSubSchema).title ||
beautifyString(someKey)}
</span>
<NodeGenericInputField
nodeId={nodeId}
key={propKey}
propKey={childKey}
propSchema={childSchema as BlockIOSubSchema}
currentValue={
currentValue ? currentValue[someKey] : undefined
}
errors={errors}
connections={connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
displayName={
chosenVariantSchema.title || beautifyString(someKey)
}
<NodeHandle
keyName={getEntryKey(someKey)}
schema={childSchema as BlockIOSubSchema}
isConnected={isConnected(getEntryKey(someKey))}
isRequired={false}
side="left"
/>
{!isConnected(someKey) && (
<NodeGenericInputField
nodeId={nodeId}
key={propKey}
propKey={childKey}
propSchema={childSchema as BlockIOSubSchema}
currentValue={
currentValue ? currentValue[someKey] : undefined
}
errors={errors}
connections={connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
displayName={beautifyString(someKey)}
/>
)}
</div>
);
},
@@ -926,6 +1026,13 @@ const NodeKeyValueInput: FC<{
);
};
// Checking if schema is type of string
function isStringSubSchema(
schema: BlockIOSimpleTypeSubSchema,
): schema is BlockIOStringSubSchema {
return "type" in schema && schema.type === "string";
}
const NodeArrayInput: FC<{
nodeId: string;
selfKey: string;

View File

@@ -1,10 +1,11 @@
// This file has been updated for the Store's "Featured Agent Section". If you want to add Carousel, keep these components in mind: CarouselIndicator, CarouselPrevious, and CarouselNext.
"use client";
import * as React from "react";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
@@ -196,67 +197,137 @@ CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
React.ComponentProps<typeof Button> & { afterClick?: () => void }
>(
(
{ className, afterClick, variant = "outline", size = "icon", ...props },
ref,
) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
});
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-[52px] w-[52px] rounded-full",
orientation === "horizontal"
? "-bottom-20 right-24 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollPrev}
onClick={() => {
scrollPrev();
if (afterClick) {
afterClick();
}
}}
{...props}
>
<ChevronLeft className="h-8 w-8" strokeWidth={1.25} />
<span className="sr-only">Previous slide</span>
</Button>
);
},
);
CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
React.ComponentProps<typeof Button> & { afterClick?: () => void }
>(
(
{ className, afterClick, variant = "outline", size = "icon", ...props },
ref,
) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
const handleClick = () => {
scrollNext();
if (afterClick) {
afterClick();
}
};
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-[52px] w-[52px] rounded-full",
orientation === "horizontal"
? "-bottom-20 right-4 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollNext}
onClick={handleClick}
{...props}
>
<ChevronRight className="h-8 w-8" strokeWidth={1.25} />
<span className="sr-only">Next slide</span>
</Button>
);
},
);
CarouselNext.displayName = "CarouselNext";
const CarouselIndicator = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { api } = useCarousel();
const [selectedIndex, setSelectedIndex] = React.useState(0);
const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([]);
const scrollTo = React.useCallback(
(index: number) => {
api?.scrollTo(index);
},
[api],
);
React.useEffect(() => {
if (!api) return;
setScrollSnaps(api.scrollSnapList());
api.on("select", () => {
setSelectedIndex(api.selectedScrollSnap());
});
}, [api]);
return (
<Button
<div
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
className={cn("relative top-10 flex h-3 items-center gap-2", className)}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
{scrollSnaps.map((_, index) => (
<div
key={index}
onClick={() => scrollTo(index)}
className={cn(
selectedIndex === index
? "h-3 w-[52px] rounded-[39px] bg-neutral-800 transition-all duration-500 dark:bg-neutral-200"
: "h-3 w-3 rounded-full bg-neutral-300 transition-all duration-500 dark:bg-neutral-600",
"cursor-pointer",
)}
/>
))}
</div>
);
});
CarouselNext.displayName = "CarouselNext";
CarouselIndicator.displayName = "CarouselIndicator";
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselIndicator,
CarouselPrevious,
CarouselNext,
};

View File

@@ -41,7 +41,7 @@ export type BlockIOSubSchema =
| BlockIOSimpleTypeSubSchema
| BlockIOCombinedTypeSubSchema;
type BlockIOSimpleTypeSubSchema =
export type BlockIOSimpleTypeSubSchema =
| BlockIOObjectSubSchema
| BlockIOCredentialsSubSchema
| BlockIOKVSubSchema
@@ -126,6 +126,7 @@ export const PROVIDER_NAMES = {
UNREAL_SPEECH: "unreal_speech",
EXA: "exa",
HUBSPOT: "hubspot",
TWITTER: "twitter",
} as const;
// --8<-- [end:BlockIOCredentialsSubSchema]

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@@ -7,14 +7,16 @@ Below is a comprehensive list of all available blocks, categorized by their prim
## Basic Operations
| Block Name | Description |
|------------|-------------|
| [Store Value](basic.md#store-value) | Stores and forwards a value |
| [Print to Console](basic.md#print-to-console) | Outputs text to the console for debugging |
| [Find in Dictionary](basic.md#find-in-dictionary) | Looks up a value in a dictionary or list |
| [Agent Input](basic.md#agent-input) | Accepts user input in a workflow |
| [Agent Output](basic.md#agent-output) | Records and formats workflow results |
| [Add to Dictionary](basic.md#add-to-dictionary) | Adds a new key-value pair to a dictionary |
| [Add to List](basic.md#add-to-list) | Adds a new entry to a list |
| [Note](basic.md#note) | Displays a sticky note in the workflow |
| [Store Value](update/basic.md#store-value) | Stores and forwards a value |
| [Print to Console](update/basic.md#print-to-console) | Outputs text to the console for debugging |
| [Find in Dictionary](update/basic.md#find-in-dictionary) | Looks up a value in a dictionary or list |
| [Agent Input](update/basic.md#agent-input) | Accepts user input in a workflow |
| [Agent Output](update/basic.md#agent-output) | Records and formats workflow results |
| [Add to Dictionary](update/basic.md#add-to-dictionary) | Adds a new key-value pair to a dictionary |
| [Add to List](update/basic.md#add-to-list) | Adds a new entry to a list |
| [Note](update/basic.md#note) | Displays a sticky note in the workflow |
| [Create Dictionary](update/basic.md#create-dictionary) | Creates a new dictionary with specified key-value pairs |
| [Create List](update/basic.md#create-list) | Creates a new list with specified values |
## Data Processing
| Block Name | Description |
@@ -39,11 +41,13 @@ Below is a comprehensive list of all available blocks, categorized by their prim
| [AI Text Summarizer](llm.md#ai-text-summarizer) | Summarizes long texts using LLMs |
| [AI Conversation](llm.md#ai-conversation) | Facilitates multi-turn conversations with LLMs |
| [AI List Generator](llm.md#ai-list-generator) | Creates lists based on prompts using LLMs |
| [AI Music Generator](update/ai_music_generator.md#ai-music-generator) | Creates unique music using AI based on text descriptions |
## Web and API Interactions
| Block Name | Description |
|------------|-------------|
| [Send Web Request](http.md#send-web-request) | Makes HTTP requests to specified web addresses |
| [Send Web Request](update/http.md#web-request-sender) | Makes HTTP requests to specified web addresses |
| [HTTP Request Handler](update/helpers/http.md#http-request-handler) | Simplifies making HTTP GET requests to web services |
| [Read RSS Feed](rss.md#read-rss-feed) | Retrieves and processes entries from RSS feeds |
| [Get Weather Information](search.md#get-weather-information) | Fetches current weather data for a location |
| [Google Maps Search](google_maps.md#google-maps-search) | Searches for local businesses using Google Maps API |
@@ -81,19 +85,37 @@ Below is a comprehensive list of all available blocks, categorized by their prim
## Media Generation
| Block Name | Description |
|------------|-------------|
| [Ideogram Model](ideogram.md#ideogram-model) | Generates images based on text prompts |
| [Ideogram Model](update/ideogram.md#ideogram-model) | Generates images based on text prompts |
| [Create Talking Avatar Video](talking_head.md#create-talking-avatar-video) | Creates videos with talking avatars |
| [Unreal Text to Speech](text_to_speech_block.md#unreal-text-to-speech) | Converts text to speech using Unreal Speech API |
| [AI Shortform Video Creator](ai_shortform_video_block.md#ai-shortform-video-creator) | Generates short-form videos using AI |
| [Replicate Flux Advanced Model](replicate_flux_advanced.md#replicate-flux-advanced-model) | Creates images using Replicate's Flux models |
| [AI Music Generator](update/ai_music_generator.md#ai-music-generator) | Creates unique music using AI based on text descriptions |
## Miscellaneous
| Block Name | Description |
|------------|-------------|
| [Transcribe YouTube Video](youtube.md#transcribe-youtube-video) | Transcribes audio from YouTube videos |
| [Send Email](email_block.md#send-email) | Sends emails using SMTP |
| [Condition Block](branching.md#condition-block) | Evaluates conditions for workflow branching |
| [Step Through Items](iteration.md#step-through-items) | Iterates through lists or dictionaries |
| [Condition Block](update/branching.md#condition-block) | Evaluates conditions for workflow branching |
| [Step Through Items](update/iteration.md#step-through-items) | Iterates through lists or dictionaries |
## HubSpot Integration
| Block Name | Description |
|------------|-------------|
| [HubSpot Company Manager](update/hubspot/company.md#hubspot-company-manager) | Manages company records in HubSpot CRM |
| [HubSpot Contact Manager](update/hubspot/contact.md#hubspot-contact-manager) | Manages contact information in HubSpot CRM |
| [HubSpot Engagement Manager](update/hubspot/engagement.md#hubspot-engagement-manager) | Tracks and manages customer interactions in HubSpot |
## Code Management
| Block Name | Description |
|------------|-------------|
| [Code Executor](update/code_executor.md#code-executor) | Executes code snippets in a secure sandbox environment |
## Compass Integration
| Block Name | Description |
|------------|-------------|
| [Compass AI Trigger](update/compass/triggers.md#compass-ai-trigger) | Processes transcription content from Compass hardware |
## Google Services
| Block Name | Description |
@@ -129,4 +151,42 @@ Below is a comprehensive list of all available blocks, categorized by their prim
| [GitHub Unassign PR Reviewer](github/pull_requests.md#github-unassign-pr-reviewer) | Removes an assigned reviewer from a specific GitHub pull request |
| [GitHub List PR Reviewers](github/pull_requests.md#github-list-pr-reviewers) | Retrieves a list of all assigned reviewers for a specific GitHub pull request |
This comprehensive list covers all the blocks available in AutoGPT. Each block is designed to perform a specific task, and they can be combined to create powerful, automated workflows. For more detailed information on each block, click on its name to view the full documentation.
## Twitter Integration
| Block Name | Description |
|------------|-------------|
| [Twitter Post Tweet](twitter/twitter.md#twitter-post-tweet-block) | Creates a tweet on Twitter with text content and optional attachments including media, polls, quotes, or deep links |
| [Twitter Delete Tweet](twitter/twitter.md#twitter-delete-tweet-block) | Deletes a specified tweet using its tweet ID |
| [Twitter Search Recent](twitter/twitter.md#twitter-search-recent-block) | Searches for tweets matching specified criteria with options for filtering and pagination |
| [Twitter Get Quote Tweets](twitter/twitter.md#twitter-get-quote-tweets-block) | Gets tweets that quote a specified tweet ID with options for pagination and filtering |
| [Twitter Retweet](twitter/twitter.md#twitter-retweet-block) | Creates a retweet of a specified tweet using its tweet ID |
| [Twitter Remove Retweet](twitter/twitter.md#twitter-remove-retweet-block) | Removes an existing retweet of a specified tweet |
| [Twitter Get Retweeters](twitter/twitter.md#twitter-get-retweeters-block) | Gets list of users who have retweeted a specified tweet with pagination and filtering options |
| [Twitter Get User Mentions](twitter/twitter.md#twitter-get-user-mentions-block) | Gets tweets where a specific user is mentioned using their user ID |
| [Twitter Get Home Timeline](twitter/twitter.md#twitter-get-home-timeline-block) | Gets recent tweets and retweets from authenticated user and followed accounts |
| [Twitter Get User](twitter/twitter.md#twitter-get-user-block) | Gets detailed profile information for a single Twitter user |
| [Twitter Get Users](twitter/twitter.md#twitter-get-users-block) | Gets profile information for multiple Twitter users (up to 100) |
| [Twitter Search Spaces](twitter/twitter.md#twitter-search-spaces-block) | Searches for Twitter Spaces matching title keywords with state filtering |
| [Twitter Get Spaces](twitter/twitter.md#twitter-get-spaces-block) | Gets information about multiple Twitter Spaces by Space IDs or creator IDs |
| [Twitter Get Space By Id](twitter/twitter.md#twitter-get-space-by-id-block) | Gets detailed information about a single Twitter Space |
| [Twitter Get Space Tweets](twitter/twitter.md#twitter-get-space-tweets-block) | Gets tweets that were shared during a Twitter Space session |
| [Twitter Follow List](twitter/twitter.md#twitter-follow-list-block) | Follows a Twitter List using its List ID |
| [Twitter Unfollow List](twitter/twitter.md#twitter-unfollow-list-block) | Unfollows a previously followed Twitter List |
| [Twitter Get List](twitter/twitter.md#twitter-get-list-block) | Gets detailed information about a specific Twitter List |
| [Twitter Get Owned Lists](twitter/twitter.md#twitter-get-owned-lists-block) | Gets all Twitter Lists owned by a specified user |
| [Twitter Get List Members](twitter/twitter.md#twitter-get-list-members-block) | Gets information about members of a specified Twitter List |
| [Twitter Add List Member](twitter/twitter.md#twitter-add-list-member-block) | Adds a specified user as a member to a Twitter List |
| [Twitter Remove List Member](twitter/twitter.md#twitter-remove-list-member-block) | Removes a specified user from a Twitter List |
| [Twitter Get List Tweets](twitter/twitter.md#twitter-get-list-tweets-block) | Gets tweets posted within a specified Twitter List |
| [Twitter Create List](twitter/twitter.md#twitter-create-list-block) | Creates a new Twitter List with specified name and settings |
| [Twitter Update List](twitter/twitter.md#twitter-update-list-block) | Updates name and/or description of an existing Twitter List |
| [Twitter Delete List](twitter/twitter.md#twitter-delete-list-block) | Deletes a specified Twitter List |
| [Twitter Pin List](twitter/twitter.md#twitter-pin-list-block) | Pins a Twitter List to appear at top of Lists |
| [Twitter Unpin List](twitter/twitter.md#twitter-unpin-list-block) | Removes a Twitter List from pinned Lists |
| [Twitter Get Pinned Lists](twitter/twitter.md#twitter-get-pinned-lists-block) | Gets all Twitter Lists that are currently pinned |
| Twitter List Get Followers | Working... Gets all followers of a specified Twitter List |
| Twitter Get Followed Lists | Working... Gets all Lists that a user follows |
| Twitter Get DM Events | Working... Retrieves direct message events for a user |
| Twitter Send Direct Message | Working... Sends a direct message to a specified user |
| Twitter Create DM Conversation | Working... Creates a new direct message conversation |
This comprehensive list covers all the blocks available in AutoGPT. Each block is designed to perform a specific task, and they can be combined to create powerful, automated workflows. For more detailed information on each block, click on its name to view the full documentation.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
## AI Music Generator
### What it is
A powerful tool that transforms text descriptions into original music pieces using artificial intelligence technology.
### What it does
Creates unique audio compositions based on your written descriptions, allowing you to specify various aspects of the music generation process, including duration, style, and output format.
### How it works
The system takes your text description and other parameters, sends them to an advanced AI music model (Meta's MusicGen), and returns a link to the generated audio file. It automatically retries if there are any issues during generation and ensures the audio meets your specified requirements.
### Inputs
- Text Description: Your written description of the music you want to create (e.g., "An upbeat electronic dance track with heavy bass")
- Model Version: Choice of AI model version (Stereo Large, Melody Large, or Large) for different generation capabilities
- Duration: Length of the generated music in seconds
- Temperature: Controls how creative or conventional the generation should be (higher values mean more creativity)
- Output Format: Choose between WAV or MP3 file formats
- Normalization Strategy: How the audio volume should be balanced (Loudness, Clip, Peak, or RMS)
- Advanced Settings:
- Top K: Helps control variety in the generation
- Top P: Influences probability-based generation
- Classifier Free Guidance: Controls how closely the output follows your description
### Outputs
- Audio File URL: Link to download the generated music file
- Error Message: Information about what went wrong (if the generation fails)
### Possible use cases
- Creating custom background music for videos
- Generating mood-specific music for presentations
- Producing unique sound effects for multimedia projects
- Experimenting with AI-generated music compositions
- Quick prototyping of musical ideas

View File

@@ -0,0 +1,224 @@
# Basic Blocks Documentation
## Store Value
### What it is
A data storage and forwarding component that can hold and pass along values.
### What it does
Stores a provided value and makes it available for reuse without modification.
### How it works
Takes an input value and either stores it directly or uses a pre-stored value, then forwards it as output.
### Inputs
- Input: The value to be stored (if no data is provided)
- Data: A pre-stored value to use instead of the input (optional)
### Outputs
- Output: The stored value
### Possible use case
Storing a user's name that needs to be used multiple times throughout a workflow.
## Print to Console
### What it is
A debugging tool that displays information during operation.
### What it does
Prints text messages to the console for monitoring and debugging purposes.
### How it works
Takes a text message and displays it in the console with a "Print: " prefix.
### Inputs
- Text: The message to be displayed
### Outputs
- Status: Confirmation that the message was printed
### Possible use case
Monitoring the progress of a workflow by printing status updates.
## Find in Dictionary
### What it is
A search tool for looking up values in data structures.
### What it does
Searches for and retrieves values from dictionaries, lists, or objects using a specified key.
### How it works
Takes a data structure and a key, then attempts to find and return the corresponding value.
### Inputs
- Input: The data structure to search in
- Key: The key to look up
### Outputs
- Output: The found value
- Missing: The original input when the key isn't found
### Possible use case
Looking up a user's details from a database response using their ID.
## Agent Input
### What it is
An interface for collecting input values from users.
### What it does
Provides a structured way to gather and validate user input with optional constraints.
### How it works
Creates an input field with customizable properties and validates the entered value.
### Inputs
- Name: Identifier for the input
- Value: The actual input value
- Title: Display name (optional)
- Description: Help text (optional)
- Placeholder Values: Suggested values
- Limit to Placeholder Values: Restrict input to suggestions only
### Outputs
- Result: The collected input value
### Possible use case
Creating a form field for users to enter their age with suggested ranges.
## Agent Output
### What it is
A display component for showing results to users.
### What it does
Formats and presents output values in a user-friendly way.
### How it works
Takes a value and optional formatting instructions to display the result.
### Inputs
- Value: The data to display
- Name: Identifier for the output
- Title: Display name
- Description: Additional information
- Format: Custom formatting instructions
### Outputs
- Output: The formatted result
### Possible use case
Displaying the results of a calculation with proper formatting and explanation.
## Add to Dictionary
### What it is
A tool for adding new entries to dictionaries.
### What it does
Adds one or more key-value pairs to an existing or new dictionary.
### How it works
Takes a dictionary and new entries, then creates an updated dictionary with the additions.
### Inputs
- Dictionary: Existing dictionary (optional)
- Key: New entry key
- Value: New entry value
- Entries: Multiple entries to add
### Outputs
- Updated Dictionary: The modified dictionary
- Error: Any error messages
### Possible use case
Adding a new user preference to an existing settings dictionary.
## Add to List
### What it is
A tool for adding items to lists.
### What it does
Adds one or more items to an existing or new list at specified positions.
### How it works
Takes a list and new items, then creates an updated list with the additions.
### Inputs
- List: Existing list (optional)
- Entry: Single item to add
- Entries: Multiple items to add
- Position: Where to insert items
### Outputs
- Updated List: The modified list
- Error: Any error messages
### Possible use case
Adding new tasks to a todo list at specific priorities.
## Note
### What it is
A documentation tool for adding explanatory notes.
### What it does
Displays text as a sticky note in the workflow.
### How it works
Takes text input and displays it as a note for documentation purposes.
### Inputs
- Text: The note content
### Outputs
- Output: The displayed note text
### Possible use case
Adding explanatory comments to describe complex workflow steps.
## Create Dictionary
### What it is
A dictionary creation tool.
### What it does
Creates a new dictionary with specified key-value pairs.
### How it works
Takes a set of key-value pairs and combines them into a new dictionary.
### Inputs
- Values: Key-value pairs for the dictionary
### Outputs
- Dictionary: The created dictionary
- Error: Any error messages
### Possible use case
Creating a new user profile with multiple fields.
## Create List
### What it is
A list creation tool.
### What it does
Creates a new list with specified values.
### How it works
Takes a set of values and combines them into a new list.
### Inputs
- Values: Items for the list
### Outputs
- List: The created list
- Error: Any error messages
### Possible use case
Creating a new shopping list with multiple items.

View File

@@ -0,0 +1,33 @@
## Condition Block
### What it is
A logical comparison tool that helps make decisions based on comparing two values.
### What it does
Compares two values using standard comparison operators (equals, not equals, greater than, less than, etc.) and produces different outputs based on whether the comparison is true or false.
### How it works
1. Takes two values and compares them using the selected operator
2. Determines if the comparison is true or false
3. Outputs the result and the appropriate value based on the comparison outcome
4. If custom output values aren't specified, uses the input values as defaults
### Inputs
- First Value: Any value you want to compare (numbers, text, or true/false values)
- Comparison Operator: The type of comparison to perform (equals, not equals, greater than, less than, etc.)
- Second Value: The value to compare against the first value
- True Result Value (Optional): Custom value to output if the comparison is true
- False Result Value (Optional): Custom value to output if the comparison is false
### Outputs
- Comparison Result: Whether the comparison was true or false
- True Output: The value produced when the comparison is true
- False Output: The value produced when the comparison is false
### Possible use cases
- Creating an age-restricted content filter: Compare user's age against a minimum required age
- Price comparison system: Check if a product's price is below a certain threshold
- Temperature control: Determine if current temperature is within acceptable range
- Grade assessment: Evaluate if a student's score meets passing criteria
- Inventory management: Check if stock levels are below reorder point

View File

@@ -0,0 +1,36 @@
## Code Executor
### What it is
A secure environment for running code snippets in various programming languages without affecting your main system.
### What it does
Executes programming code in an isolated sandbox environment with internet access, allowing you to run programs safely while maintaining the ability to install and use external packages.
### How it works
1. Creates a secure, isolated environment (sandbox)
2. Sets up any required dependencies using setup commands
3. Executes the provided code in the chosen programming language
4. Captures and returns the results, including any output or errors
5. Automatically closes the environment after execution
### Inputs
- Credentials: Your E2B sandbox access key for authentication
- Setup Commands: Optional preparation steps, such as installing packages or downloading files
- Code: The actual program or script you want to run
- Programming Language: Your choice of Python, JavaScript, Bash, R, or Java
- Timeout: Maximum time (in seconds) allowed for code execution
- Template ID: Optional custom sandbox configuration for specific requirements
### Outputs
- Response: The main result of your code execution
- Standard Output: Regular program output and messages
- Standard Error: Any error messages or warnings
- Error: Detailed error information if the execution fails
### Possible use cases
- Testing new code snippets before implementing them in a larger project
- Running experiments with different programming languages
- Teaching programming concepts in a safe environment
- Testing scripts that require specific system configurations
- Executing code that needs isolation from the main system

View File

@@ -0,0 +1,30 @@
## Compass AI Trigger
### What it is
A specialized component that processes and extracts transcription content from Compass hardware systems. It's designed to work with voice-to-text conversions and handle detailed transcription data.
### What it does
This block receives transcription data from Compass hardware and makes the transcribed text available for further processing. It takes complex transcription data (including timing and speaker information) and simplifies it into accessible text content.
### How it works
1. Receives transcription data from Compass hardware through a webhook
2. Processes the incoming data, which includes detailed information about the transcription
3. Extracts the main transcription text
4. Makes the transcribed text available for other components to use
### Inputs
- Payload: A structured package of data containing:
- Date: When the transcription was created
- Transcription: The main text content
- Detailed transcriptions: A collection of individual transcription segments with speaker and timing information
### Outputs
- Transcription: The complete text content from the transcription process, ready for further use
### Possible use cases
- Converting recorded meetings into text
- Processing customer service call recordings
- Documenting voice notes or interviews
- Creating text records from audio presentations
- Real-time transcription of live speeches or presentations

View File

@@ -0,0 +1,32 @@
## CSV Reader
### What it is
A versatile tool that converts CSV (Comma-Separated Values) file contents into organized, structured data that's easy to work with.
### What it does
Reads and processes CSV file contents, transforming them into a structured format while offering various customization options like handling headers, skipping rows, and cleaning data.
### How it works
The tool takes your CSV content and breaks it down line by line, organizing each row's data into a neat, labeled structure. It can work with different types of CSV formats and gives you options to customize how the data is processed, such as removing extra spaces or skipping certain rows.
### Inputs
- File Contents: The actual text content of your CSV file
- Delimiter: The character that separates values in your file (default is comma)
- Quote Character: The character used to wrap text that contains special characters (default is double quote)
- Escape Character: The character used to mark special characters (default is backslash)
- Has Header: Whether your file includes column names in the first row (default is yes)
- Skip Rows: Number of rows to skip from the beginning of the file (default is 0)
- Strip Whitespace: Whether to remove extra spaces from values (default is yes)
- Skip Columns: List of columns you want to exclude from the results (default is none)
### Outputs
- Individual Row: Provides each row of data as it's processed, with values organized by column names
- Complete Dataset: Delivers all rows together as a single collection once processing is finished
### Possible use cases
- Importing contact lists from spreadsheets
- Processing financial data from exported reports
- Converting inventory spreadsheets into structured data
- Analyzing survey responses from CSV exports
- Transforming exported data tables into a more usable format

View File

@@ -0,0 +1,46 @@
# Discord Integration Blocks
## Read Discord Messages
### What it is
A component that monitors and captures messages from Discord channels.
### What it does
Connects to Discord and reads incoming messages, including any text file attachments, providing the message content along with channel and sender information.
### How it works
Once activated, it connects to Discord using a bot account and listens for new messages. When a message is received, it captures the content, channel name, and username of the sender. If the message includes text file attachments, it will also read and include their contents.
### Inputs
- Discord Credentials: A bot token that allows the block to connect to Discord
### Outputs
- Message Content: The text content of the received message, including any attached text files
- Channel Name: The name of the Discord channel where the message was sent
- Username: The name of the user who sent the message
### Possible use case
Creating a customer support system that monitors support channels for questions and automatically logs all conversations for future reference.
## Send Discord Message
### What it is
A component that sends messages to specified Discord channels.
### What it does
Sends text messages to designated Discord channels and confirms the delivery status.
### How it works
The block connects to Discord using a bot account, locates the specified channel, and sends the message. If the message is longer than Discord's character limit, it automatically splits it into smaller chunks for proper delivery.
### Inputs
- Discord Credentials: A bot token that allows the block to connect to Discord
- Message Content: The text message you want to send
- Channel Name: The name of the channel where you want to send the message
### Outputs
- Status: Confirmation of whether the message was sent successfully or if any errors occurred
### Possible use case
Setting up an automated notification system that sends status updates or alerts to a Discord channel when certain events occur in your application.

View File

@@ -0,0 +1,34 @@
## Exa Contents
### What it is
A specialized component that retrieves and processes document contents using the Exa service platform. It's designed to fetch various types of content from documents, including full text, highlights, and summaries.
### What it does
This block retrieves document contents based on provided document IDs. It can extract full text content, generate relevant highlights, and create summaries of documents. The block offers flexible configuration options to control how content is retrieved and processed.
### How it works
1. Accepts document IDs and content retrieval settings
2. Authenticates with the Exa service
3. Sends a request to retrieve the specified content
4. Processes the response and returns the formatted results
5. Handles any errors that might occur during the process
### Inputs
- Credentials: Authentication information required to access the Exa service
- Document IDs: A list of unique identifiers for the documents you want to retrieve
- Content Settings: Customizable options for content retrieval
* Text Settings: Controls maximum character count and HTML tag handling
* Highlight Settings: Determines number of sentences and highlights per URL
* Summary Settings: Configures how document summaries are generated
### Outputs
- Results: A list containing the retrieved document contents, which may include full text, highlights, or summaries depending on the specified settings
- Error Message: If something goes wrong, the block provides information about what happened
### Possible use cases
- Building a document management system that needs to display document previews
- Creating a research tool that extracts relevant highlights from multiple documents
- Developing a content aggregation platform that needs to generate summaries of articles
- Implementing a search system that shows document snippets in search results
- Building a content analysis tool that processes document contents for further analysis

View File

@@ -0,0 +1,34 @@
## Exa Search
### What it is
A powerful web search tool that leverages Exa's advanced search capabilities to find and filter web content based on various criteria.
### What it does
Performs comprehensive web searches with fine-tuned control over the results, including filtering by dates, domains, and text patterns. It can search within specific categories, exclude unwanted sources, and deliver customized result sets.
### How it works
The tool sends your search query to Exa's search engine along with any specified filters and preferences. It processes your request and returns relevant web content that matches your criteria. The search can be enhanced with automatic prompt optimization and can be configured to return exactly the number of results you need.
### Inputs
- Query: Your search terms or question
- Auto Prompt: Option to automatically enhance your search query for better results
- Number of Results: How many search results you want to receive (default: 10)
- Include Domains: List of websites you want to search within
- Exclude Domains: List of websites you want to avoid
- Crawl Date Range: Filter content based on when it was discovered by the search engine
- Publication Date Range: Filter content based on when it was published
- Include Text: Specific text patterns that must appear in the results
- Exclude Text: Text patterns that should not appear in the results
- Content Settings: Preferences for how content should be retrieved and presented
- Type: Specific type of search to perform
- Category: Specific category to search within
### Outputs
- Results: A list of search results matching your criteria, including relevant web content and metadata
### Possible use cases
- Research Project: A researcher could use this tool to find academic articles published within a specific date range, excluding certain domains and focusing on particular keywords
- Content Monitoring: A business analyst could track mentions of their company across specific websites during a marketing campaign
- News Gathering: A journalist could search for recent news articles about a topic while excluding unreliable sources
- Competitive Analysis: A market researcher could gather information about competitors by searching within their domains during specific time periods

View File

@@ -0,0 +1,39 @@
## Exa Find Similar
### What it is
A tool that finds web content similar to a specified URL using Exa's content discovery technology.
### What it does
This block searches across the web to find content that is similar to a given webpage. It can filter results based on various criteria such as domains, dates, and specific text patterns, making it highly customizable for different search needs.
### How it works
When you provide a URL, the block analyzes the content and searches Exa's database to find similar content across the web. It applies any specified filters (such as date ranges or domain restrictions) and returns a list of matching documents, ranked by similarity.
### Inputs
- URL: The webpage address you want to find similar content for
- Number of Results: How many similar items you want to receive (default is 10)
- Include Domains: List of website domains to specifically search within
- Exclude Domains: List of website domains to ignore in the search
- Start Crawl Date: Earliest date from which to consider when content was discovered
- End Crawl Date: Latest date from which to consider when content was discovered
- Start Published Date: Earliest publication date to consider
- End Published Date: Latest publication date to consider
- Include Text: Specific text patterns to look for (limited to 5 words)
- Exclude Text: Specific text patterns to exclude (limited to 5 words)
- Content Settings: Advanced settings for how content should be retrieved
### Outputs
- Results: A list of similar documents, each containing:
- Title of the page
- URL of the page
- Publication date
- Author information
- Similarity score
### Possible use cases
- A content creator researching similar articles in their field
- A marketing professional tracking competitive content
- A researcher finding related academic papers
- A news organization identifying similar coverage of a story
- A website owner finding potentially duplicate or plagiarized content

View File

@@ -0,0 +1,37 @@
## AI Video Generator
### What it is
A powerful tool that converts text descriptions into videos using artificial intelligence models from FAL AI.
### What it does
Takes a written description of a desired video and automatically generates a corresponding video clip using advanced AI technology. The system can work with different AI models to create various styles of videos.
### How it works
1. Accepts your text description and chosen AI model
2. Securely connects to the FAL AI service
3. Submits your request for video generation
4. Monitors the generation progress
5. Delivers the final video URL when complete
### Inputs
- Text Description: A detailed description of the video you want to create (e.g., "A dog running in a field")
- AI Model: Choice between different video generation models (MOCHI or LUMA)
- Credentials: Your secure access information for the FAL AI service
### Outputs
- Video URL: Web link to access your generated video
- Error Message: Clear explanation if something goes wrong during generation
- Progress Logs: Step-by-step updates about your video's creation process
### Possible use cases
- Creating quick video prototypes for marketing concepts
- Generating video content for social media posts
- Visualizing creative ideas without traditional video production
- Producing animated sequences from written descriptions
- Testing different video concepts before full production
### Notes
- The generation process may take some time depending on system load
- Video quality and style will vary based on the chosen AI model
- Internet connection is required for video generation and retrieval

View File

@@ -0,0 +1,181 @@
# GitHub Issue Management Blocks
## GitHub Comment
### What it is
A tool for adding comments to GitHub issues or pull requests.
### What it does
Posts new comments on existing GitHub issues or pull requests automatically.
### How it works
Takes your comment text and adds it to the specified issue or pull request using your GitHub credentials.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue or pull request
- Comment: The text you want to post as a comment
### Outputs
- Comment ID: A unique identifier for your posted comment
- Comment URL: Direct link to view your comment
- Error Message: Information if something goes wrong
### Possible use case
Automatically responding to bug reports with status updates or requesting more information from users.
## GitHub Make Issue
### What it is
A tool for creating new issues in GitHub repositories.
### What it does
Creates new issues with customized titles and descriptions in any GitHub repository you have access to.
### How it works
Uses your provided information to create a new issue in the specified repository with your desired title and description.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Repository URL: The web address of the GitHub repository
- Title: The heading for your new issue
- Body: The main content of your issue
### Outputs
- Issue Number: The unique identifier for your new issue
- Issue URL: Direct link to view your issue
- Error Message: Information if something goes wrong
### Possible use case
Automatically creating standardized bug reports or feature requests based on user feedback.
## GitHub Read Issue
### What it is
A tool for retrieving information from existing GitHub issues.
### What it does
Fetches and provides the contents and details of any specified GitHub issue.
### How it works
Retrieves the title, description, and creator information from a given issue URL.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue you want to read
### Outputs
- Title: The heading of the issue
- Body: The main content of the issue
- User: The username of who created the issue
- Error Message: Information if something goes wrong
### Possible use case
Monitoring specific issues for updates or collecting issue information for reporting.
## GitHub List Issues
### What it is
A tool for getting a list of all issues in a GitHub repository.
### What it does
Retrieves and lists all available issues from a specified GitHub repository.
### How it works
Fetches all issues from the repository and provides their titles and URLs in a list format.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Repository URL: The web address of the GitHub repository
### Outputs
- Issues: A list of issues with their titles and URLs
- Error Message: Information if something goes wrong
### Possible use case
Creating a dashboard of all open issues or generating reports about repository activity.
## GitHub Add Label
### What it is
A tool for adding labels to GitHub issues or pull requests.
### What it does
Applies specified labels to any GitHub issue or pull request you have access to.
### How it works
Adds the specified label to the issue or pull request identified by the URL.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue or pull request
- Label: The name of the label to add
### Outputs
- Status: Confirmation of the label addition
- Error Message: Information if something goes wrong
### Possible use case
Automatically categorizing issues based on their content or priority level.
## GitHub Remove Label
### What it is
A tool for removing labels from GitHub issues or pull requests.
### What it does
Removes specified labels from any GitHub issue or pull request you have access to.
### How it works
Removes the specified label from the issue or pull request identified by the URL.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue or pull request
- Label: The name of the label to remove
### Outputs
- Status: Confirmation of the label removal
- Error Message: Information if something goes wrong
### Possible use case
Updating issue categories when their status changes or correcting miscategorized issues.
## GitHub Assign Issue
### What it is
A tool for assigning GitHub issues to specific users.
### What it does
Assigns a specified GitHub user to any issue you have access to.
### How it works
Adds the specified user as an assignee to the issue identified by the URL.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue
- Assignee: The username of the person to assign
### Outputs
- Status: Confirmation of the assignment
- Error Message: Information if something goes wrong
### Possible use case
Automatically assigning issues to team members based on their expertise or workload.
## GitHub Unassign Issue
### What it is
A tool for removing user assignments from GitHub issues.
### What it does
Removes specified users from being assigned to a GitHub issue.
### How it works
Removes the specified user from the list of assignees on the given issue.
### Inputs
- GitHub Credentials: Your authentication details for GitHub
- Issue URL: The web address of the issue
- Assignee: The username to remove from the assignment
### Outputs
- Status: Confirmation of the unassignment
- Error Message: Information if something goes wrong
### Possible use case
Redistributing workload or clearing assignments when team members are unavailable.

View File

@@ -0,0 +1,145 @@
# GitHub Pull Request Management Blocks
## List Pull Requests
### What it is
A tool that retrieves a list of all pull requests from a specified GitHub repository.
### What it does
Fetches and displays information about all open pull requests, including their titles and URLs.
### How it works
Connects to GitHub, accesses the specified repository, and retrieves the list of pull requests.
### Inputs
- Repository URL: The web address of the GitHub repository
- GitHub Credentials: Authentication details to access the repository
### Outputs
- Pull Request List: Collection of pull requests with their titles and URLs
- Error Message: Information about any issues that occurred
### Possible use case
Monitoring active development work by viewing all ongoing pull requests in a project.
## Make Pull Request
### What it is
A tool for creating new pull requests in a GitHub repository.
### What it does
Creates a new pull request with specified details, including title, description, and branch information.
### How it works
Takes your provided information and submits it to GitHub to create a new pull request.
### Inputs
- Repository URL: The web address of the GitHub repository
- Title: Name of the pull request
- Body: Detailed description of the changes
- Head Branch: The branch containing your changes
- Base Branch: The branch you want to merge into
- GitHub Credentials: Authentication details
### Outputs
- Pull Request Number: Unique identifier for the created PR
- Pull Request URL: Web address of the new PR
- Error Message: Information about any issues
### Possible use case
Automating the creation of pull requests for regular code updates or maintenance tasks.
## Read Pull Request
### What it is
A tool that retrieves detailed information about a specific pull request.
### What it does
Fetches and displays comprehensive information about a pull request, including its content and changes.
### How it works
Retrieves the pull request details from GitHub and presents them in an organized format.
### Inputs
- Pull Request URL: Web address of the specific pull request
- Include Changes Flag: Option to include code changes
- GitHub Credentials: Authentication details
### Outputs
- Title: Name of the pull request
- Body: Full description
- Author: Creator of the pull request
- Changes: Code modifications (if requested)
- Error Message: Information about any issues
### Possible use case
Reviewing pull request details before approving changes or providing feedback.
## Assign PR Reviewer
### What it is
A tool for assigning reviewers to pull requests.
### What it does
Adds a specified user as a reviewer to a pull request.
### How it works
Updates the pull request's reviewer list with the specified username.
### Inputs
- Pull Request URL: Web address of the specific pull request
- Reviewer Username: GitHub username of the reviewer
- GitHub Credentials: Authentication details
### Outputs
- Status: Success or failure message
- Error Message: Information about any issues
### Possible use case
Automatically assigning team members to review code changes.
## Unassign PR Reviewer
### What it is
A tool for removing reviewers from pull requests.
### What it does
Removes a specified reviewer from a pull request.
### How it works
Updates the pull request to remove the specified reviewer from the reviewer list.
### Inputs
- Pull Request URL: Web address of the specific pull request
- Reviewer Username: GitHub username to remove
- GitHub Credentials: Authentication details
### Outputs
- Status: Success or failure message
- Error Message: Information about any issues
### Possible use case
Reassigning reviews when a team member is unavailable or when restructuring review assignments.
## List PR Reviewers
### What it is
A tool that shows all assigned reviewers for a pull request.
### What it does
Retrieves and displays a list of all users assigned to review a specific pull request.
### How it works
Fetches the reviewer information from GitHub and presents it in an organized list.
### Inputs
- Pull Request URL: Web address of the specific pull request
- GitHub Credentials: Authentication details
### Outputs
- Reviewer List: Collection of reviewer usernames and profile URLs
- Error Message: Information about any issues
### Possible use case
Checking who is responsible for reviewing a particular pull request.

View File

@@ -0,0 +1,297 @@
# GitHub Repository Management Blocks
## GitHub List Tags
### What it is
A tool that retrieves all tags from a GitHub repository.
### What it does
Fetches and displays a list of all tags in a specified GitHub repository, including their names and URLs.
### How it works
Connects to GitHub, retrieves tag information from the specified repository, and presents each tag with its corresponding URL.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
### Outputs
- Tag Name: The name of each tag
- Tag URL: Direct link to browse the repository at that tag
- Error Message: Any error that occurred during the process
### Possible use case
Monitoring version tags of a software project to track releases and updates.
## GitHub List Branches
### What it is
A tool that retrieves all branches from a GitHub repository.
### What it does
Fetches and displays a list of all branches in a specified GitHub repository, including their names and URLs.
### How it works
Connects to GitHub, retrieves branch information from the specified repository, and presents each branch with its corresponding URL.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
### Outputs
- Branch Name: The name of each branch
- Branch URL: Direct link to browse the repository at that branch
- Error Message: Any error that occurred during the process
### Possible use case
Monitoring active development branches in a project to track different features or versions being worked on.
## GitHub List Discussions
### What it is
A tool that retrieves recent discussions from a GitHub repository.
### What it does
Fetches and displays a list of recent discussions from a specified GitHub repository.
### How it works
Connects to GitHub, retrieves discussion information, and presents each discussion with its title and URL.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- Number of Discussions: How many recent discussions to retrieve (default: 5)
### Outputs
- Discussion Title: The title of each discussion
- Discussion URL: Direct link to the discussion
- Error Message: Any error that occurred during the process
### Possible use case
Monitoring community engagement and discussions about a project.
## GitHub List Releases
### What it is
A tool that retrieves all releases from a GitHub repository.
### What it does
Fetches and displays a list of all releases in a specified GitHub repository.
### How it works
Connects to GitHub, retrieves release information, and presents each release with its name and URL.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
### Outputs
- Release Name: The name of each release
- Release URL: Direct link to the release
- Error Message: Any error that occurred during the process
### Possible use case
Tracking official releases and their documentation for a software project.
## GitHub Read File
### What it is
A tool that reads the content of a file from a GitHub repository.
### What it does
Retrieves and displays the content of a specified file from a GitHub repository.
### How it works
Connects to GitHub, locates the specified file, and retrieves its content in both text and raw formats.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- File Path: Location of the file within the repository
- Branch: Which branch to read from (default: master)
### Outputs
- Text Content: The file's content in readable text format
- Raw Content: The file's content in base64 encoded format
- File Size: Size of the file in bytes
- Error Message: Any error that occurred during the process
### Possible use case
Retrieving configuration files or documentation from a repository.
## GitHub Read Folder
### What it is
A tool that reads the contents of a folder from a GitHub repository.
### What it does
Retrieves and displays a list of all files and folders within a specified directory.
### How it works
Connects to GitHub, locates the specified folder, and lists all its contents with details.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- Folder Path: Location of the folder within the repository
- Branch: Which branch to read from (default: master)
### Outputs
- Files: List of files with names, paths, and sizes
- Directories: List of subdirectories with names and paths
- Error Message: Any error that occurred during the process
### Possible use case
Exploring the structure of a project or finding specific files within a repository.
## GitHub Make Branch
### What it is
A tool that creates a new branch in a GitHub repository.
### What it does
Creates a new branch from an existing source branch in a repository.
### How it works
Connects to GitHub, copies the specified source branch, and creates a new branch with the desired name.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- New Branch Name: Name for the branch to be created
- Source Branch Name: Name of the branch to copy from
### Outputs
- Status: Result of the branch creation operation
- Error Message: Any error that occurred during the process
### Possible use case
Creating a new feature branch for development work.
## GitHub Delete Branch
### What it is
A tool that deletes a branch from a GitHub repository.
### What it does
Removes a specified branch from the repository.
### How it works
Connects to GitHub and removes the specified branch from the repository.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- Branch Name: Name of the branch to delete
### Outputs
- Status: Result of the branch deletion operation
- Error Message: Any error that occurred during the process
### Possible use case
Cleaning up old feature branches after merging work.
## GitHub Create File
### What it is
A tool that creates a new file in a GitHub repository.
### What it does
Creates a new file with specified content in a repository.
### How it works
Connects to GitHub and creates a new file at the specified location with the provided content.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- File Path: Where to create the file
- Content: What to write in the file
- Branch: Which branch to create the file in
- Commit Message: Description of the change
### Outputs
- File URL: Web address of the created file
- Commit SHA: Unique identifier for the commit
- Error Message: Any error that occurred during the process
### Possible use case
Adding new documentation or configuration files to a project.
## GitHub Update File
### What it is
A tool that updates an existing file in a GitHub repository.
### What it does
Modifies the content of an existing file in a repository.
### How it works
Connects to GitHub, locates the specified file, and updates its content with the new version.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
- File Path: Location of the file to update
- Content: New content for the file
- Branch: Which branch contains the file
- Commit Message: Description of the change
### Outputs
- File URL: Web address of the updated file
- Commit SHA: Unique identifier for the commit
- Error Message: Any error that occurred during the process
### Possible use case
Updating version numbers or documentation in project files.
## GitHub Create Repository
### What it is
A tool that creates a new GitHub repository.
### What it does
Creates a new repository with specified settings and initial content.
### How it works
Connects to GitHub and creates a new repository with the provided configuration.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Name: Name for the new repository
- Description: Description of the repository
- Private: Whether the repository should be private
- Auto Initialize: Whether to create an initial README file
- GitIgnore Template: Template for ignoring files
### Outputs
- Repository URL: Web address of the created repository
- Clone URL: Address for cloning the repository
- Error Message: Any error that occurred during the process
### Possible use case
Setting up a new project repository with proper initial configuration.
## GitHub List Stargazers
### What it is
A tool that retrieves all users who have starred a GitHub repository.
### What it does
Fetches and displays a list of users who have starred the repository.
### How it works
Connects to GitHub and retrieves information about users who have starred the repository.
### Inputs
- GitHub Credentials: Authentication details for accessing GitHub
- Repository URL: The web address of the GitHub repository
### Outputs
- Username: Name of each user who starred the repository
- Profile URL: Link to each user's GitHub profile
- Error Message: Any error that occurred during the process
### Possible use case
Analyzing community interest in a project or reaching out to engaged users.

View File

@@ -0,0 +1,44 @@
## GitHub Pull Request Trigger
### What it is
A monitoring tool that watches for and responds to GitHub pull request activities in specified repositories.
### What it does
This block listens for various pull request events on a GitHub repository and provides detailed information about these events when they occur. It can track multiple types of pull request activities, such as when pull requests are opened, closed, edited, or updated.
### How it works
The block sets up a webhook connection with a specified GitHub repository. When pull request activities occur in the repository, GitHub sends information to this webhook. The block then processes this information and provides structured data about the event and the pull request.
### Inputs
- Repository Path: The GitHub repository to monitor (format: "owner/repository")
- Event Selections: Choose which pull request events to monitor:
- Pull request opened
- Pull request closed
- Pull request edited
- Pull request reopened
- Changes pushed (synchronize)
- Assignee changes
- Label changes
- Draft status changes
- Lock status changes
- Review request changes
- Milestone changes
- Auto-merge setting changes
- GitHub Credentials: Authentication details for accessing the repository
### Outputs
- Event Type: The specific type of pull request event that occurred
- Pull Request Number: The identifying number of the affected pull request
- Pull Request Details: Complete information about the pull request
- Pull Request URL: Direct link to the pull request on GitHub
- Triggered User: Information about the GitHub user who caused the event
- Complete Payload: Detailed technical information about the event
- Error Message: Information about any problems that occurred (if applicable)
### Possible use cases
1. Automatically notify team channels when new pull requests are opened
2. Track pull request status changes for project management
3. Generate reports on pull request activities
4. Trigger automated code review processes
5. Update project dashboards with real-time pull request information

View File

@@ -0,0 +1,117 @@
# Gmail Integration Blocks
## Gmail Reader
### What it is
A tool that reads and retrieves emails from your Gmail account.
### What it does
Fetches emails based on search criteria and provides detailed information about each email, including subject, sender, content, and attachments.
### How it works
Connects to your Gmail account, searches for emails matching your criteria, and returns both the email content and metadata in an organized format.
### Inputs
- Credentials: Your Gmail account access permissions
- Search Query: How to filter emails (e.g., "is:unread" for unread emails)
- Maximum Results: How many emails to retrieve at once
### Outputs
- Email: Individual email data including subject, sender, content, and attachments
- Emails: List of multiple email data
- Error: Any error messages if something goes wrong
### Possible use case
Automatically monitoring incoming emails for important messages or creating a custom email dashboard.
## Gmail Sender
### What it is
A tool for sending emails through your Gmail account.
### What it does
Composes and sends emails to specified recipients with custom subjects and content.
### How it works
Uses your Gmail account to create and send new email messages to designated recipients.
### Inputs
- Credentials: Your Gmail account access permissions
- To: Recipient's email address
- Subject: Email subject line
- Body: Email content
### Outputs
- Result: Confirmation of email sending
- Error: Any error messages if something goes wrong
### Possible use case
Sending automated notifications or responses based on specific triggers.
## Gmail Label Lister
### What it is
A tool that shows all labels in your Gmail account.
### What it does
Retrieves and displays a complete list of all labels (categories) in your Gmail account.
### How it works
Connects to Gmail and fetches all existing labels, both system-created and custom.
### Inputs
- Credentials: Your Gmail account access permissions
### Outputs
- Result: List of all Gmail labels
- Error: Any error messages if something goes wrong
### Possible use case
Reviewing available labels before setting up email organization rules.
## Gmail Label Adder
### What it is
A tool that adds labels to specific emails in Gmail.
### What it does
Applies specified labels to individual email messages for organization.
### How it works
Takes an email message and a label name, then applies that label to the message.
### Inputs
- Credentials: Your Gmail account access permissions
- Message ID: The specific email to label
- Label Name: The label to apply
### Outputs
- Result: Confirmation of label addition
- Error: Any error messages if something goes wrong
### Possible use case
Automatically categorizing incoming emails based on content or sender.
## Gmail Label Remover
### What it is
A tool that removes labels from specific emails in Gmail.
### What it does
Removes specified labels from individual email messages.
### How it works
Takes an email message and a label name, then removes that label from the message.
### Inputs
- Credentials: Your Gmail account access permissions
- Message ID: The specific email to modify
- Label Name: The label to remove
### Outputs
- Result: Confirmation of label removal
- Error: Any error messages if something goes wrong
### Possible use case
Cleaning up email categorization or updating email status after processing.

View File

@@ -0,0 +1,55 @@
# Google Sheets Integration Blocks
## Google Sheets Reader
### What it is
A tool that allows you to retrieve data from Google Sheets spreadsheets.
### What it does
Reads specified ranges of data from a Google Sheets spreadsheet and returns the content in an organized format.
### How it works
1. Connects to Google Sheets using your provided credentials
2. Locates the specified spreadsheet using its ID
3. Retrieves data from the specified range of cells
4. Returns the data as a structured list
### Inputs
- Credentials: Your Google account authentication details
- Spreadsheet ID: The unique identifier for your Google Sheet (found in the sheet's URL)
- Range: The cell range you want to read (e.g., "Sheet1!A1:B2")
### Outputs
- Result: The data retrieved from the spreadsheet, organized in rows and columns
- Error: Any error message if the operation wasn't successful
### Possible use case
Automatically collecting daily sales data from a team's shared spreadsheet for analysis or reporting.
## Google Sheets Writer
### What it is
A tool that enables you to write data into Google Sheets spreadsheets.
### What it does
Writes data into specified ranges of a Google Sheets spreadsheet, updating or replacing existing content.
### How it works
1. Connects to Google Sheets using your provided credentials
2. Locates the specified spreadsheet using its ID
3. Updates the specified range with your provided data
4. Returns information about the update operation
### Inputs
- Credentials: Your Google account authentication details
- Spreadsheet ID: The unique identifier for your Google Sheet (found in the sheet's URL)
- Range: The cell range where you want to write data (e.g., "Sheet1!A1:B2")
- Values: The data you want to write, organized in rows and columns
### Outputs
- Result: Details about the write operation, including number of cells, rows, and columns updated
- Error: Any error message if the operation wasn't successful
### Possible use case
Automatically updating a project status spreadsheet with daily progress metrics collected from various sources.

View File

@@ -0,0 +1,33 @@
## Google Maps Search
### What it is
A search tool that helps you find and get detailed information about local businesses and places using Google Maps.
### What it does
This tool searches for places based on your search terms and provides comprehensive details about each location found, including contact information, ratings, and website details.
### How it works
When you provide a search query (like "restaurants in New York"), the tool searches within your specified radius and returns detailed information about matching places. It can gather multiple results and provides important details about each location it finds.
### Inputs
- Search Query: What you're looking for and where (e.g., "coffee shops in Seattle")
- Search Radius: How far to look from the center point, up to 50 kilometers (50,000 meters)
- Maximum Results: How many places you want to find, up to 60 locations
- Google Maps API Key: Authentication credentials (handled automatically by the system)
### Outputs
- Business Name: The name of the found location
- Address: The complete street address
- Phone Number: Contact telephone number
- Rating: Customer rating (typically out of 5 stars)
- Review Count: Number of customer reviews
- Website: Business website address (if available)
### Possible use cases
- Finding restaurants in a new city
- Locating nearby services like gas stations or pharmacies
- Researching hotels in a specific area
- Discovering highly-rated businesses in your neighborhood
- Creating lists of local attractions for tourists
- Finding specific types of businesses within walking distance

View File

@@ -0,0 +1,33 @@
## HTTP Request Handler
### What it is
A utility component that simplifies the process of making HTTP GET requests to web services and APIs.
### What it does
Fetches data from specified web addresses (URLs) and returns the response in either JSON format or plain text, depending on your needs.
### How it works
When given a web address, this component sends a request to that address, optionally including any specified headers. It then processes the response and returns it in your preferred format (either structured JSON data or plain text).
### Inputs
- URL: The web address you want to fetch data from
- Headers: Optional additional information to send with the request (such as authentication tokens or content preferences)
- JSON Flag: A simple yes/no option that determines whether the response should be processed as JSON data
### Outputs
- When JSON is requested: Structured data that can be easily processed
- When JSON is not requested: Plain text content from the web address
### Possible use cases
- Fetching weather data from a weather service API
- Retrieving user profile information from a social media platform
- Downloading content from web services
- Checking the status of external services
- Integrating with third-party data providers
### Best Practices
- Always provide valid URLs
- Include appropriate headers when accessing secured services
- Use JSON output when working with API responses
- Handle potential connection errors appropriately

View File

@@ -0,0 +1,36 @@
## Web Request Sender
### What it is
A communication tool that allows you to send requests to web services and APIs, handling both the sending of information and receiving of responses.
### What it does
This block establishes connections with external web services, sends data in various formats, and processes the responses. It can handle different types of web requests and automatically manages how data is formatted and processed.
### How it works
1. Takes your request details (URL, method, data)
2. Formats the information appropriately (JSON or plain text)
3. Sends the request to the specified web service
4. Receives the response
5. Categorizes the response based on success or type of error
6. Returns the processed result
### Inputs
- URL: The web address you want to send the request to (Example: https://api.example.com)
- Method: The type of request to make (GET, POST, PUT, DELETE, etc.)
- Headers: Additional information to send with your request (Optional)
- JSON Format: Whether to treat the data as JSON or plain text (Default: Yes)
- Body: The main content you want to send with your request (Optional)
### Outputs
- Response: The information received when the request is successful
- Client Error: Information about what went wrong if there's a problem with your request
- Server Error: Information about what went wrong if there's a problem with the server
### Possible use cases
- Fetching weather data from a weather service
- Sending user information to a registration system
- Retrieving product information from an e-commerce API
- Updating customer records in a remote database
- Checking the status of a service
- Integrating with social media platforms

View File

@@ -0,0 +1,34 @@
## HubSpot Company Manager
### What it is
A tool that helps you manage company information in HubSpot's CRM system, allowing you to create, update, and retrieve company records.
### What it does
This component handles all basic operations related to company data in HubSpot, including:
- Creating new company profiles
- Updating existing company information
- Retrieving company details using domain names
### How it works
The tool connects to your HubSpot account and performs the requested operation:
1. When creating a company, it adds a new company record with your provided information
2. When retrieving company details, it searches using the company's domain name
3. When updating, it first finds the company by domain name, then applies your changes
### Inputs
- Credentials: Your HubSpot account authentication details
- Operation: The action you want to perform ("create", "update", or "get")
- Company Data: Information about the company (like name, industry, size, etc.) when creating or updating records
- Domain: The company's website domain (e.g., "example.com") for finding specific companies
### Outputs
- Company: The company information returned by HubSpot
- Status: The result of your requested operation (e.g., "created", "updated", "retrieved", or "company_not_found")
### Possible use cases
- Setting up a new customer database by creating multiple company records
- Updating company information after receiving new details from a sales team
- Automatically retrieving company details when a new lead comes in
- Synchronizing company data between HubSpot and other business systems
- Building automated workflows that need to access or modify company information

View File

@@ -0,0 +1,34 @@
## HubSpot Contact Manager
### What it is
A powerful tool that helps you manage contact information in your HubSpot CRM system. It serves as a bridge between your application and HubSpot's contact database.
### What it does
This component allows you to perform three essential contact management operations:
- Create new contacts in HubSpot
- Update existing contact information
- Retrieve contact details using email addresses
### How it works
The tool connects to your HubSpot account using secure credentials and performs the requested operation:
1. When creating contacts, it adds new contact information to your HubSpot database
2. When updating, it first looks up the contact by email, then modifies their information
3. When retrieving contacts, it searches using the provided email address and returns the matching contact's details
### Inputs
- Credentials: Your HubSpot account access information
- Operation: The action you want to perform ("create", "update", or "get")
- Contact Data: The information you want to store or update for a contact
- Email: The contact's email address (required for updating or retrieving contacts)
### Outputs
- Contact: The contact's information, including any fields stored in HubSpot
- Status: The result of your operation (such as "created", "updated", "retrieved", or "contact_not_found")
### Possible use cases
1. Customer Registration: Automatically create new HubSpot contacts when customers sign up on your website
2. Profile Updates: Update contact information when customers modify their profiles
3. Contact Lookup: Retrieve customer details for personalized service interactions
4. Data Synchronization: Keep your local database in sync with HubSpot contacts
5. Lead Management: Create and update leads in your CRM system automatically

View File

@@ -0,0 +1,41 @@
## HubSpot Engagement Manager
### What it is
A tool that helps manage and track customer interactions through HubSpot's platform, focusing on email communications and engagement analysis.
### What it does
- Sends emails to contacts through HubSpot
- Tracks how contacts interact with your emails
- Measures engagement levels over time
- Calculates engagement scores based on customer interactions
### How it works
The tool connects to your HubSpot account and can perform two main functions:
1. Email Sending: Creates and sends emails to your contacts through HubSpot's system
2. Engagement Tracking: Monitors how contacts interact with your emails by collecting data about opens, clicks, and replies
### Inputs
- Credentials: Your HubSpot account authentication details
- Operation: Choose between "send_email" or "track_engagement"
- Email Data: Information needed for sending emails (recipient, subject, content)
- Contact ID: The unique identifier for the contact you want to track
- Timeframe Days: How many days back you want to analyze engagement (default is 30 days)
### Outputs
- Result: Contains either email sending confirmation or detailed engagement metrics
- Status: Indicates whether the operation was successful ("email_sent" or "engagement_tracked")
### Engagement Metrics Include
- Email opens: How many times your emails were opened
- Email clicks: How many times links in your emails were clicked
- Email replies: How many times contacts replied to your emails
- Last engagement: The most recent interaction time
- Engagement score: A calculated value based on different types of interactions
### Possible use cases
1. Marketing Campaign Monitoring: Track how well your email campaigns are performing by measuring customer engagement over time
2. Automated Email Communications: Set up automated email sends to specific contacts
3. Customer Engagement Analysis: Evaluate how actively different contacts interact with your communications
4. ROI Measurement: Calculate the effectiveness of your email marketing efforts through engagement scoring

View File

@@ -0,0 +1,38 @@
## Ideogram Model
### What it is
A powerful AI image generation tool that creates custom images based on text descriptions using Ideogram's advanced AI models.
### What it does
Transforms text descriptions into high-quality images with customizable settings for style, aspect ratio, and color schemes. It can also enhance image quality through AI upscaling.
### How it works
1. Takes your text description and image preferences
2. Sends the request to Ideogram's AI system
3. Processes your requirements using the selected AI model
4. Returns a URL containing your generated image
5. Optionally upscales the image for higher quality
### Inputs
- Prompt: Your text description of the desired image
- Image Generation Model: Choice of AI model version (V1, V2, etc.)
- Aspect Ratio: The shape and dimensions of your image
- Upscale Image: Option to enhance image quality
- Magic Prompt Option: Setting to automatically enhance your text prompt
- Seed: Optional number for reproducible results
- Style Type: Visual style preference (Realistic, Anime, 3D Render, etc.)
- Negative Prompt: Description of elements to exclude from the image
- Color Palette Preset: Predefined color schemes (Fresh, Jungle, Magic, etc.)
### Outputs
- Result: Web link to your generated image
- Error: Message explaining any issues that occurred
### Possible use cases
- Creating unique artwork for social media posts
- Generating product visualization concepts
- Designing marketing materials
- Producing story illustrations
- Developing concept art for projects
- Creating custom visual content for presentations

View File

@@ -0,0 +1,31 @@
## Step Through Items
### What it is
A logical component that helps you process lists or collections of items one at a time, like going through items in a shopping list one by one.
### What it does
Takes a collection of items (either as a list or a dictionary) and processes them individually, outputting each item along with its position or identifier in the sequence.
### How it works
The component accepts your collection of items and then:
1. Checks if you've provided items in any of the accepted formats
2. Goes through each item one by one
3. For each item, provides both the item itself and its position/identifier
4. Continues until all items have been processed
### Inputs
- Items: A list of items you want to process (like [1, 2, 3] or ["apple", "banana", "orange"])
- Items Object: A dictionary of items with labels (like {"fruit": "apple", "vegetable": "carrot"})
- Items String: A text version of your list or dictionary, useful when your data comes as formatted text
### Outputs
- Item: The current item being processed from your collection
- Key: The position (for lists) or label (for dictionaries) of the current item
### Possible use cases
- Processing a list of customer orders one at a time
- Going through a collection of files in a folder
- Handling survey responses individually
- Processing inventory items one by one
- Working through a series of tasks in sequence

View File

@@ -0,0 +1,30 @@
## Jina Text Chunking
### What it is
A specialized tool that breaks down large texts into smaller, manageable pieces using Jina AI's advanced text segmentation service.
### What it does
This component takes long texts and intelligently divides them into smaller chunks while maintaining context and meaning. It can also provide detailed information about how the text was divided if needed.
### How it works
1. The tool connects to Jina AI's segmentation service using your security credentials
2. It processes each text you provide, splitting it according to your specifications
3. It returns the smaller chunks and, if requested, information about how the text was divided
4. All chunks maintain context and are sized according to your requirements
### Inputs
- Texts: A collection of text documents you want to divide into smaller pieces
- Credentials: Your Jina AI security credentials for accessing the service
- Maximum Chunk Length: The largest size you want for each chunk (default is 1000 characters)
- Return Tokens: Option to receive detailed information about how the text was divided (default is No)
### Outputs
- Chunks: The collection of smaller text segments created from your original texts
- Tokens: (Optional) Detailed information about how each piece of text was divided
### Possible use cases
- Breaking down large documents like research papers into more manageable sections
- Preparing content for AI analysis systems that work better with smaller text segments
- Creating summaries or excerpts from longer documents
- Processing large amounts of text data for content management systems

View File

@@ -0,0 +1,30 @@
## Jina Embedding
### What it is
A specialized tool that converts text into numerical representations (embeddings) using Jina AI's technology. These numerical representations capture the meaning and context of the text in a format that computers can process effectively.
### What it does
Transforms a list of text inputs into their corresponding mathematical representations, making it possible to analyze and compare texts based on their meaning rather than just their exact words.
### How it works
1. Accepts a list of texts you want to process
2. Connects securely to Jina AI's service using your credentials
3. Sends the texts to be processed by the specified Jina model
4. Receives and organizes the mathematical representations of your texts
5. Returns the processed embeddings in a structured format
### Inputs
- Texts: A collection of text pieces you want to convert into embeddings
- Credentials: Your Jina AI access credentials (required for using the service)
- Model: The specific Jina embedding model to use (automatically set to "jina-embeddings-v2-base-en" if not specified)
### Outputs
- Embeddings: A list of numerical representations corresponding to your input texts
### Possible use cases
- Building a smart document search system that can find relevant documents based on meaning, not just keywords
- Creating a content recommendation system that suggests similar articles or posts
- Developing a text classification system that can automatically categorize documents
- Implementing a plagiarism detection system that can identify similar content
- Creating a chatbot that can understand and respond to user queries based on semantic meaning

View File

@@ -0,0 +1,25 @@
## Fact Checker
### What it is
A tool that analyzes statements and determines their factual accuracy using Jina AI's advanced fact-checking technology.
### What it does
This component examines a given statement and provides a comprehensive assessment of its truthfulness, including a numerical score, a yes/no result, and a detailed explanation of the reasoning behind the assessment.
### How it works
When you provide a statement, the system sends it to Jina AI's specialized fact-checking service. The service analyzes the statement against its knowledge base and returns a detailed evaluation of the statement's factuality. The results are presented in an easy-to-understand format that includes both numerical and written assessments.
### Inputs
- Statement: The text you want to verify (such as "The Earth is round" or "Paris is the capital of Italy")
- Credentials: Your Jina AI account information (automatically handled by the system)
### Outputs
- Factuality Score: A number that indicates how factual the statement is (higher numbers mean more factual)
- Result: A simple yes/no determination of whether the statement is factual
- Reason: A written explanation of why the statement was determined to be true or false
- Error Message: If something goes wrong during the checking process, you'll receive an explanation of what happened
### Possible use cases
A news organization could use this tool to quickly verify facts in their articles before publication. For example, if a reporter writes "Company X announced record profits in 2023," the fact checker could verify this statement against reliable sources and provide a confidence score along with supporting evidence or contradictions.

View File

@@ -0,0 +1,47 @@
# Web Search and Content Extraction Blocks
## Search The Web
### What it is
A tool that performs web searches using the Jina.ai search engine.
### What it does
This block takes a search query and returns comprehensive results from across the internet, including content from the top 5 relevant URLs.
### How it works
When you provide a search query, the block sends it to Jina.ai's search service, which scans the internet and returns the most relevant results. The process is similar to using a regular search engine, but it's designed to be integrated into automated workflows.
### Inputs
- Credentials: Your Jina.ai authentication details
- Query: The text you want to search for on the internet
### Outputs
- Results: A collection of search findings including content from the most relevant web pages
- Error: Any error message if the search wasn't successful
### Possible use case
A researcher could use this block to automatically gather information about a specific topic, such as "renewable energy trends," and receive comprehensive search results from multiple sources in one go.
## Extract Website Content
### What it is
A tool that extracts readable content from any website URL.
### What it does
This block visits a specified webpage and pulls out the main content, removing unnecessary elements like advertisements and navigation menus.
### How it works
When given a URL, the block either performs a direct content extraction (raw mode) or uses Jina.ai's specialized reader service to intelligently extract the most relevant content from the webpage.
### Inputs
- Credentials: Your Jina.ai authentication details
- URL: The web address of the page you want to extract content from
- Raw Content: A toggle that determines whether to use basic extraction or Jina.ai's enhanced reader (advanced option)
### Outputs
- Content: The extracted text from the webpage
- Error: Any error message if the content couldn't be retrieved
### Possible use case
A content curator could use this block to automatically extract articles from various news websites, getting clean, readable content without the clutter of advertisements and sidebars.

View File

@@ -0,0 +1,128 @@
# Language Model Blocks Documentation
## AI Structured Response Generator
### What it is
A sophisticated AI component that generates structured, formatted responses using various language models.
### What it does
Converts user prompts into structured data responses, ensuring the output follows a specific format defined by the user.
### How it works
The component sends your prompt to an AI model, along with formatting instructions, and ensures the response matches your required structure. It will retry multiple times if the response isn't properly formatted.
### Inputs
- Prompt: The main question or instruction for the AI
- Expected Format: The structure you want the response to follow
- Model: Choice of AI model to use
- System Prompt: Additional context or instructions for the AI
- Conversation History: Previous messages for context
- Retry Count: Number of attempts to get a properly formatted response
- Prompt Values: Variables to insert into the prompt
### Outputs
- Response: The structured data response from the AI
- Error: Any error messages if the process fails
### Possible use case
Converting unstructured customer feedback into categorized data with specific fields like sentiment, main topics, and action items.
## AI Text Generator
### What it is
A straightforward tool for generating natural language text responses.
### What it does
Processes your prompt and returns a natural language response without any specific formatting requirements.
### How it works
Sends your prompt to an AI model and returns the response as plain text, handling all the technical details of the AI interaction.
### Inputs
- Prompt: Your question or instruction
- Model: Choice of AI model
- System Prompt: Additional context or instructions
- Prompt Values: Variables to insert into the prompt
### Outputs
- Response: The generated text
- Error: Any error messages
### Possible use case
Generating product descriptions, creative writing, or answering general questions.
## AI Text Summarizer
### What it is
A tool that condenses long texts into shorter, meaningful summaries.
### What it does
Processes long pieces of text and creates concise summaries while maintaining the most important information.
### How it works
Breaks down long text into manageable chunks, summarizes each chunk, and then combines these summaries into a final, coherent summary.
### Inputs
- Text: The long text to summarize
- Model: Choice of AI model
- Focus: Specific topic to focus on in the summary
- Style: Format of the summary (concise, detailed, bullet points, or numbered list)
- Max Tokens: Maximum length of the summary
- Chunk Overlap: How much overlap to maintain between chunks for context
### Outputs
- Summary: The final summarized text
- Error: Any error messages
### Possible use case
Summarizing long research papers, articles, or reports into brief executive summaries.
## AI Conversation
### What it is
A tool for managing multi-turn conversations with AI models.
### What it does
Maintains a conversation thread with an AI, keeping track of context and previous messages.
### How it works
Sends the entire conversation history to the AI model with each new message, ensuring responses remain contextually relevant.
### Inputs
- Messages: List of previous conversation messages
- Model: Choice of AI model
- Max Tokens: Maximum length of responses
### Outputs
- Response: The AI's reply to the conversation
- Error: Any error messages
### Possible use case
Creating interactive chatbots or virtual assistants that maintain context throughout a conversation.
## AI List Generator
### What it is
A specialized tool for creating lists from text or prompts.
### What it does
Generates organized lists based on provided information or creates new lists based on specific topics.
### How it works
Analyzes source data or follows prompt instructions to create structured lists, validating the format and ensuring proper list generation.
### Inputs
- Focus: The main topic or purpose of the list
- Source Data: Optional text to extract list items from
- Model: Choice of AI model
- Max Retries: Number of attempts to generate a valid list
- Max Tokens: Maximum length of the generated list
### Outputs
- Generated List: The complete list of items
- List Item: Individual items from the list
- Error: Any error messages
### Possible use case
Extracting key points from meeting notes or creating organized lists of items from unstructured text data.

View File

@@ -0,0 +1,45 @@
# Mathematical Operation Blocks
## Calculator
### What it is
A versatile mathematical calculator that performs basic arithmetic operations between two numbers.
### What it does
Takes two numbers and performs one of five mathematical operations: addition, subtraction, multiplication, division, or raising to a power. It can also optionally round the result to the nearest whole number.
### How it works
The calculator takes your chosen operation and two numbers, processes them according to the selected mathematical operation, and returns the result. It includes safety features like handling division by zero and provides special values for error cases.
### Inputs
- Operation: Choose from Add, Subtract, Multiply, Divide, or Power
- Number A: The first number in your calculation
- Number B: The second number in your calculation
- Round Result: Choose whether to round the final answer to a whole number
### Outputs
- Result: The numerical outcome of your calculation
### Possible use case
A teacher creating a grading system could use this to calculate final scores by adding up test results or computing weighted averages.
## Count Items
### What it is
A counting tool that determines how many items are in any collection of data.
### What it does
Counts the number of elements in various types of collections, such as lists, text strings, or sets of items.
### How it works
The counter examines your provided collection and determines its size, returning the total number of items it contains. It works with different types of collections and provides error handling for invalid inputs.
### Inputs
- Collection: The group of items you want to count (can be a list, text string, dictionary, or similar)
### Outputs
- Count: The number of items found in the collection
### Possible use case
An inventory manager could use this to quickly count the number of products in a list or the number of characters in a product description.

View File

@@ -0,0 +1,36 @@
## Publish to Medium
### What it is
A tool that enables automatic publication of content to Medium's blogging platform, allowing for various publication settings and content management options.
### What it does
This block takes your content and publishes it to Medium as a new post. It can handle different types of content formats, manage publication status, and provides options for content licensing and reader notifications.
### How it works
The block connects to your Medium account using your credentials, processes your content according to your specified settings, and creates a new post on Medium. It then returns the details of the published post, including its URL and publication time.
### Inputs
- Author ID: Your unique Medium author identifier
- Title: The headline for your Medium post
- Content: The main body of your post
- Content Format: Specify whether your content is in HTML or Markdown format
- Tags: Up to 5 keywords to categorize your post
- Original URL: Optional link to where the content was first published
- Publication Status: Choose between public, draft, or unlisted
- License Type: Specify the copyright terms for your content
- Notify Followers: Choose whether to alert your followers about the new post
- Credentials: Your Medium API access information
### Outputs
- Post ID: A unique identifier for your published post
- Post URL: The direct link to your post on Medium
- Publication Time: When the post was published
- Error Message: Information about any issues that occurred during publication
### Possible use cases
- Automatically republish blog content to Medium
- Create draft posts for team review before publication
- Maintain a consistent content schedule across platforms
- Cross-post content while maintaining proper attribution
- Schedule content releases with specific visibility settings

View File

@@ -0,0 +1,33 @@
## Nvidia Deepfake Detector
### What it is
A specialized tool that analyzes images to determine if they have been artificially created or manipulated using deepfake technology.
### What it does
This component examines uploaded images using Nvidia's advanced AI technology to detect signs of artificial manipulation or generation. It provides a confidence score indicating how likely an image is to be a deepfake, and can optionally return a marked version of the image highlighting areas of potential manipulation.
### How it works
When you submit an image, the system:
1. Securely sends your image to Nvidia's AI analysis service
2. Processes the image using advanced detection algorithms
3. Evaluates the likelihood of artificial manipulation
4. Returns results including a probability score and optional marked image
5. Provides a status update on the analysis process
### Inputs
- Image: The digital image you want to analyze for potential manipulation
- Return Image Option: Choose whether you want to receive a marked version of the analyzed image
- Credentials: Your Nvidia API access credentials (handled automatically by the system)
### Outputs
- Detection Status: Indicates if the analysis was successful, encountered an error, or was filtered for content
- Deepfake Probability: A score between 0 and 1 indicating how likely the image is to be artificially manipulated (higher numbers indicate greater likelihood of manipulation)
- Processed Image: If requested, a version of your image with visual indicators showing areas of potential manipulation
### Possible use cases
- A news organization verifying the authenticity of submitted photographs
- A social media platform automatically screening uploaded content for manipulated images
- A forensics team analyzing evidence for potential digital tampering
- A content moderation team reviewing user-submitted materials
- A fact-checking organization verifying viral images

Some files were not shown because too many files have changed in this diff Show More