lolcommits-slack 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b394a7a611e79e1342e8b175af1d0d61b1d12f1daba2a540c794b45cb190c39a
4
- data.tar.gz: 3362c5d0c21c2705c6d0895fb0586b4265d4aba74ff58e38f789294303713d98
3
+ metadata.gz: fdd2793a3a713d8bebcb1c64940469385c756802bd1ba4a77a0e206d53cd9958
4
+ data.tar.gz: ee6af55125e45994a7339f4d828d16290aa23b56c7f75af061c7f8a9471e285c
5
5
  SHA512:
6
- metadata.gz: 0c5130d79903c73868a55d11077f66ef8f776af003dbc6adf8b64e78101fed222edeb9975b51ca76f401572cfd6ecc0fc66b2d3a5c798cf4e450205f8436ffdd
7
- data.tar.gz: 413d7151b5d90c9c2f26b05657905464debc448b6e2e6a4a799849c2d06e5f478c1aa405789643856435130be98f72fae432df1e3bd17b8e632917ffdb416269
6
+ metadata.gz: d86dd41fc503ff860955697a563cedc1aebfda2547d5ce3b08ad40e64798e96b3569fe2fc4dda3b1430737004479126fa5818a54569fca9db119f46bb2b04556
7
+ data.tar.gz: acbdb890e1df388215c1fcfc5cd4627f7f720053a284c26d906f37ae261a340b62340f836d358027859520f8849cda5698ac28207c66789dadebdbe984d2df4f
data/.travis.yml CHANGED
@@ -2,10 +2,10 @@ sudo: false
2
2
  language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.4.9
6
- - 2.5.7
7
- - 2.6.5
8
- - 2.7.0
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.2
8
+ - 3.0.0
9
9
  - ruby-head
10
10
 
11
11
  before_install:
data/CHANGELOG.md CHANGED
@@ -9,6 +9,11 @@ project adheres to [Semantic Versioning][Semver].
9
9
 
10
10
  - Your contribution here!
11
11
 
12
+ ## [0.6.0] - 2025-12-09
13
+ ### Changed
14
+ - Switch to multi-step file uploads (files.upload no longer supported)
15
+ - Reword config instructions for Oauth app tokens
16
+
12
17
  ## [0.5.0] - 2020-01-24
13
18
  ### Removed
14
19
  - Support for Ruby < 2.4 (older rubies no longer supported)
@@ -63,7 +68,8 @@ project adheres to [Semantic Versioning][Semver].
63
68
  ### Changed
64
69
  - Initial release
65
70
 
66
- [Unreleased]: https://github.com/lolcommits/lolcommits-slack/compare/v0.5.0...HEAD
71
+ [Unreleased]: https://github.com/lolcommits/lolcommits-slack/compare/v0.6.0...HEAD
72
+ [0.6.0]: https://github.com/lolcommits/lolcommits-slack/compare/v0.5.0...v0.6.0
67
73
  [0.5.0]: https://github.com/lolcommits/lolcommits-slack/compare/v0.4.0...v0.5.0
68
74
  [0.4.0]: https://github.com/lolcommits/lolcommits-slack/compare/v0.3.0...v0.4.0
69
75
  [0.3.0]: https://github.com/lolcommits/lolcommits-slack/compare/v0.2.0...v0.3.0
data/README.md CHANGED
@@ -11,11 +11,12 @@ webcam every time you git commit code, and archives a lolcat style image
11
11
  with it. Git blame has never been so much fun!
12
12
 
13
13
  This plugin automatically posts your lolcommits to one (or more)
