brutalismbot 1.6.2 → 2.0.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: c823de7d2cb19bf4f6d8742111bf4a995b565a40fde6eb89ab66e0fa49f32dcf
4
- data.tar.gz: 2f7053d644bf1c64e9221aec56d6b4bc28ab3ea393972d97fd7e70304b39ed64
3
+ metadata.gz: b7a72ed65ec740aef21cb54256d7f8f9b9e3128a039eb217711ee02c372683bf
4
+ data.tar.gz: 75b2ebef6b98ec94b716fe00cb1f657b386e9d9c40d89211b37443c8f0d98fb0
5
5
  SHA512:
6
- metadata.gz: 967b8a00fedb99a4bc186f14c2c5122366ccd3c5ba012e6e5851a2868d88f0fa5ac944c00e05b174e9a2bf99dcbf51594790c24240cb4468a4999ae7f2f77f47
7
- data.tar.gz: aee379c78663682c29bdccfe56215e9694d997807e3629768b31c208954cba643c681579dfd48ac1788daa6987feef41ecd06a238b5d939c772c65dfb646e4bd
6
+ metadata.gz: 80fb1a88e38e9d1fc5988d7586aebd1859339cf9c3bcd67b08cb7a587f5b0c6c0ab6c13db295f2569c19088637ade5a5e2a28c7ed7064ed57a63e5329a1747b5
7
+ data.tar.gz: eca017057b7c3a4d0c2d184ea8a328af49e319d96ac02f42e51977a65a5677e31fedd770a484c960c78e5b1f5bcc1ed834c0239ebc9bd12c7d6f320ea24bbe9b
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <img alt="brutalismbot" src="https://brutalismbot.com/banner.png"/>
2
2
 
3
- [![rspec](https://github.com/brutalismbot/gem/workflows/rspec/badge.svg)](https://github.com/brutalismbot/gem/actions)
4
- [![Gem Version](https://badge.fury.io/rb/brutalismbot.svg)](http://badge.fury.io/rb/brutalismbot)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/83275cbdbf10f9fd2df7/test_coverage)](https://codeclimate.com/github/brutalismbot/gem/test_coverage)
6
- [![Maintainability](https://api.codeclimate.com/v1/badges/83275cbdbf10f9fd2df7/maintainability)](https://codeclimate.com/github/brutalismbot/gem/maintainability)
3
+ ![gem](https://img.shields.io/gem/v/brutalismbot?logo=rubygems&logoColor=eee&style=flat-square)
4
+ [![rspec](https://img.shields.io/github/workflow/status/brutalismbot/gem/rspec?logo=github&style=flat-square)](https://github.com/brutalismbot/gem/actions)
5
+ [![coverage](https://img.shields.io/codeclimate/coverage/brutalismbot/gem?logo=code-climate&style=flat-square)](https://codeclimate.com/github/brutalismbot/gem/test_coverage)
6
+ [![maintainability](https://img.shields.io/codeclimate/maintainability/brutalismbot/gem?logo=code-climate&style=flat-square)](https://codeclimate.com/github/brutalismbot/gem/maintainability)
7
7
 
8
8
  Brutalismbot RubyGem
9
9
 
@@ -16,27 +16,27 @@ gem install brutalismbot
16
16
  ## Usage
17
17
 
18
18
  ```ruby
19
- require "brutalismbot/s3"
19
+ require "brutalismbot"
20
20
 
21
- brutbot = Brutalismbot::S3::Client.new bucket: "my-bucket", prefix: "my/prefix/"
21
+ bot = Brutalismbot::Client.new
22
22
 
23
23
  # Get latest cached post
24
- post = brutbot.posts.last
24
+ post = bot.posts.last
25
25
 
26
26
  # Get newest posts
27
- brutbot.subreddit.posts(:new).all
27
+ bot.reddit.list(:new).all
28
28
 
29
29
  # Get new posts since latest
30
- brutbot.subreddit.posts(:new, before: post.fullname).all
30
+ bot.reddit.list(:new, before: post.fullname).all
31
31
 
32
32
  # Get current top post
33
- brutbot.subreddit.posts(:top, limit: 1).first
33
+ bot.reddit.list(:top, limit: 1).first
34
34
 
35
35
  # Pull latest posts
36
- brutbot.posts.pull
36
+ bot.pull
37
37
 
38
38
  # Mirror a post to all clients
39
- brutbot.auths.mirror post
39
+ bot.push post
40
40
  ```
41
41
 
42
42
  ## Contributing
@@ -5,7 +5,7 @@ module Brutalismbot
5
5
  module Parser
6
6
  def parse(source, opts = {})
7
7
  item = JSON.parse(source, opts)
8
- new(**item)
8
+ new(**item.transform_keys(&:to_sym))
9
9
  end
10
10
  end
11
11
 
@@ -14,16 +14,11 @@ module Brutalismbot
14
14
  @twitter = twitter || Twitter::Client.new
15
15
  end
16
16
 
17
- def lag_time
18
- lag = ENV["BRUTALISMBOT_LAG_TIME"].to_s
19
- lag.empty? ? 9000 : lag.to_i
20
- end
21
-
22
- def pull(limit:nil, min_time:nil, max_time:nil, lag:nil, dryrun:nil)
17
+ def pull(limit:nil, min_time:nil, max_time:nil, min_age:nil, dryrun:nil)
23
18
  # Get time window for new posts
24
- lag ||= lag_time
19
+ min_age ||= 9000
25
20
  min_time ||= @posts.max_time
26
- max_time ||= Time.now.utc.to_i - lag
21
+ max_time ||= Time.now.utc.to_i - min_age.to_i
27
22
 
28
23
  # Get posts
29
24
  opts = {q:"self:no AND nsfw:no", restrict_sr: true, sort: "new"}
@@ -43,7 +38,9 @@ module Brutalismbot
43
38
  @twitter.push(post, dryrun: dryrun)
44
39
 
45
40
  # Push to Slack
46
- @slack.push(post, dryrun: dryrun)
41
+ @slack.list.map(&:webhook_url).each do |webhook_url|
42
+ @slack.push(post, webhook_url, dryrun: dryrun)
43
+ end
47
44
 
48
45
  nil
49
46
  end
@@ -15,7 +15,8 @@ module Brutalismbot
15
15
  end
16
16
 
17
17
  def key_for(post)
18
- File.join(@prefix, post.path)
18
+ path = post.created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
19
+ File.join(@prefix, path)
19
20
  end
20
21
 
21
22
  def get(**options)
@@ -27,7 +28,7 @@ module Brutalismbot
27
28
  end
28
29
 
29
30
  def list(**options)
30
- super(**options) do |object|
31
+ super do |object|
31
32
  Brutalismbot.logger.info("GET s3://#{@bucket}/#{object.key}")
32
33
  Reddit::Post.parse(object.get.body.read)
33
34
  end
@@ -54,10 +55,14 @@ module Brutalismbot
54
55
  end
55
56
 
56
57
  def push(post, dryrun:nil)
57
- options = post.to_s3(bucket: @bucket, prefix: @prefix)
58
- Brutalismbot.logger.info("PUT #{"DRYRUN " if dryrun}s3://#{options[:bucket]}/#{options[:key]}")
59
- @client.put_object(**options) unless dryrun
60
- options.slice(:bucket, :key)
58
+ key = key_for(post)
59
+ Brutalismbot.logger.info("PUT #{"DRYRUN " if dryrun}s3://#{@bucket}/#{key}")
60
+ @client.put_object(bucket: @bucket, key: key, body: post.to_json) unless dryrun
61
+ {
62
+ bucket: @bucket,
63
+ key: key,
64
+ url: post.permalink,
65
+ }
61
66
  end
62
67
  end
63
68
  end
@@ -39,128 +39,74 @@ module Brutalismbot
39
39
  "#<#{self.class} #{data["permalink"]}>"
40
40
  end
41
41
 
42
+ def is_gallery?
43
+ data["is_gallery"] || false
44
+ end
45
+
42
46
  def is_self?
43
- data["is_self"]
47
+ data["is_self"] || false
44
48
  end
45
49
 
46
50
  def kind
47
51
  @item["kind"]
48
52
  end
49
53
 
50
- def media_uri
51
- URI.parse(media_url)
54
+ def media_metadata
55
+ data["media_metadata"]
52
56
  end
53
57
 
54
- def media_url
55
- # Use URL if it's an image
56
- if mime_type.start_with?("image/")
57
- data["url"]
58
+ def permalink
59
+ "https://reddit.com#{data["permalink"]}"
60
+ end
58
61
 
59
- # Extract preview image URL
60
- else
61
- images = data.dig("preview", "images") || {}
62
- source = images.map{|x| x["source"] }.compact.max do |a,b|
63
- a.slice("width", "height").values <=> b.slice("width", "height").values
64
- end
65
- CGI.unescape_html(source["url"])
66
- end
62
+ def preview_images
63
+ data.dig("preview", "images")
67
64
  end
68
65
 
69
- def mime_type
70
- @mime_type ||= begin
71
- uri = URI.parse(data["url"])
72
- ssl = uri.scheme == "https"
73
- Brutalismbot.logger.info("HEAD #{uri}")
74
- Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
75
- req = Net::HTTP::Head.new(uri)
76
- http.request(req)["Content-Type"]
77
- end
78
- end
66
+ def title
67
+ CGI.unescape_html(data["title"])
79
68
  end
80
69
 
81
- def mime_type=(value)
82
- @mime_type = value
70
+ def url
71
+ data["url"]
83
72
  end
84
73
 
74
+ ##
75
+ # Get media URLs for post
76
+ def media_urls(&block)
77
+ if is_gallery?
78
+ media_urls_gallery(&block)
79
+ elsif preview_images
80
+ media_urls_preview(&block)
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ ##
87
+ # S3 path
85
88
  def path
86
89
  created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
87
90
  end
88
91
 
89
- def permalink
90
- "https://reddit.com#{data["permalink"]}"
91
- end
92
+ private
92
93
 
93
- def title
94
- CGI.unescapeHTML(data["title"])
95
- end
96
-
97
- def to_s3(bucket:nil, prefix:nil)
98
- bucket ||= ENV["POSTS_S3_BUCKET"] || "brutalismbot"
99
- prefix ||= ENV["POSTS_S3_PREFIX"] || "data/v1/posts/"
100
- {
101
- bucket: bucket,
102
- key: File.join(*[prefix, path].compact),
103
- body: to_json,
104
- }
105
- end
106
-
107
- def to_slack
108
- is_self? ? to_slack_text : to_slack_image
109
- end
110
-
111
- def to_slack_image
112
- {
113
- blocks: [
114
- {
115
- type: "image",
116
- title: {
117
- type: "plain_text",
118
- text: "/r/brutalism",
119
- emoji: true,
120
- },
121
- image_url: media_url,
122
- alt_text: title,
123
- },
124
- {
125
- type: "context",
126
- elements: [
127
- {
128
- type: "mrkdwn",
129
- text: "<#{permalink}|#{title}>",
130
- },
131
- ],
132
- },
133
- ],
134
- }
135
- end
136
-
137
- def to_slack_text
138
- {
139
- blocks: [
140
- {
141
- type: "section",
142
- text: {
143
- type: "mrkdwn",
144
- text: "<#{permalink}|#{title}>",
145
- },
146
- accessory: {
147
- type: "image",
148
- image_url: "https://brutalismbot.com/logo-red-ppl.png",
149
- alt_text: "/r/brutalism",
150
- },
151
- },
152
- ],
153
- }
154
- end
155
-
156
- def to_twitter
157
- max = 280 - permalink.length - 1
158
- text = title.length <= max ? title : "#{title[0...max - 1]}…"
159
- [text, permalink].join("\n")
94
+ ##
95
+ # Get media URLs from gallery
96
+ def media_urls_gallery(&block)
97
+ media_metadata.values.map do |image|
98
+ url = block_given? ? yield(image) : image.dig("s", "u")
99
+ CGI.unescape_html(url) unless url.nil?
100
+ end.compact
160
101
  end
161
102
 
162
- def url
163
- data["url"]
103
+ ##
104
+ # Get media URLs from previews
105
+ def media_urls_preview(&block)
106
+ preview_images.map do |image|
107
+ url = block_given? ? yield(image) : image.dig("source", "url")
108
+ CGI.unescape_html(url) unless url.nil?
109
+ end.compact
164
110
  end
165
111
  end
166
112
  end
@@ -24,9 +24,12 @@ module Brutalismbot
24
24
  response = JSON.parse(http.request(request).body)
25
25
  children = response.dig("data", "children") || []
26
26
  children.each do |child|
27
- post = Post.new(**child)
28
- Brutalismbot.logger.warn("NO PHOTO URL for #{post.permalink}") if post.url.nil?
29
- yield post
27
+ item = child.transform_keys(&:to_sym)
28
+ post = Post.new(**item)
29
+ unless post.is_self?
30
+ Brutalismbot.logger.warn("NO MEDIA URLs for #{post.permalink}") if post.media_urls.empty?
31
+ yield post
32
+ end
30
33
  end
31
34
  end
32
35
  end
@@ -17,6 +17,28 @@ module Brutalismbot
17
17
  permalink: "/r/brutalism/comments/#{permalink_id}/test/",
18
18
  title: "Post to /r/brutalism",
19
19
  url: "https://image.host/#{image_id}.jpg",
20
+ media_metadata: {
21
+ abcdef: {
22
+ s: {
23
+ u: "https://preview.image.host/#{image_id}_1.jpg",
24
+ },
25
+ p: [
26
+ {x: 1, y: 1, u: "https://preview.image.host/#{image_id}_1.jpg"},
27
+ {x: 2, y: 2, u: "https://preview.image.host/#{image_id}_2.jpg"},
28
+ {x: 3, y: 3, u: "https://preview.image.host/#{image_id}_3.jpg"},
29
+ ],
30
+ },
31
+ ghijkl: {
32
+ s: {
33
+ u: "https://preview.image.host/#{image_id}_2.jpg",
34
+ },
35
+ p: [
36
+ {x: 1, y: 1, u: "https://preview.image.host/#{image_id}_1.jpg"},
37
+ {x: 2, y: 2, u: "https://preview.image.host/#{image_id}_2.jpg"},
38
+ {x: 3, y: 3, u: "https://preview.image.host/#{image_id}_3.jpg"},
39
+ ],
40
+ },
41
+ },
20
42
  preview: {
21
43
  images: [
22
44
  {
@@ -26,13 +48,6 @@ module Brutalismbot
26
48
  height: 1000,
27
49
  },
28
50
  },
29
- {
30
- source: {
31
- url: "https://preview.image.host/#{image_id}_small.jpg",
32
- width: 500,
33
- height: 500,
34
- }
35
- }
36
51
  ],
37
52
  },
38
53
  },
@@ -20,24 +20,12 @@ module Brutalismbot
20
20
  File.join("team=#{team_id}", "channel=#{channel_id}", "oauth.json")
21
21
  end
22
22
 
23
- def push(post, dryrun:nil)
24
- body = post.to_slack.to_json
25
- uri = URI.parse(webhook_url)
26
- Brutalismbot.logger.info("POST #{"DRYRUN " if dryrun}#{uri}")
27
- unless dryrun
28
- ssl = uri.scheme == "https"
29
- req = Net::HTTP::Post.new(uri, "content-type" => "application/json")
30
- req.body = body
31
- Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
32
- http.request(req)
33
- end
34
- else
35
- Net::HTTPOK.new("1.1", "204", "ok")
36
- end
23
+ def team_id
24
+ @item["team_id"] || @item.dig("team", "id")
37
25
  end
38
26
 
39
- def team_id
40
- @item.dig("team_id")
27
+ def team_name
28
+ @item["team_name"] || @item.dig("team", "name")
41
29
  end
42
30
 
43
31
  def to_s3(bucket:nil, prefix:nil)
@@ -31,17 +31,28 @@ module Brutalismbot
31
31
  end
32
32
 
33
33
  def list(**options)
34
- super(**options) do |object|
34
+ super do |object|
35
35
  Brutalismbot.logger.info("GET s3://#{@bucket}/#{object.key}")
36
36
  Auth.parse(object.get.body.read)
37
37
  end
38
38
  end
39
39
 
40
- def push(post, dryrun:nil)
41
- list.map do |auth|
42
- key = key_for(auth)
43
- Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}s3://#{@bucket}/#{key}")
44
- auth.push(post, dryrun: dryrun)
40
+ def push(post, webhook_url, dryrun:nil)
41
+ blocks = blocks_for(post)
42
+
43
+ Brutalismbot.logger.info("POST #{"DRYRUN " if dryrun}#{webhook_url}")
44
+ unless dryrun
45
+ uri = URI.parse(webhook_url)
46
+ ssl = uri.scheme == "https"
47
+ req = Net::HTTP::Post.new(uri, "content-type" => "application/json")
48
+ req.body = { blocks: blocks }.to_json
49
+ Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
50
+ http.request(req)
51
+ end.tap do |res|
52
+ Brutalismbot.logger.error("RESPONSE [#{res.code}] #{res.body}") unless res.kind_of?(Net::HTTPSuccess)
53
+ end
54
+ else
55
+ Net::HTTPOK.new("1.1", "204", "ok")
45
56
  end
46
57
  end
47
58
 
@@ -53,6 +64,34 @@ module Brutalismbot
53
64
  object.delete unless dryrun
54
65
  end
55
66
  end
67
+
68
+ private
69
+
70
+ def blocks_for(post)
71
+ post.media_urls.map do |media_url|
72
+ [
73
+ {
74
+ type: "image",
75
+ title: {
76
+ type: "plain_text",
77
+ text: "/r/brutalism",
78
+ emoji: true,
79
+ },
80
+ image_url: media_url,
81
+ alt_text: post.title,
82
+ },
83
+ {
84
+ type: "context",
85
+ elements: [
86
+ {
87
+ type: "mrkdwn",
88
+ text: "<#{post.permalink}|#{post.title}>",
89
+ },
90
+ ],
91
+ },
92
+ ]
93
+ end.flatten
94
+ end
56
95
  end
57
96
  end
58
97
  end
@@ -20,21 +20,53 @@ module Brutalismbot
20
20
  end
21
21
 
22
22
  def push(post, dryrun:nil)
23
- method = post.is_self? ? :push_text : :push_image
24
- send(method, post, dryrun: dryrun)
23
+ opts = {}
24
+ slices_for(post).each_with_index.map do |slice, index|
25
+ status, media = slice
26
+ Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
27
+ begin
28
+ res = @client.update_with_media(status, media, opts)
29
+ opts[:in_reply_to_status_id] = res.id
30
+ rescue ::Twitter::Error::BadRequest => err
31
+ if err.message =~ /Image file size must be <= \d+ bytes/
32
+ Brutalismbot.logger.warn("IMAGE TOO LARGE - RETRYING WITH PREVIEWS")
33
+ res = push_preview(post, opts, index)
34
+ opts[:in_reply_to_status_id] = res.id
35
+ end
36
+ end unless dryrun
37
+
38
+ res&.id
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def push_preview(post, opts, index)
45
+ status, media = slices_for(post) do |i|
46
+ i["p"].max {|a,b| a["x"] * a["y"] <=> b["x"] * b["y"] }["u"]
47
+ end.to_a[index]
48
+ @client.update_with_media(status, media, opts)
25
49
  end
26
50
 
27
- def push_text(post, dryrun:nil)
28
- Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
29
- @client.update(post.to_twitter) unless dryrun
51
+ def slices_for(post, &block)
52
+ status = status_for(post)
53
+ media_urls = post.media_urls(&block)
54
+ case media_urls.count % 4
55
+ when 1 then media_urls.each_slice(3).to_a
56
+ when 2 then media_urls.each_slice(3).to_a
57
+ else media_urls.each_slice(4).to_a
58
+ end.map do |media_urls_slice|
59
+ media_urls_slice.map do |media_url|
60
+ Brutalismbot.logger.info("GET #{media_url}")
61
+ URI.open(media_url)
62
+ end
63
+ end.zip([status]).map(&:reverse)
30
64
  end
31
65
 
32
- def push_image(post, dryrun:nil)
33
- Brutalismbot.logger.info("GET #{post.media_uri}")
34
- Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
35
- post.media_uri.open do |media|
36
- @client.update_with_media(post.to_twitter, media)
37
- end unless dryrun
66
+ def status_for(post)
67
+ max = 280 - post.permalink.length - 1
68
+ status = post.title.length <= max ? post.title : "#{post.title[0...max - 1]}"
69
+ status << "\n#{post.permalink}"
38
70
  end
39
71
  end
40
72
  end
@@ -1,3 +1,3 @@
1
1
  module Brutalismbot
2
- VERSION = "1.6.2"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brutalismbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Mancevice
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-19 00:00:00.000000000 Z
11
+ date: 2020-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: twitter
@@ -24,118 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
- - !ruby/object:Gem::Dependency
28
- name: aws-sdk-s3
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.0'
41
- - !ruby/object:Gem::Dependency
42
- name: bundler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.0'
55
- - !ruby/object:Gem::Dependency
56
- name: dotenv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '2.7'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.7'
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.13'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.13'
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '13.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '13.0'
97
- - !ruby/object:Gem::Dependency
98
- name: rspec
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '3.8'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '3.8'
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "<"
116
- - !ruby/object:Gem::Version
117
- version: '0.18'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "<"
123
- - !ruby/object:Gem::Version
124
- version: '0.18'
125
- - !ruby/object:Gem::Dependency
126
- name: webmock
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.6'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '3.6'
139
27
  description: 'A Slack app that mirrors posts from /r/brutalism to a #channel of your
140
28
  choosing using incoming webhooks.'
141
29
  email: