brutalismbot 1.6.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: