slack-ruby-client 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/prompts/changelog-entries.prompt.yml +32 -0
  3. data/.github/workflows/danger-comment.yml +11 -0
  4. data/.github/workflows/danger.yml +13 -0
  5. data/.github/workflows/lint.yml +3 -1
  6. data/.github/workflows/test.yml +1 -1
  7. data/.github/workflows/update_api.yml +94 -16
  8. data/.rubocop.yml +2 -1
  9. data/.rubocop_todo.yml +18 -4
  10. data/AGENTS.md +12 -0
  11. data/CHANGELOG.md +43 -1
  12. data/CONTRIBUTING.md +10 -1
  13. data/Dangerfile +4 -1
  14. data/Gemfile +20 -5
  15. data/README.md +47 -2
  16. data/RELEASING.md +2 -2
  17. data/Rakefile +7 -3
  18. data/UPGRADING.md +16 -0
  19. data/bin/commands/admin_analytics_messages.rb +36 -0
  20. data/bin/commands/admin_apps.rb +3 -0
  21. data/bin/commands/admin_apps_mcp_servers.rb +21 -0
  22. data/bin/commands/admin_apps_mcp_servers_permissions.rb +33 -0
  23. data/bin/commands/admin_apps_permissions.rb +58 -0
  24. data/bin/commands/admin_conversations.rb +20 -0
  25. data/bin/commands/admin_functions.rb +1 -1
  26. data/bin/commands/admin_functions_permissions.rb +1 -0
  27. data/bin/commands/admin_usergroups.rb +74 -0
  28. data/bin/commands/admin_users.rb +10 -8
  29. data/bin/commands/admin_users_session.rb +0 -1
  30. data/bin/commands/admin_workflows_triggers_types_permissions.rb +4 -3
  31. data/bin/commands/agents_sessions.rb +38 -0
  32. data/bin/commands/apps_icon.rb +22 -0
  33. data/bin/commands/apps_managed_permissions.rb +21 -0
  34. data/bin/commands/apps_manifest.rb +7 -6
  35. data/bin/commands/apps_user_connection.rb +21 -0
  36. data/bin/commands/assistant_search.rb +3 -2
  37. data/bin/commands/assistant_threads.rb +6 -3
  38. data/bin/commands/blocks.rb +22 -0
  39. data/bin/commands/bookmarks.rb +6 -6
  40. data/bin/commands/canvases.rb +12 -2
  41. data/bin/commands/chat.rb +17 -7
  42. data/bin/commands/conversations.rb +2 -2
  43. data/bin/commands/conversations_canvases.rb +1 -1
  44. data/bin/commands/entity.rb +27 -0
  45. data/bin/commands/files.rb +5 -2
  46. data/bin/commands/oauth_v2.rb +22 -0
  47. data/bin/commands/oauth_v2_user.rb +27 -0
  48. data/bin/commands/openid_connect.rb +1 -0
  49. data/bin/commands/slackLists_download.rb +6 -0
  50. data/bin/commands/slackLists_items.rb +1 -0
  51. data/bin/commands/users.rb +3 -2
  52. data/bin/commands/views.rb +1 -0
  53. data/bin/slack +5 -1
  54. data/examples/files_upload_v2/files_upload_v2.rb +1 -1
  55. data/examples/hi_web/hi.rb +1 -1
  56. data/examples/new_ticket/new_ticket.rb +1 -1
  57. data/examples/oauth_v2/oauth_v2.rb +6 -6
  58. data/lib/slack/events/config.rb +1 -1
  59. data/lib/slack/events/request.rb +2 -2
  60. data/lib/slack/version.rb +1 -1
  61. data/lib/slack/web/api/endpoints/admin_analytics_messages.rb +64 -0
  62. data/lib/slack/web/api/endpoints/admin_apps.rb +6 -0
  63. data/lib/slack/web/api/endpoints/admin_apps_mcp_servers.rb +31 -0
  64. data/lib/slack/web/api/endpoints/admin_apps_mcp_servers_permissions.rb +46 -0
  65. data/lib/slack/web/api/endpoints/admin_apps_permissions.rb +83 -0
  66. data/lib/slack/web/api/endpoints/admin_conversations.rb +30 -0
  67. data/lib/slack/web/api/endpoints/admin_emoji.rb +0 -1
  68. data/lib/slack/web/api/endpoints/admin_functions.rb +1 -1
  69. data/lib/slack/web/api/endpoints/admin_functions_permissions.rb +2 -1
  70. data/lib/slack/web/api/endpoints/admin_usergroups.rb +111 -0
  71. data/lib/slack/web/api/endpoints/admin_users.rb +11 -6
  72. data/lib/slack/web/api/endpoints/admin_users_session.rb +0 -2
  73. data/lib/slack/web/api/endpoints/admin_workflows_triggers_types_permissions.rb +3 -3
  74. data/lib/slack/web/api/endpoints/agents_sessions.rb +54 -0
  75. data/lib/slack/web/api/endpoints/apps_icon.rb +28 -0
  76. data/lib/slack/web/api/endpoints/apps_managed_permissions.rb +27 -0
  77. data/lib/slack/web/api/endpoints/apps_manifest.rb +5 -3
  78. data/lib/slack/web/api/endpoints/apps_user_connection.rb +27 -0
  79. data/lib/slack/web/api/endpoints/assistant_search.rb +3 -1
  80. data/lib/slack/web/api/endpoints/assistant_threads.rb +9 -4
  81. data/lib/slack/web/api/endpoints/blocks.rb +28 -0
  82. data/lib/slack/web/api/endpoints/bookmarks.rb +6 -12
  83. data/lib/slack/web/api/endpoints/canvases.rb +16 -2
  84. data/lib/slack/web/api/endpoints/chat.rb +39 -17
  85. data/lib/slack/web/api/endpoints/conversations.rb +2 -2
  86. data/lib/slack/web/api/endpoints/conversations_canvases.rb +1 -1
  87. data/lib/slack/web/api/endpoints/entity.rb +48 -0
  88. data/lib/slack/web/api/endpoints/files.rb +8 -1
  89. data/lib/slack/web/api/endpoints/oauth_v2.rb +35 -0
  90. data/lib/slack/web/api/endpoints/oauth_v2_user.rb +37 -0
  91. data/lib/slack/web/api/endpoints/openid_connect.rb +2 -0
  92. data/lib/slack/web/api/endpoints/slackLists_download.rb +12 -0
  93. data/lib/slack/web/api/endpoints/slackLists_items.rb +2 -0
  94. data/lib/slack/web/api/endpoints/users.rb +3 -2
  95. data/lib/slack/web/api/endpoints/views.rb +1 -0
  96. data/lib/slack/web/api/endpoints.rb +20 -0
  97. data/lib/slack/web/api/errors/server_error.rb +1 -1
  98. data/lib/slack/web/api/errors/slack_error.rb +1 -1
  99. data/lib/slack/web/api/errors/too_many_requests_error.rb +1 -1
  100. data/lib/slack/web/api/errors.rb +230 -6
  101. data/lib/slack/web/api/helpers/files.rb +2 -2
  102. data/lib/slack/web/api/mixins/conversations.id.rb +2 -1
  103. data/lib/slack/web/api/mixins/ids.id.rb +3 -1
  104. data/lib/slack/web/api/mixins/users.id.rb +2 -1
  105. data/lib/slack/web/api/mixins/users.search.rb +1 -1
  106. data/lib/slack/web/api/patches/files.default-filename.patch +12 -0
  107. data/lib/slack/web/api/templates/method.erb +3 -1
  108. data/lib/slack/web/api/templates/method_spec.erb +32 -3
  109. data/lib/slack/web/client.rb +29 -0
  110. data/lib/slack/web/faraday/request.rb +22 -2
  111. data/lib/tasks/web.rake +5 -2
  112. data/slack-ruby-client.gemspec +0 -1
  113. metadata +32 -19
  114. data/.github/workflows/pr_lint.yml +0 -21
  115. data/Gemfile.danger +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2adaaa262bea7ceba8032b2a8ff54c179e1a972a94a34c1ca9fb526fb2e82728
4
- data.tar.gz: 12da6b07266549b471b6ca7e07ccb525269a074f0f4f0b8f8fc9fc9563c4a620
3
+ metadata.gz: 41ed3cba2bb8122e285978e01bdcc9ce1e66c8572734bf90f9bd9c048fab5b27
4
+ data.tar.gz: 9cd4ec9ee0d7c8ab512491700e7e78133a099ef0be1ed553e63cf766f487f835
5
5
  SHA512:
6
- metadata.gz: 2998c891be3fc2195b24ff696989fd748695f3350a9f8db14672fc94a66519aac0a3c332f5002adda414a628574094348de2de6b5010fd06cfe61e50fc0eeb08
7
- data.tar.gz: 13ece08f3789dbed0edbc3c5e3a150ce171a8b89585b554710165af55008eb798b5a08c7062a5a7735eb0082f67818f02ea428b6653d6959609881bc6794fc17
6
+ metadata.gz: acf9e865def155ca75f77079bafb8abd1382943a3de640ed2d9eb8462712dfd48c49d61c603872b97158937ae8b769b5f02a976961590f79973369f7cf770b90
7
+ data.tar.gz: d38d2f774122deb06c9c00c788d511bf9fe2af8664c01f87edce0121f2f23669bbf528784b922f7cbcb4a35e5bdc83a2909038d6635c2ce45fb4001d0762411b
@@ -0,0 +1,32 @@
1
+ messages:
2
+ - role: system
3
+ content: |-
4
+ You write CHANGELOG entries describing an automated API update to slack-ruby-client, a
5
+ Ruby gem whose Web API endpoint methods, argument validations, specs, and bin commands
6
+ are code-generated from JSON method definitions vendored from
7
+ https://github.com/slack-ruby/slack-api-ref (via a git submodule and rake task).
8
+
9
+ Given a diffstat and a diff of the regenerated files, respond with ONLY a single JSON
10
+ object (no markdown code fences, no other text) in exactly this shape:
11
+ {"entries": [string, ...]}
12
+
13
+ Rules for entries:
14
+ - Each entry is a single short line, imperative mood, no leading bullet/dash and no
15
+ trailing period (the caller adds both).
16
+ - Group related changes together into one entry per notable change or affected method
17
+ group (e.g. one entry for "Add chat.appendStream and chat.stopStream methods", one
18
+ entry for "Add a metadata argument to chat.postEphemeral, chat.postMessage,
19
+ chat.scheduleMessage, and chat.update"), rather than one entry per file.
20
+ - Focus on developer-visible API surface changes: new/removed/renamed methods, new/
21
+ removed/changed arguments (including required/mutually-exclusive validation changes),
22
+ new/removed error codes, and behavioral changes to generated code.
23
+ - Skip purely mechanical noise (e.g. reformatted docs/comments with no semantic change).
24
+ - Produce at most 8 entries. If there are no notable API surface changes, return a single
25
+ generic entry describing the update in one line.
26
+ - role: user
27
+ content: |-
28
+ Diffstat:
29
+ {{diff_stat}}
30
+
31
+ Diff (may be truncated):
32
+ {{diff}}
@@ -0,0 +1,11 @@
1
+ name: Danger Comment
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: [Danger]
6
+ types: [completed]
7
+
8
+ jobs:
9
+ comment:
10
+ uses: numbata/danger-pr-comment/.github/workflows/danger-comment.yml@v0.1.0
11
+ secrets: inherit
@@ -0,0 +1,13 @@
1
+ name: Danger
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
6
+
7
+ jobs:
8
+ danger:
9
+ uses: numbata/danger-pr-comment/.github/workflows/danger-run.yml@v0.1.0
10
+ secrets: inherit
11
+ with:
12
+ ruby-version: '4.0'
13
+ bundler-cache: true
@@ -6,9 +6,11 @@ jobs:
6
6
  runs-on: ubuntu-latest
7
7
  steps:
8
8
  - name: Checkout
9
- uses: actions/checkout@v5
9
+ uses: actions/checkout@v7
10
10
  - name: Set up Ruby
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
+ ruby-version: '3.3'
14
+ bundler: latest
13
15
  bundler-cache: true
14
16
  - run: bundle exec rubocop
@@ -17,7 +17,7 @@ jobs:
17
17
  name: test (ruby=${{ matrix.entry.ruby }})
18
18
  steps:
19
19
  - name: Checkout
20
- uses: actions/checkout@v5
20
+ uses: actions/checkout@v7
21
21
  - name: Set up Ruby
22
22
  uses: ruby/setup-ruby@v1
23
23
  with:
@@ -11,7 +11,7 @@ jobs:
11
11
  contents: write
12
12
  pull-requests: write
13
13
  steps:
14
- - uses: actions/checkout@v5
14
+ - uses: actions/checkout@v7
15
15
  with:
16
16
  submodules: recursive
17
17
  fetch-depth: 0
@@ -28,10 +28,71 @@ jobs:
28
28
  run: rm -rf vendor
29
29
  - name: Get current date
30
30
  id: date
31
- run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
31
+ run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
32
32
  - name: Get slack-api-ref ref
33
33
  id: api-ref
34
- run: echo "::set-output name=api-ref::$(git rev-parse --short HEAD:lib/slack/web/api/slack-api-ref)"
34
+ run: echo "api-ref=$(git rev-parse --short HEAD:lib/slack/web/api/slack-api-ref)" >> $GITHUB_OUTPUT
35
+ - name: Check for changes
36
+ id: changes
37
+ run: |
38
+ if git diff --quiet; then
39
+ echo "changed=false" >> "$GITHUB_OUTPUT"
40
+ else
41
+ echo "changed=true" >> "$GITHUB_OUTPUT"
42
+ fi
43
+ - name: Prepare diff for AI summary
44
+ if: steps.changes.outputs.changed == 'true'
45
+ run: |
46
+ git diff --stat | sed 's/^/ /' > /tmp/diff_stat.txt
47
+ git diff | head -c 20000 | sed 's/^/ /' > /tmp/diff.txt
48
+ - name: Set up Node
49
+ if: steps.changes.outputs.changed == 'true'
50
+ uses: actions/setup-node@v7
51
+ - name: Install Copilot CLI
52
+ if: steps.changes.outputs.changed == 'true'
53
+ run: npm install -g @github/copilot
54
+ - name: Generate changelog entries with AI
55
+ if: steps.changes.outputs.changed == 'true'
56
+ id: ai
57
+ uses: actions/ai-inference@v3
58
+ with:
59
+ prompt-file: ./.github/prompts/changelog-entries.prompt.yml
60
+ model: ''
61
+ file_input: |
62
+ diff_stat: /tmp/diff_stat.txt
63
+ diff: /tmp/diff.txt
64
+ env:
65
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT }}
66
+ - name: Build changelog entries
67
+ id: entries
68
+ run: |
69
+ entries=""
70
+ if [ "${{ steps.changes.outputs.changed }}" = "true" ]; then
71
+ response_file="${{ steps.ai.outputs.response-file }}"
72
+ echo "AI changelog response:"
73
+ cat "$response_file" || true
74
+ # Copilot CLI's response file may include narration/thinking lines
75
+ # before the final JSON answer, so grab the last line that looks
76
+ # like a JSON object rather than trying to parse the whole file.
77
+ json_line="$(grep -E '^\{.*\}$' "$response_file" 2>/dev/null | tail -n 1)"
78
+ if [ -z "$json_line" ]; then
79
+ echo "::error::AI changelog response did not contain a JSON object, see response above."
80
+ exit 1
81
+ fi
82
+ entries="$(printf '%s' "$json_line" | jq -r '.entries[]? // empty')"
83
+ fi
84
+ if [ -z "$entries" ]; then
85
+ entries="Update API from slack-api-ref@${{ steps.api-ref.outputs.api-ref }}"
86
+ fi
87
+ body_list="$(printf '%s\n' "$entries" | sed 's/^/- /')"
88
+ {
89
+ echo 'entries<<CHANGELOG_ENTRIES_EOF'
90
+ echo "$entries"
91
+ echo 'CHANGELOG_ENTRIES_EOF'
92
+ echo 'body_list<<CHANGELOG_BODY_EOF'
93
+ echo "$body_list"
94
+ echo 'CHANGELOG_BODY_EOF'
95
+ } >> "$GITHUB_OUTPUT"
35
96
  - name: GitHub App token
36
97
  if: ${{ github.repository == 'slack-ruby/slack-ruby-client' }}
37
98
  id: github_app_token
@@ -39,18 +100,26 @@ jobs:
39
100
  with:
40
101
  app_id: ${{ secrets.CI_APP_ID }}
41
102
  private_key: ${{ secrets.CI_APP_PRIVATE_KEY }}
42
- installation_id: 36985419
103
+ installation_retrieval_mode: id
104
+ installation_retrieval_payload: 36985419
43
105
  - name: Create pull request
44
106
  id: cpr
45
- uses: peter-evans/create-pull-request@v7
107
+ uses: peter-evans/create-pull-request@v8
46
108
  with:
47
- token: ${{ secrets.GITHUB_TOKEN }}
48
- commit-message: Update API from slack-api-ref@${{ steps.api-ref.outputs.api-ref }} (${{ steps.date.outputs.date }})
109
+ token: ${{ steps.github_app_token.outputs.token || secrets.GITHUB_TOKEN }}
110
+ commit-message: |
111
+ Update API from slack-api-ref@${{ steps.api-ref.outputs.api-ref }} (${{ steps.date.outputs.date }})
112
+
113
+ ${{ steps.entries.outputs.body_list }}
49
114
  title: Update API from slack-api-ref@${{ steps.api-ref.outputs.api-ref }}
50
115
  body: |
51
116
  Update API from [slack-api-ref](https://github.com/slack-ruby/slack-api-ref).
52
117
  Rev: ${{ steps.api-ref.outputs.api-ref }}
53
118
  Date: ${{ steps.date.outputs.date }}
119
+
120
+ ## Changes
121
+
122
+ ${{ steps.entries.outputs.body_list }}
54
123
  branch: automated-api-update
55
124
  base: master
56
125
  committer: slack-ruby-ci-bot <noreply@github.com>
@@ -61,21 +130,30 @@ jobs:
61
130
  git fetch origin automated-api-update
62
131
  git checkout automated-api-update
63
132
  - name: Update CHANGELOG
64
- uses: jacobtomlinson/gha-find-replace@v3
65
133
  if: ${{ steps.cpr.outputs.pull-request-number != '' }}
66
- with:
67
- include: CHANGELOG.md
68
- find: "\\* Your contribution here."
69
- replace: "* [#${{steps.cpr.outputs.pull-request-number}}](https://github.com/slack-ruby/slack-ruby-client/pull/${{steps.cpr.outputs.pull-request-number}}): Update API from [slack-api-ref@${{ steps.api-ref.outputs.api-ref }}](https://github.com/slack-ruby/slack-api-ref/commit/${{ steps.api-ref.outputs.api-ref }}) - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).\n* Your contribution here."
134
+ env:
135
+ PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }}
136
+ ENTRIES: ${{ steps.entries.outputs.entries }}
137
+ run: |
138
+ ruby - <<'RUBYEOF'
139
+ pr_number = ENV.fetch("PR_NUMBER")
140
+ pr_url = "https://github.com/slack-ruby/slack-ruby-client/pull/#{pr_number}"
141
+ entries = ENV.fetch("ENTRIES").each_line.map(&:strip).reject(&:empty?)
142
+
143
+ lines = entries.map do |entry|
144
+ "* [##{pr_number}](#{pr_url}): #{entry} - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot)."
145
+ end.join("\n")
146
+
147
+ content = File.read("CHANGELOG.md")
148
+ content = content.sub("* Your contribution here.", "#{lines}\n* Your contribution here.")
149
+ File.write("CHANGELOG.md", content)
150
+ RUBYEOF
70
151
  - name: Commit and Push
71
152
  if: ${{ steps.cpr.outputs.pull-request-number != '' }}
72
153
  run: |
73
154
  git config --local user.name 'slack-ruby-ci-bot'
74
155
  git config --local user.email 'noreply@github.com'
75
- git config --local --unset-all http.https://github.com/.extraheader || true
76
- AUTH=$(echo -n "x-access-token:${{ steps.github_app_token.outputs.token || secrets.GITHUB_TOKEN }}" | base64)
77
- echo "::add-mask::${AUTH}"
78
- git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${AUTH}"
79
156
  git add CHANGELOG.md
80
157
  git commit --amend --no-edit
158
+ git remote set-url origin https://x-access-token:${{ steps.github_app_token.outputs.token || secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
81
159
  git push origin automated-api-update -f
data/.rubocop.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
- require:
3
+ plugins:
4
4
  - rubocop-performance
5
5
  - rubocop-rake
6
6
  - rubocop-rspec
7
+ - rubocop-exception_messages
7
8
 
8
9
  AllCops:
9
10
  TargetRubyVersion: 2.7
data/.rubocop_todo.yml CHANGED
@@ -1,11 +1,21 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-09-21 17:08:14 UTC using RuboCop version 1.26.1.
3
+ # on 2026-07-05 15:28:32 UTC using RuboCop version 1.26.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Remaining offenses after safe autocorrection with modern RuboCop.
10
+ Naming/PredicateMethod:
11
+ Exclude:
12
+ - 'lib/slack/events/request.rb'
13
+ - 'lib/slack/utils/security.rb'
14
+
15
+ Style/DirectiveScope:
16
+ Exclude:
17
+ - 'lib/slack/utils/security.rb'
18
+
9
19
  # Offense count: 1
10
20
  # This cop supports unsafe auto-correction (--auto-correct-all).
11
21
  Lint/NonDeterministicRequireOrder:
@@ -33,6 +43,9 @@ Metrics/CyclomaticComplexity:
33
43
  Metrics/MethodLength:
34
44
  Max: 45
35
45
 
46
+ Metrics/ModuleLength:
47
+ Enabled: false
48
+
36
49
  # Offense count: 1
37
50
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
38
51
  Metrics/ParameterLists:
@@ -96,7 +109,7 @@ RSpec/ContextWording:
96
109
  - 'spec/slack/web/client_spec.rb'
97
110
  - 'spec/slack/web/faraday/request_spec.rb'
98
111
 
99
- # Offense count: 35
112
+ # Offense count: 36
100
113
  # Configuration parameters: CountAsOne.
101
114
  RSpec/ExampleLength:
102
115
  Max: 17
@@ -104,7 +117,7 @@ RSpec/ExampleLength:
104
117
  # Offense count: 10
105
118
  # Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly.
106
119
  # Include: **/*_spec*rb*, **/spec/**/*
107
- RSpec/FilePath:
120
+ RSpec/SpecFilePathFormat:
108
121
  Exclude:
109
122
  - 'spec/slack/version_spec.rb'
110
123
  - 'spec/slack/web/api/endpoints/custom/auth_spec.rb'
@@ -160,7 +173,8 @@ RSpec/SubjectStub:
160
173
  # Configuration parameters: .
161
174
  # SupportedStyles: constant, string
162
175
  RSpec/VerifiedDoubleReference:
163
- EnforcedStyle: string
176
+ Exclude:
177
+ - 'spec/slack/web/faraday/request_spec.rb'
164
178
 
165
179
  # Offense count: 2
166
180
  # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
data/AGENTS.md ADDED
@@ -0,0 +1,12 @@
1
+ # Contributor guidance
2
+
3
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) first for setup, tests, lint, changelog, pull request, and API patch instructions. Keep those instructions there rather than duplicating them here.
4
+
5
+ - Preserve the supported-Ruby test matrix when changing lint dependencies.
6
+ - When upgrading lint tooling, review safe corrections and baseline only remaining unrelated offenses.
7
+ - Exception message corrections can change public error strings. Update matching specs, including handwritten specs under `spec/slack/web/api/endpoints/custom/`.
8
+ - Use lowercase exception messages without a trailing period, including messages passed to `super` in error constructors; the exception-message cops may not detect that form. Preserve upstream Slack error codes and dynamically supplied messages.
9
+ - Load RuboCop extensions through `plugins:`. When upgrading an extension, migrate renamed cops and obsolete parameters, review safe corrections, and preserve existing behavior rather than blindly applying unsafe fixes.
10
+ - Do not edit the API-reference submodule or include existing submodule changes unless requested.
11
+ - The API update task pulls the latest reference and can generate unrelated changes. Verify the patch against both the latest and pinned reference; keep unrelated generated updates out of a focused PR. Custom endpoint specs are handwritten despite their location under `endpoints/`.
12
+ - Do not suppress environment warnings in application code or tests. Report local validation limitations explicitly; CLI specs compare subprocess output and can fail when Bundler emits unrelated warnings.
data/CHANGELOG.md CHANGED
@@ -1,4 +1,46 @@
1
- ### 3.1.0 (Next)
1
+ ### 3.3.0 (2026/10/09)
2
+
3
+ * [#607](https://github.com/slack-ruby/slack-ruby-client/pull/607): Support non-rewindable Rack 3 request bodies when verifying Events API signatures, with WEBrick integration coverage; follows [#515](https://github.com/slack-ruby/slack-ruby-client/pull/515) - [@olleolleolle](https://github.com/olleolleolle), [@dblock](https://github.com/dblock).
4
+ * [#606](https://github.com/slack-ruby/slack-ruby-client/pull/606): Add configured client copies with error callbacks for Slack and transport failures - [@dblock](https://github.com/dblock).
5
+ * [#605](https://github.com/slack-ruby/slack-ruby-client/pull/605): Add configured client copies with request and response callbacks, including pagination - [@dblock](https://github.com/dblock).
6
+ * [#603](https://github.com/slack-ruby/slack-ruby-client/pull/603): Enforce consistent exception messages with rubocop-exception_messages and run lint separately from the test matrix - [@dblock](https://github.com/dblock).
7
+ * [#602](https://github.com/slack-ruby/slack-ruby-client/pull/602): Make gli an optional dependency; only the `slack` command-line client needs it - [@corsonknowles](https://github.com/corsonknowles).
8
+ * [#599](https://github.com/slack-ruby/slack-ruby-client/pull/599): Set a default filename in `files_upload` so Slack displays image previews correctly when none is specified - [@ts-3156](https://github.com/ts-3156), [@dblock](https://github.com/dblock).
9
+ * [#591](https://github.com/slack-ruby/slack-ruby-client/pull/591): Generate AI CHANGELOG entries and PR summaries for automated API update PRs, lock simplecov below 1.1.0 to avoid breaking Coveralls - [@dblock](https://github.com/dblock).
10
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add entity.acknowledgeCommentAction and entity.presentComments methods - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
11
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add admin.apps.mcpServers, admin.apps.mcpServers.permissions, and admin.apps.permissions endpoints - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
12
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add a metadata argument to chat.postEphemeral - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
13
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Remove required :url validation from admin.emoji.add - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
14
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add username, icon_url, and icon_emoji arguments to files.completeUploadExternal - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
15
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add format, include_threads, and include_attachments arguments to slackLists.download.start and slackLists.download.get - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
16
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add exclude_muted argument to users.conversations - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
17
+ * [#590](https://github.com/slack-ruby/slack-ruby-client/pull/590): Add new error codes to errors.rb and update slack-api-ref submodule - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
18
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Add admin.conversations.bulkSetProperties method for bulk-setting channel properties - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
19
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Add admin.usergroups.removeTeams method for removing default workspaces from an organization-wide IDP Group or Admin Group - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
20
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Add unfurl_app_links argument to chat.postMessage - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
21
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Remove required team_id argument from admin.users.session.invalidate - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
22
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Add AgentsSessions and AppsManagedPermissions endpoint modules for agents.sessions and apps.managedPermissions methods - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
23
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Increase conversations.invite users limit from 100 to 1000 - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
24
+ * [#597](https://github.com/slack-ruby/slack-ruby-client/pull/597): Add new Slack API error codes - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
25
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Add channel_ids argument to admin.apps.permissions.add and admin.apps.permissions.remove, and channel_restriction_mode/channel_ids arguments to admin.apps.permissions.set - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
26
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Add oauth.v2.beginShortTokenRotation and oauth.v2.completeShortTokenRotation methods - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
27
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Add session_status argument to chat.stopStream - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
28
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Add include_list argument to slackLists.items.list - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
29
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Update chat.startStream and chat.stopStream chunks argument documentation to mention markdown text, task update, plan update, and blocks chunk objects - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
30
+ * [#600](https://github.com/slack-ruby/slack-ruby-client/pull/600): Add new error codes including AgentPromptDisplayNotAllowed, ChannelRestrictionRequiresAppAccess, ChannelRestrictionsNotAvailable, RotationNotFound, TokenTooLong, TooManyChannels, UserIsExternalGuest, and UserNotAuthenticated - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
31
+ * [#608](https://github.com/slack-ruby/slack-ruby-client/pull/608): Add admin.usergroups.addUsers, admin.usergroups.removeUsers, admin.usergroups.create, admin.usergroups.fetch, admin.usergroups.update, and admin.usergroups.uploadUsers methods - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
32
+ * [#608](https://github.com/slack-ruby/slack-ruby-client/pull/608): Add canvases.getContent method to fetch canvas content as markdown or HTML - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
33
+ * [#608](https://github.com/slack-ruby/slack-ruby-client/pull/608): Add new Slack API error codes to Errors - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
34
+ ### 3.2.0 (2026/07/05)
35
+
36
+ * [#581](https://github.com/slack-ruby/slack-ruby-client/pull/581): Migrate Danger to use danger-pr-comment workflow - [@dblock](https://github.com/dblock).
37
+ * [#583](https://github.com/slack-ruby/slack-ruby-client/pull/583): Not found errors raised by id_for use more specific error classes when they exist - [@eizengan](https://github.com/eizengan).
38
+ * [#585](https://github.com/slack-ruby/slack-ruby-client/pull/585): Fix ci not triggering on automated api update prs - [@Copilot](https://github.com/Copilot).
39
+ * [#588](https://github.com/slack-ruby/slack-ruby-client/pull/588): Fix text/markdown_text mutual exclusion in chat methods - [@dblock](https://github.com/dblock).
40
+ * [#589](https://github.com/slack-ruby/slack-ruby-client/pull/589): Fix Ruby-version-dependent hash literal syntax in generated specs - [@dblock](https://github.com/dblock).
41
+ * [#578](https://github.com/slack-ruby/slack-ruby-client/pull/578): Update API from [slack-api-ref@38cb2f4](https://github.com/slack-ruby/slack-api-ref/commit/38cb2f4) - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
42
+
43
+ ### 3.1.0 (2025/11/15)
2
44
 
3
45
  * [#576](https://github.com/slack-ruby/slack-ruby-client/pull/576): Add support for `blocks` in `files_upload_v2` - [@childish-sambino](https://github.com/childish-sambino).
4
46
  * [#574](https://github.com/slack-ruby/slack-ruby-client/pull/574): Update API from [slack-api-ref@bd5dd34](https://github.com/slack-ruby/slack-api-ref/commit/bd5dd34) - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).
data/CONTRIBUTING.md CHANGED
@@ -59,7 +59,16 @@ We definitely appreciate pull requests that highlight or reproduce a problem, ev
59
59
 
60
60
  Implement your feature or bug fix.
61
61
 
62
- Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop). Run `bundle exec rubocop` and fix any style issues highlighted, auto-correct issues when possible with `bundle exec rubocop -a`. To silence generally ignored issues, including line lengths or code complexity metrics, run `bundle exec rubocop --auto-gen-config`.
62
+ Ruby style is enforced with [RuboCop](https://github.com/rubocop/rubocop), including [exception message conventions](https://github.com/dblock/rubocop-exception_messages). The main Gemfile includes lint dependencies only on Ruby 3.2+ so tests can continue running on older supported Ruby versions.
63
+
64
+ ```bash
65
+ bundle install
66
+ bundle exec rubocop
67
+ ```
68
+
69
+ Fix any style issues highlighted, auto-correcting safe issues with `bundle exec rubocop -a`. Exception message corrections require `-A`; review them and update message expectations in tests. To regenerate the baseline for generally ignored issues, including line lengths or code complexity metrics, run `bundle exec rubocop --auto-gen-config`.
70
+
71
+ On Ruby 3.2+, `bundle exec rake rubocop` also runs lint. The default `bundle exec rake` runs tests only; CI runs lint separately on Ruby 3.3.
63
72
 
64
73
  Make sure that `bundle exec rake` completes without errors.
65
74
 
data/Dangerfile CHANGED
@@ -1,3 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- danger.import_dangerfile(gem: 'slack-ruby-danger')
3
+ danger.import_dangerfile(gem: 'danger-pr-comment')
4
+
5
+ changelog.check!
6
+ toc.check!
data/Gemfile CHANGED
@@ -7,20 +7,35 @@ group :test do
7
7
  gem 'activesupport'
8
8
  gem 'base64'
9
9
  gem 'bigdecimal'
10
+ gem 'danger-changelog', require: false
11
+ gem 'danger-pr-comment', require: false
12
+ gem 'danger-toc', require: false
10
13
  gem 'erubis'
11
14
  gem 'faraday-typhoeus'
15
+ gem 'gli'
12
16
  gem 'json-schema'
13
17
  gem 'mutex_m'
14
18
  gem 'racc'
19
+ gem 'rackup', '~> 2.1'
15
20
  gem 'rake', '~> 13'
16
21
  gem 'rspec'
17
- gem 'rubocop', '1.26.1' # Lock to specific version to avoid breaking cops/changes
18
- gem 'rubocop-performance'
19
- gem 'rubocop-rake'
20
- gem 'rubocop-rspec'
21
- gem 'simplecov'
22
+ # Lock below 1.1.0, which started writing float timestamps to
23
+ # coverage/.resultset.json, breaking coverallsapp/github-action's parser
24
+ # (coverallsapp/github-action#269, coverallsapp/coverage-reporter#191).
25
+ gem 'simplecov', '< 1.1.0'
22
26
  gem 'simplecov-lcov'
23
27
  gem 'timecop'
24
28
  gem 'vcr'
25
29
  gem 'webmock'
30
+ gem 'webrick', '~> 1.8'
31
+ end
32
+
33
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
34
+ group :lint do
35
+ gem 'rubocop', '~> 1.72'
36
+ gem 'rubocop-exception_messages', '~> 0.2.0', require: false
37
+ gem 'rubocop-performance', '~> 1.27'
38
+ gem 'rubocop-rake', '~> 0.7'
39
+ gem 'rubocop-rspec', '~> 3.10'
40
+ end
26
41
  end
data/README.md CHANGED
@@ -70,7 +70,7 @@ A Ruby client for the Slack [Web](https://api.slack.com/web) and [Events](https:
70
70
 
71
71
  ## Stable Release
72
72
 
73
- You're reading the documentation for the **stable** release of slack-ruby-client. See [UPGRADING](UPGRADING.md) when upgrading from an older version.
73
+ You're reading the documentation for the **stable** release of slack-ruby-client, 3.3.0. See [UPGRADING](UPGRADING.md) when upgrading from an older version.
74
74
 
75
75
  ## Installation
76
76
 
@@ -301,6 +301,41 @@ You can also pass request options, including `timeout` and `open_timeout` into i
301
301
  client.conversations_list(request: { timeout: 180 })
302
302
  ```
303
303
 
304
+ Use `with_request` and `with_response` to create a configured client copy with HTTP callbacks, without changing the original client or parsed-body return values:
305
+
306
+ ```ruby
307
+ scopes = nil
308
+ observed_client = client.with_request do |request|
309
+ request.headers['X-Custom'] = 'value'
310
+ end.with_response do |response|
311
+ scopes = response.headers['x-oauth-scopes']&.split(',')&.map(&:strip)
312
+ end
313
+
314
+ result = observed_client.auth_test
315
+ observed_client.users_list do |page|
316
+ puts page.members
317
+ end
318
+ ```
319
+
320
+ Request callbacks receive a `Faraday::Request` after authentication and request options are configured, before dispatch. They can change headers, body, and options. Response callbacks receive a `Faraday::Response` with the parsed body, headers, and status after successful completion; they do not run when Slack or transport errors are raised. Callbacks apply to each HTTP call, including pagination pages and requests made by helpers. Endpoint blocks retain their existing meaning.
321
+
322
+ Chaining registers callbacks in order. Copies preserve client configuration but use independent connections and callback lists; the original client is unchanged. Callback exceptions propagate and request callback failures prevent dispatch. Callbacks are synchronous; avoid storing sensitive authorization headers, and synchronize any shared state if using a client concurrently.
323
+
324
+ Use `with_error` to observe Slack and transport errors without recovering from them:
325
+
326
+ ```ruby
327
+ observed_client = client.with_error do |error|
328
+ logger.warn(
329
+ message: error.message,
330
+ errors: error.respond_to?(:errors) && error.response ? error.errors : nil,
331
+ response_metadata: error.respond_to?(:response_metadata) && error.response ? error.response_metadata : nil
332
+ )
333
+ end
334
+ observed_client.auth_test
335
+ ```
336
+
337
+ Error callbacks receive the exception and then it is re-raised unchanged. They run for every failed HTTP attempt, including failures that pagination subsequently retries. Successful calls and exceptions from request or response callbacks do not trigger them. If an error callback raises, its exception propagates instead and later callbacks are not invoked. As with other callbacks, these methods return a configured copy and can be chained.
338
+
304
339
  You can control what proxy options are used by modifying the `http_proxy` environment variable per [Net::HTTP's documentation](https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html#class-Net::HTTP-label-Proxies).
305
340
 
306
341
  Note that Docker on OSX seems to incorrectly set the proxy, causing `Faraday::ConnectionFailed, ERROR -- : Failed to open TCP connection to : (getaddrinfo: Name or service not known)`. You might need to manually unset `http_proxy` in that case, eg. `http_proxy="" bundle exec ruby ./my_bot.rb`.
@@ -426,6 +461,10 @@ Slack::Events::Request.new(
426
461
 
427
462
  The `verify!` call may raise `Slack::Events::Request::MissingSigningSecret`, `Slack::Events::Request::InvalidSignature` or `Slack::Events::Request::TimestampExpired` errors.
428
463
 
464
+ Both rewindable request bodies and non-rewindable streams permitted by Rack 3 are supported. `Slack::Events::Request#body` caches the raw body. Rewindable inputs are rewound before and after reading, preserving access for other consumers. Non-rewindable inputs are consumed once; use `slack_request.body` for subsequent access to the raw body.
465
+
466
+ Verify the signature before middleware or application code reads a non-rewindable input. Bytes already consumed from a one-shot stream cannot be recovered, and verification will fail. If multiple consumers need to read `rack.input`, install `Rack::RewindableInput::Middleware` (Rack 3) before any body-reading middleware to buffer the input.
467
+
429
468
  ### Message Handling
430
469
 
431
470
  All text in Slack uses the same [system of formatting and escaping](https://api.slack.com/docs/formatting): chat messages, direct messages, file comments, etc. [Slack::Messages::Formatting](lib/slack/messages/formatting.rb) provides convenience methods to format and parse messages.
@@ -562,7 +601,13 @@ Slack::Messages::Formatting.escape('Hello & <world>')
562
601
 
563
602
  ### Command-Line Client
564
603
 
565
- The slack command-line client returns JSON data from the Slack API.
604
+ The slack command-line client returns JSON data from the Slack API. It uses [GLI](https://github.com/davetron5000/gli), which is not a dependency of slack-ruby-client, so install it alongside the client.
605
+
606
+ ```
607
+ gem install gli
608
+ ```
609
+
610
+ Or add `gem 'gli'` to your Gemfile.
566
611
 
567
612
  #### Authenticate with Slack
568
613
 
data/RELEASING.md CHANGED
@@ -16,7 +16,7 @@ Check that the last build succeeded in [GitHub Actions](https://github.com/slack
16
16
  Change "Next" in [CHANGELOG.md](CHANGELOG.md) to the current date.
17
17
 
18
18
  ```
19
- ### 0.2.2 (7/10/2015)
19
+ ### 0.2.2 (2015/07/10)
20
20
  ```
21
21
 
22
22
  Remove the line with "Your contribution here.", since there will be no more contributions to this release.
@@ -44,7 +44,7 @@ $ rake release
44
44
 
45
45
  slack-ruby-client 0.2.2 built to pkg/slack-ruby-client-0.2.2.gem.
46
46
  Tagged v0.2.2.
47
- Pushed git commits and tags.
47
+ Pushed git commits and release tag.
48
48
  Pushed slack-ruby-client 0.2.2 to rubygems.org.
49
49
  ```
50
50
 
data/Rakefile CHANGED
@@ -12,10 +12,14 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
12
12
  spec.pattern = FileList['spec/**/*_spec.rb']
13
13
  end
14
14
 
15
- require 'rubocop/rake_task'
16
- RuboCop::RakeTask.new
15
+ desc 'Run RuboCop (Ruby 3.2+).'
16
+ task :rubocop do
17
+ abort 'RuboCop requires Ruby 3.2 or newer' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2')
17
18
 
18
- task default: %i[spec rubocop]
19
+ sh 'bundle exec rubocop'
20
+ end
21
+
22
+ task default: :spec
19
23
 
20
24
  load 'tasks/git.rake'
21
25
  load 'tasks/web.rake'
data/UPGRADING.md CHANGED
@@ -1,6 +1,22 @@
1
1
  Upgrading Slack-Ruby-Client
2
2
  ===========================
3
3
 
4
+ ### Upgrading to >= 3.3.0
5
+
6
+ #### Command-Line Client Dependency
7
+
8
+ [#602](https://github.com/slack-ruby/slack-ruby-client/pull/602) removes [gli](https://github.com/davetron5000/gli) from slack-ruby-client's runtime dependencies. Only the `slack` command-line client uses it. If you use the command-line client, add `gem 'gli'` to your Gemfile or run `gem install gli`. Without it, `slack` exits with a message saying so. The library itself is unaffected.
9
+
10
+ #### Exception Messages
11
+
12
+ [#603](https://github.com/slack-ruby/slack-ruby-client/pull/603) standardizes handwritten exception messages to start with lowercase letters and omit trailing periods. Some messages also quote argument names and values with backticks. For example, `TooManyRequestsError` now reports `retry after 3600 seconds` instead of `Retry after 3600 seconds`, and file-upload validation reports ``required argument :`filename` missing in file (`0`)`` instead of `Required argument :filename missing in file (0)`.
13
+
14
+ Update tests, regular expressions, or application logic that match these messages exactly. Exception classes and rescue behavior are unchanged; upstream Slack error codes (such as `invalid_auth`) and dynamically supplied messages are preserved. Prefer matching exception classes and structured error attributes rather than human-readable message text.
15
+
16
+ ### Upgrading to >= 3.2.0
17
+
18
+ [#583](https://github.com/slack-ruby/slack-ruby-client/pull/583) modifies the error types raised when a nonexistent #channel-name or @user-handle is looked up. These will now raise `ChannelNotFound` and `UserNotFound` respectively, whereas they previously raised a `SlackError`. If your code relies on these types without accounting for inheritance, it will need to be migrated. Notably, `rescue SlackError` will continue to work as normal, since both error classes inherit from it.
19
+
4
20
  ### Upgrading to >= 3.0.0
5
21
 
6
22
  Support for the [RTM API](https://docs.slack.dev/tools/java-slack-sdk/guides/rtm/) has been removed in [#573](https://github.com/slack-ruby/slack-ruby-client/pull/573). By now you should have [migrated your app to granular permissions](https://code.dblock.org/2020/11/30/migrating-classic-slack-ruby-bots-to-granular-permissions.html), and you should lock your slack-ruby-client to 2.x for those bots. Next, to remove RTM components we found it more effective to just [write a new version of a bot](https://code.dblock.org/2024/06/30/writing-a-channel-slack-bot.html) and avoid a complex migration.