14
- [Slack](https://slack.com) channels.
14
+ [Slack](https://slack.com) channels using Slack's modern [file upload
15
+ API](https://docs.slack.dev/messaging/working-with-files).
15
16
 
16
17
  The Slack post will contain the git commit message and repo name. The
17
- SHA is used as the uploaded file name. Posting will be retried (once)
18
- should any error occur.
18
+ SHA is used as the uploaded file name. Each upload step will be retried
19
+ (twice) should any error occur.
19
20
 
20
21
  ## Requirements
21
22
 
@@ -38,24 +39,29 @@ Next configure and enable with:
38
39
 
39
40
  $ lolcommits --config -p slack
40
41
  # set enabled to `true`
41
- # enter your access token and Slack channel ID list (see below)
42
+ # enter your Oauth User token and Slack channel ID list (see below)
42
43
 
43
44
  That's it! Every lolcommit will now be posted to these Slack channels.
44
45
  To disable simply reconfigure with `enabled: false`.
45
46
 
46
- ### Access Token
47
+ ### Authentication
47
48
 
48
- This plugin uses Slack [legacy
49
- tokens](https://api.slack.com/custom-integrations/legacy-tokens) for
50
- authentication. Create (or grab) them
51
- [here](https://api.slack.com/custom-integrations/legacy-tokens).
49
+ **NOTE**: This plugin no longer supports Slack [legacy
50
+ tokens](https://api.slack.com/custom-integrations/legacy-tokens), a Slack app
51
+ with OAuth is now required.
52
+
53
+ * Open [this URL](https://api.slack.com/apps?new_app=1") to create a new Slack app (or view existing)
54
+ * Ensure OAuth User Token Scopes includes `files:write`
55
+ * Install the app to your Slack workspace
56
+ * Use the app's User OAuth Token (e.g. xxxx-xxxxxxxxx-xxxx) when configuring
57
+ with `lolcommits --config -p slack`
52
58
 
53
59
  ### Channel List
54
60
 
55
- You must supply one or more Slack channel *IDs*. You can use Slack's own
56
- [API testing tool](https://api.slack.com/methods/channels.list/test) to
57
- list all Slack channels in your account, and grab the IDs from the JSON
58
- response presented.
61
+ You must supply one or more Slack channel *IDs*.
62
+
63
+ Grab a channel ID: right-click channel in side bar, select `View Channel
64
+ Details` and look for a Channel ID e.g. `C0FPKDOJJ`
59
65
 
60
66
  ## Development
61
67
 
@@ -86,7 +92,6 @@ moment to check it hasn't been raised in the past (and possibly closed).
86
92
 
87
93
  ## TODO
88
94
 
89
- - [ ] Use new Oauth / App (instead of legacy tokens)
90
95
  - [ ] Query for channel list and let user select
91
96
 
92
97
  ## Contributing
@@ -2,12 +2,14 @@
2
2
 
3
3
  require 'lolcommits/plugin/base'
4
4
  require 'rest_client'
5
+ require 'json'
5
6
 
6
7
  module Lolcommits
7
8
  module Plugin
8
9
  class Slack < Base
9
- # Slack API File upload endpoint
10
- ENDPOINT_URL = 'https://slack.com/api/files.upload'.freeze
10
+ # Slack API endpoints
11
+ GET_UPLOAD_URL_ENDPOINT = 'https://slack.com/api/files.getUploadURLExternal'.freeze
12
+ COMPLETE_UPLOAD_ENDPOINT = 'https://slack.com/api/files.completeUploadExternal'.freeze
11
13
 
12
14
  # Number of times to retry if RestClient.post fails
13
15
  RETRY_COUNT = 2
@@ -16,41 +18,25 @@ module Lolcommits
16
18
  # Capture ready hook, runs after lolcommits captures a snapshot.
17
19
  #
18
20
  # Uses `RestClient` to post the lolcommit to (one or more) Slack
19
- # channels. Posting will be retried (`RETRY_COUNT`) times if any
20
- # error occurs.
21
+ # channels using the new 3-step upload process. Each step will be
22
+ # retried (`RETRY_COUNT`) times if any error occurs.
21
23
  #
22
24
  # The post contains the git commit message, repo name and the SHA
23
- # is used for the filename. The response from the POST
25
+ # is used for the filename. The response from the final POST
24
26
  # request is sent to the debug log.
25
27
  #
26
28
  def run_capture_ready
27
- retries = RETRY_COUNT
28
- begin
29
- print "Posting to Slack ... "
30
- response = RestClient.post(
31
- ENDPOINT_URL,
32
- file: File.new(runner.lolcommit_path),
33
- token: configuration[:access_token],
34
- filetype: 'jpg',
35
- filename: runner.sha,
36
- title: runner.message + "[#{runner.vcs_info.repo}]",
37
- channels: configuration[:channels]
38
- )
29
+ print "Posting to Slack ... "
39
30
 
40
- debug response
41
- print "done!\n"
42
- rescue => e
43
- retries -= 1
44
- print "failed! #{e.message}"
45
- if retries > 0
46
- print " - retrying ...\n"
47
- retry
48
- else
49
- print " - giving up ...\n"
50
- puts 'Try running config again:'
51
- puts "\tlolcommits --config -p slack"
52
- end
53
- end
31
+ upload_url, file_id = get_upload_url
32
+ upload_file(upload_url)
33
+ complete_upload(file_id)
34
+
35
+ print "done!\n"
36
+ rescue => e
37
+ print "failed! #{e.message}\n"
38
+ puts 'Try running config again:'
39
+ puts "\tlolcommits --config -p slack"
54
40
  end
55
41
 
56
42
  ##
@@ -65,13 +51,15 @@ module Lolcommits
65
51
  options = super
66
52
 
67
53
  if options[:enabled]
68
- print "open the url below and issue a token for your user:\n"
69
- print "https://api.slack.com/custom-integrations/legacy-tokens\n"
70
- print "enter the generated token below, then press enter: (e.g. xxxx-xxxxxxxxx-xxxx) \n"
54
+ print "Open the URL below to create a new Slack app (or view existing):\n"
55
+ print "https://api.slack.com/apps?new_app=1\n"
56
+ print "Ensure OAuth User Token Scopes includes files:write\n"
57
+ print "Install the app to your Slack workspace\n"
58
+ print "Paste the app's User OAuth Token below: (e.g. xxxx-xxxxxxxxx-xxxx)\n"
71
59
  code = parse_user_input(gets.strip)
72
60
 
73
- print "enter a comma-seperated list of channel ids to post lolcommits in, then press enter: (e.g. c1234567890,c1234567890)\n"
74
- print "note: you must use channel ids (not channel names). grab them from here; https://api.slack.com/methods/channels.list/test\n"
61
+ print "Enter a comma-seperated list of channel IDs to post into: (e.g. C0FPKDOJJ,C0APK3PPK)\n"
62
+ print "Note: you must use channel IDs (not names). Right-click channel -> View Channel Details -> look for a Channel ID\n"
75
63
  channels = parse_user_input(gets.strip)
76
64
 
77
65
  options.merge!(
@@ -82,6 +70,110 @@ module Lolcommits
82
70
 
83
71
  options
84
72
  end
73
+
74
+ private
75
+
76
+ ##
77
+ # Step 1: Get upload URL from Slack
78
+ #
79
+ # @return [Array<String>] upload_url and file_id
80
+ #
81
+ def get_upload_url
82
+ retries = RETRY_COUNT
83
+ begin
84
+ response = RestClient.post(
85
+ GET_UPLOAD_URL_ENDPOINT,
86
+ {
87
+ filename: runner.sha,
88
+ length: File.size(runner.lolcommit_path)
89
+ },
90
+ {
91
+ Authorization: "Bearer #{configuration[:access_token]}"
92
+ }
93
+ )
94
+
95
+ result = JSON.parse(response.body)
96
+ debug "Step 1 response: #{result}"
97
+
98
+ unless result['ok']
99
+ raise "Slack API error: #{result['error']}"
100
+ end
101
+
102
+ [result['upload_url'], result['file_id']]
103
+ rescue => e
104
+ retries -= 1
105
+ if retries > 0
106
+ retry
107
+ else
108
+ raise e
109
+ end
110
+ end
111
+ end
112
+
113
+ ##
114
+ # Step 2: Upload file binary to the upload URL
115
+ #
116
+ # @param upload_url [String] the upload URL from step 1
117
+ #
118
+ def upload_file(upload_url)
119
+ retries = RETRY_COUNT
120
+ begin
121
+ RestClient.post(
122
+ upload_url,
123
+ {
124
+ file: File.new(runner.lolcommit_path)
125
+ }
126
+ )
127
+
128
+ debug "Step 2: File uploaded successfully"
129
+ rescue => e
130
+ retries -= 1
131
+ if retries > 0
132
+ retry
133
+ else
134
+ raise e
135
+ end
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Step 3: Complete the upload and share to channels
141
+ #
142
+ # @param file_id [String] the file_id from step 1
143
+ #
144
+ def complete_upload(file_id)
145
+ retries = RETRY_COUNT
146
+ begin
147
+ title = runner.message + "[#{runner.vcs_info.repo}]"
148
+ files_json = JSON.generate([{ id: file_id, title: title }])
149
+
150
+ response = RestClient.post(
151
+ COMPLETE_UPLOAD_ENDPOINT,
152
+ {
153
+ files: files_json,
154
+ channels: configuration[:channels]
155
+ },
156
+ {
157
+ Authorization: "Bearer #{configuration[:access_token]}"
158
+ }
159
+ )
160
+
161
+ result = JSON.parse(response.body)
162
+ debug "Step 3 response: #{result}"
163
+
164
+ unless result['ok']
165
+ raise "Slack API error: #{result['error']}"
166
+ end
167
+ rescue => e
168
+ retries -= 1
169
+ if retries > 0
170
+ retry
171
+ else
172
+ raise e
173
+ end
174
+ end
175
+ end
176
+
85
177
  end
86
178
  end
87
179
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lolcommits
4
4
  module Slack
5
- VERSION = "0.5.0".freeze
5
+ VERSION = "0.6.0".freeze
6
6
  end
7
7
  end
@@ -34,28 +34,133 @@ describe Lolcommits::Plugin::Slack do
34
34
  end
35
35
 
36
36
  it 'should post the message to slack' do
37
- stub_request(:any, plugin.class::ENDPOINT_URL)
37
+ # Step 1: Stub getting upload URL
38
+ stub_request(:post, plugin.class::GET_UPLOAD_URL_ENDPOINT)
39
+ .to_return(
40
+ status: 200,
41
+ body: { ok: true, upload_url: "https://files.slack.com/upload/v1/ABC123", file_id: "F123ABC" }.to_json,
42
+ headers: { 'Content-Type' => 'application/json' }
43
+ )
44
+
45
+ # Step 2: Stub file upload to Slack's upload URL
46
+ stub_request(:post, /files\.slack\.com/)
47
+ .to_return(status: 200, body: '')
48
+
49
+ # Step 3: Stub completing the upload
50
+ stub_request(:post, plugin.class::COMPLETE_UPLOAD_ENDPOINT)
51
+ .to_return(
52
+ status: 200,
53
+ body: { ok: true }.to_json,
54
+ headers: { 'Content-Type' => 'application/json' }
55
+ )
56
+
38
57
  in_repo do
39
58
  plugin.configuration = valid_enabled_config
40
59
  output = fake_io_capture { plugin.run_capture_ready }
41
60
  assert_equal output, "Posting to Slack ... done!\n"
42
61
 
43
- assert_requested :post, plugin.class::ENDPOINT_URL,
44
- headers: { 'Content-Type' => /multipart\/form-data;/ },
62
+ # Verify Step 1: Get upload URL was called with Authorization header and correct parameters
63
+ assert_requested :post, plugin.class::GET_UPLOAD_URL_ENDPOINT,
64
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
65
+ times: 1 do |req|
66
+ # Verify filename and length parameters are sent
67
+ req.body.include?('filename=') && req.body.include?('length=')
68
+ end
69
+
70
+ # Verify Step 2: File upload was called
71
+ assert_requested :post, /files\.slack\.com/,
45
72
  times: 1
73
+
74
+ # Verify Step 3: Complete upload was called with correct parameters
75
+ assert_requested :post, plugin.class::COMPLETE_UPLOAD_ENDPOINT,
76
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
77
+ times: 1 do |req|
78
+ # Verify channels and files (as JSON) parameters are sent
79
+ req.body.include?('channels=c123%2Cc456') && req.body.include?('files=')
80
+ end
46
81
  end
47
82
  end
48
83
 
49
- it 'should retry (and explain) if there is a failure (req timeout)' do
84
+ it 'should retry (and explain) if step 1 fails' do
50
85
  in_repo do
51
- stub_request(:any, plugin.class::ENDPOINT_URL).to_timeout
86
+ # Stub step 1 to timeout
87
+ stub_request(:post, plugin.class::GET_UPLOAD_URL_ENDPOINT).to_timeout
88
+
52
89
  plugin.configuration = valid_enabled_config
53
90
 
54
91
  _(Proc.new { plugin.run_capture_ready }).
55
- must_output("Posting to Slack ... failed! Timed out connecting to server - retrying ...\nPosting to Slack ... failed! Timed out connecting to server - giving up ...\nTry running config again:\n\tlolcommits --config -p slack\n")
92
+ must_output("Posting to Slack ... failed! Timed out connecting to server\nTry running config again:\n\tlolcommits --config -p slack\n")
93
+
94
+ # Verify Step 1 was retried RETRY_COUNT times before giving up
95
+ assert_requested :post, plugin.class::GET_UPLOAD_URL_ENDPOINT,
96
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
97
+ times: plugin.class::RETRY_COUNT
98
+ end
99
+ end
100
+
101
+ it 'should retry (and explain) if step 2 fails' do
102
+ in_repo do
103
+ # Step 1 succeeds
104
+ stub_request(:post, plugin.class::GET_UPLOAD_URL_ENDPOINT)
105
+ .to_return(
106
+ status: 200,
107
+ body: { ok: true, upload_url: "https://files.slack.com/upload/v1/ABC123", file_id: "F123ABC" }.to_json,
108
+ headers: { 'Content-Type' => 'application/json' }
109
+ )
110
+
111
+ # Step 2 times out
112
+ stub_request(:post, /files\.slack\.com/).to_timeout
113
+
114
+ plugin.configuration = valid_enabled_config
115
+
116
+ _(Proc.new { plugin.run_capture_ready }).
117
+ must_output("Posting to Slack ... failed! Timed out connecting to server\nTry running config again:\n\tlolcommits --config -p slack\n")
118
+
119
+ # Verify Step 1 was called once
120
+ assert_requested :post, plugin.class::GET_UPLOAD_URL_ENDPOINT,
121
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
122
+ times: 1
123
+
124
+ # Verify Step 2 was retried RETRY_COUNT times
125
+ assert_requested :post, /files\.slack\.com/,
126
+ times: plugin.class::RETRY_COUNT
127
+ end
128
+ end
129
+
130
+ it 'should retry (and explain) if step 3 fails' do
131
+ in_repo do
132
+ # Step 1 succeeds
133
+ stub_request(:post, plugin.class::GET_UPLOAD_URL_ENDPOINT)
134
+ .to_return(
135
+ status: 200,
136
+ body: { ok: true, upload_url: "https://files.slack.com/upload/v1/ABC123", file_id: "F123ABC" }.to_json,
137
+ headers: { 'Content-Type' => 'application/json' }
138
+ )
139
+
140
+ # Step 2 succeeds
141
+ stub_request(:post, /files\.slack\.com/)
142
+ .to_return(status: 200, body: '')
143
+
144
+ # Step 3 times out
145
+ stub_request(:post, plugin.class::COMPLETE_UPLOAD_ENDPOINT).to_timeout
146
+
147
+ plugin.configuration = valid_enabled_config
148
+
149
+ _(Proc.new { plugin.run_capture_ready }).
150
+ must_output("Posting to Slack ... failed! Timed out connecting to server\nTry running config again:\n\tlolcommits --config -p slack\n")
151
+
152
+ # Verify Step 1 was called once
153
+ assert_requested :post, plugin.class::GET_UPLOAD_URL_ENDPOINT,
154
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
155
+ times: 1
156
+
157
+ # Verify Step 2 was called once
158
+ assert_requested :post, /files\.slack\.com/,
159
+ times: 1
56
160
 
57
- assert_requested :post, plugin.class::ENDPOINT_URL,
58
- headers: { 'Content-Type' => /multipart\/form-data;/ },
161
+ # Verify Step 3 was retried RETRY_COUNT times
162
+ assert_requested :post, plugin.class::COMPLETE_UPLOAD_ENDPOINT,
163
+ headers: { 'Authorization' => 'Bearer acbd-1234-wxyz-5678' },
59
164
  times: plugin.class::RETRY_COUNT
60
165
  end
61
166
  end
@@ -78,8 +183,9 @@ describe Lolcommits::Plugin::Slack do
78
183
  it 'should allow plugin options to be configured' do
79
184
  configured_plugin_options = {}
80
185
 
81
- fake_io_capture(inputs: %w(true abc-def c1,c3,c4)) do
82
- configured_plugin_options = plugin.configure_options!
186
+ # Use .dup to create mutable strings (frozen_string_literal compatibility)
187
+ fake_io_capture(inputs: %w(true abc-def c1,c3,c4).map(&:dup)) do
188
+ configured_plugin_options = plugin.send(:configure_options!)
83
189
  end
84
190
 
85
191
  _(configured_plugin_options).must_equal({
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lolcommits-slack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Hutchinson
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-01-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rest-client
@@ -143,7 +142,6 @@ metadata:
143
142
  source_code_uri: https://github.com/lolcommits/lolcommits-slack
144
143
  bug_tracker_uri: https://github.com/lolcommits/lolcommits-slack/issues
145
144
  allowed_push_host: https://rubygems.org
146
- post_install_message:
147
145
  rdoc_options: []
148
146
  require_paths:
149
147
  - lib
@@ -158,8 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
156
  - !ruby/object:Gem::Version
159
157
  version: '0'
160
158
  requirements: []
161
- rubygems_version: 3.1.2
162
- signing_key:
159
+ rubygems_version: 3.7.2
163
160
  specification_version: 4
164
161
  summary: Sends lolcommits to one (or more) Slack channels
165
162
  test_files: