brutalismbot 0.4.0 → 0.5.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: 5adaf818709e409365f83810ea0ac9a883a0e24c5d91432adcce5c567fe0085d
4
- data.tar.gz: 54010b835fdb82d057b21961fcd14be8e14087c31b49d3084849a101e95126bf
3
+ metadata.gz: 0cf18d4d7ec09728b41cd4bc32f1208565ee8a8e29df981a950cc12a022ebe05
4
+ data.tar.gz: a57aeccee651d4bbd95c8f775af885400b33c00b817759d65ae36129c0e9eaa5
5
5
  SHA512:
6
- metadata.gz: bfe6c521032918f9f0a5f908a4c14cf3f8cb0a8564168c08246c9600146fe8bdd92efd5b910720041d825fc4b8da4bfc72a8b8c6b27faca32fba67f16b488574
7
- data.tar.gz: f8653819c8375a31ac16cd041a82f9d026d1b0e09792b2a99e77946c598a31d25f49cd499fc7349833348368a6253655537d83dfc6dbf6c38611e550f0cd9e7c
6
+ metadata.gz: 19a19b76a2102521cd1280c9b079013adbacc9828e10f1af5983a717e09e6c4f608777e485ab37ba670602d9119e6c29e06c5e67781002143dd93e20f28de652
7
+ data.tar.gz: 7263c86e3380544e031843c750e20f69a7eade16bbea7d73c8953b9eadef8930d846a2b186cdadf1d74b45620127272118abe80dbaaa0f6ae462801eb485ea5d
data/README.md CHANGED
@@ -15,26 +15,27 @@ gem install brutalismbot
15
15
  ## Usage
16
16
 
17
17
  ```ruby
18
- require "aws-sdk-s3"
19
- require "brutalismbot"
18
+ require "brutalismbot/s3"
20
19
 
21
- bucket = Aws::S3::Bucket.new name: "my-bucket"
22
- brutbot = Brutalismbot::S3::Client.new bucket: bucket, prefix: "my/prefix/"
20
+ brutbot = Brutalismbot::S3::Client.new bucket: "my-bucket", prefix: "my/prefix/"
23
21
 
24
22
  # Get latest cached post
25
- brutbot.posts.latest
23
+ post = brutbot.posts.last
26
24
 
27
- # Get latest post as S3 Object
28
- brutbot.posts.max_key
25
+ # Get newest posts
26
+ brutbot.subreddit.posts(:new).all
29
27
 
30
- # Get post-time of latest cached post
31
- limit = brutbot.posts.max_time
32
-
33
- # Get newest post after a given time
34
- brutbot.subreddit.posts(:new).since(time: limit).first
28
+ # Get new posts since latest
29
+ brutbot.subreddit.posts(:new, before: post.fullname).all
35
30
 
36
31
  # Get current top post
37
- brutbot.subreddit.posts(:top).first
32
+ brutbot.subreddit.posts(:top, limit: 1).first
33
+
34
+ # Pull latest posts
35
+ brutbot.posts.pull
36
+
37
+ # Mirror a post to all clients
38
+ brutbot.auths.mirror post
38
39
  ```
39
40
 
40
41
  ## Contributing
@@ -0,0 +1,57 @@
1
+ module Brutalismbot
2
+ class Auth < Hash
3
+ def channel_id
4
+ dig "incoming_webhook", "channel_id"
5
+ end
6
+
7
+ def post(body:, dryrun:nil)
8
+ uri = URI.parse webhook_url
9
+ ssl = uri.scheme == "https"
10
+ req = Net::HTTP::Post.new uri, "content-type" => "application/json"
11
+ req.body = body
12
+ Brutalismbot.logger.info "POST #{dryrun ? "DRYRUN " : ""}#{uri}"
13
+ if dryrun
14
+ Net::HTTPOK.new "1.1", "204", "ok"
15
+ else
16
+ Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
17
+ http.request req
18
+ end
19
+ end
20
+ end
21
+
22
+ def team_id
23
+ dig "team_id"
24
+ end
25
+
26
+ def webhook_url
27
+ dig "incoming_webhook", "url"
28
+ end
29
+
30
+ class << self
31
+ def stub
32
+ bot_id = "B#{SecureRandom.alphanumeric(8).upcase}"
33
+ channel_id = "C#{SecureRandom.alphanumeric(8).upcase}"
34
+ team_id = "T#{SecureRandom.alphanumeric(8).upcase}"
35
+ user_id = "U#{SecureRandom.alphanumeric(8).upcase}"
36
+ Auth[{
37
+ "ok" => true,
38
+ "access_token" => "<token>",
39
+ "scope" => "identify,incoming-webhook",
40
+ "user_id" => user_id,
41
+ "team_name" => "My Team",
42
+ "team_id" => team_id,
43
+ "incoming_webhook" => {
44
+ "channel" => "#brutalism",
45
+ "channel_id" => channel_id,
46
+ "configuration_url" => "https://my-team.slack.com/services/#{bot_id}",
47
+ "url" => "https://hooks.slack.com/services/#{team_id}/#{bot_id}/1234567890abcdef12345678",
48
+ },
49
+ "scopes" => [
50
+ "identify",
51
+ "incoming-webhook",
52
+ ],
53
+ }]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,94 @@
1
+ module Brutalismbot
2
+ class Post < Hash
3
+ def created_after?(time)
4
+ created_utc.to_i > time.to_i
5
+ end
6
+
7
+ def created_utc
8
+ Time.at(dig("data", "created_utc").to_i).utc
9
+ end
10
+
11
+ def fullname
12
+ "#{kind}_#{id}"
13
+ end
14
+
15
+ def id
16
+ dig "data", "id"
17
+ end
18
+
19
+ def kind
20
+ dig "kind"
21
+ end
22
+
23
+ def permalink
24
+ dig "data", "permalink"
25
+ end
26
+
27
+ def title
28
+ dig "data", "title"
29
+ end
30
+
31
+ def to_slack
32
+ {
33
+ blocks: [
34
+ {
35
+ type: "image",
36
+ title: {
37
+ type: "plain_text",
38
+ text: "/r/brutalism",
39
+ emoji: true,
40
+ },
41
+ image_url: url,
42
+ alt_text: title,
43
+ },
44
+ {
45
+ type: "context",
46
+ elements: [
47
+ {
48
+ type: "mrkdwn",
49
+ text: "<https://reddit.com#{permalink}|#{title}>",
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ }
55
+ end
56
+
57
+ def url
58
+ images = dig "data", "preview", "images"
59
+ source = images.map{|x| x["source"] }.compact.max do |a,b|
60
+ a.slice("width", "height").values <=> b.slice("width", "height").values
61
+ end
62
+ CGI.unescapeHTML source.dig("url")
63
+ rescue NoMethodError
64
+ dig("data", "media_metadata")&.values&.first&.dig("s", "u")
65
+ end
66
+
67
+ class << self
68
+ def stub
69
+ created_utc = Time.now.utc - rand(86400) - 86400
70
+ post_id = SecureRandom.alphanumeric(6).downcase
71
+ permalink_id = SecureRandom.alphanumeric.downcase
72
+ image_id = SecureRandom.alphanumeric
73
+ Post[{
74
+ "kind" => "t3",
75
+ "data" => {
76
+ "id" => post_id,
77
+ "created_utc" => created_utc.to_i,
78
+ "permalink" => "/r/brutalism/comments/#{permalink_id}/test/",
79
+ "title" => "Post to /r/brutalism",
80
+ "preview" => {
81
+ "images" => [
82
+ {
83
+ "source" => {
84
+ "url" => "https://preview.redd.it/#{image_id}.jpg",
85
+ }
86
+ }
87
+ ]
88
+ }
89
+ }
90
+ }]
91
+ end
92
+ end
93
+ end
94
+ end
@@ -5,10 +5,10 @@ module Brutalismbot
5
5
 
6
6
  def initialize(endpoint:nil, user_agent:nil)
7
7
  @endpoint = endpoint
8
- @user_agent = user_agent
8
+ @user_agent = user_agent || "Brutalismbot #{VERSION}"
9
9
  end
10
10
 
11
- def posts(resource, **params)
11
+ def posts(resource, params = {})
12
12
  url = File.join @endpoint, "#{resource}.json"
13
13
  qry = URI.encode_www_form params
14
14
  uri = URI.parse "#{url}?#{qry}"
@@ -19,11 +19,10 @@ module Brutalismbot
19
19
  class PostCollection
20
20
  include Enumerable
21
21
 
22
- def initialize(uri:, user_agent:, min_time:nil)
22
+ def initialize(uri:, user_agent:)
23
23
  @uri = uri
24
24
  @ssl = uri.scheme == "https"
25
25
  @user_agent = user_agent
26
- @min_time = min_time.to_i
27
26
  end
28
27
 
29
28
  def each
@@ -32,22 +31,26 @@ module Brutalismbot
32
31
  request = Net::HTTP::Get.new @uri, "user-agent" => @user_agent
33
32
  response = JSON.parse http.request(request).body
34
33
  children = response.dig("data", "children") || []
35
- children.reverse.each do |child|
36
- post = Brutalismbot::Post[child]
37
- yield post if post.created_after time: @min_time
34
+ children.each do |child|
35
+ post = Post[child]
36
+ yield post
38
37
  end
39
38
  end
40
39
  end
41
40
 
42
- def since(time:)
43
- PostCollection.new uri: @uri, user_agent: @user_agent, min_time: time
41
+ def all
42
+ to_a
43
+ end
44
+
45
+ def last
46
+ to_a.last
44
47
  end
45
48
  end
46
49
 
47
50
  class Brutalism < Subreddit
48
51
  def initialize(endpoint:nil, user_agent:nil)
49
- super endpoint: endpoint || "https://www.reddit.com/r/brutalism",
50
- user_agent: user_agent || "Brutalismbot #{Brutalismbot::VERSION}"
52
+ endpoint ||= "https://www.reddit.com/r/brutalism"
53
+ super
51
54
  end
52
55
  end
53
56
  end
@@ -1,121 +1,173 @@
1
+ require "aws-sdk-s3"
2
+
3
+ require "brutalismbot"
4
+
1
5
  module Brutalismbot
2
6
  module S3
3
- class Collection
7
+ class Prefix
4
8
  include Enumerable
5
9
 
6
- attr_reader :bucket, :prefix
7
-
8
- def initialize(bucket:nil, prefix:nil)
9
- @bucket = bucket || ::Aws::S3::Bucket.new(name: ENV["S3_BUCKET"])
10
- @prefix = prefix || ENV["S3_PREFIX"]
11
- end
12
-
13
- def each
14
- Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{@prefix}*"
15
- @bucket.objects(prefix: @prefix).each do |object|
16
- yield object
10
+ attr_reader :prefix, :client
11
+
12
+ def initialize(bucket:nil, prefix:nil, client:nil, stub_responses:nil)
13
+ @bucket = bucket || "brutalismbot"
14
+ @prefix = prefix || "data/v1/"
15
+ @client = client || Aws::S3::Client.new(stub_responses: stub_responses || false)
16
+ if stub_responses
17
+ @auths = 3.times.map{ Auth.stub }
18
+ @posts = 3.times.map{ Post.stub }.sort{|a,b| a.created_utc <=> b.created_utc }
19
+ @client.stub_responses :delete_object
20
+ @client.stub_responses :list_objects, -> (context) { stub_list_objects context }
21
+ @client.stub_responses :get_object, -> (context) { stub_get_object context }
17
22
  end
18
23
  end
19
24
 
20
- def put(body:, key:, dryrun:nil)
21
- if dryrun
22
- Brutalismbot.logger.info "PUT DRYRUN s3://#{@bucket.name}/#{key}"
23
- else
24
- Brutalismbot.logger.info "PUT s3://#{@bucket.name}/#{key}"
25
- @bucket.put_object key: key, body: body
26
- end
25
+ def each
26
+ bucket.objects(prefix: @prefix).each{|x| yield x }
27
+ end
28
+
29
+ def all
30
+ to_a
31
+ end
32
+
33
+ def bucket(options = {})
34
+ options[:name] ||= @bucket
35
+ options[:client] ||= @client
36
+ Aws::S3::Bucket.new options
37
+ end
38
+
39
+ def delete(object)
40
+ key = key_for object
41
+ Brutalismbot.logger.info "DELETE #{"DRYRUN " if stubbed?}s3://#{@bucket}/#{key}"
42
+ bucket.delete_objects delete: {objects: [{key: key}]}
43
+ end
44
+
45
+ def put(object)
46
+ key = key_for object
47
+ Brutalismbot.logger.info "PUT #{"DRYRUN " if stubbed?}s3://#{@bucket}/#{key}"
48
+ bucket.put_object key: key, body: object.to_json
49
+ end
50
+
51
+ def stubbed?
52
+ @client.config.stub_responses
53
+ end
54
+
55
+ def stub_list_objects(context)
56
+ {
57
+ contents: if context.params[:prefix] =~ /auths\//
58
+ @auths.map do |auth|
59
+ File.join(
60
+ @prefix,
61
+ "auths",
62
+ "team=#{auth.team_id}",
63
+ "channel=#{auth.channel_id}",
64
+ "oauth.json",
65
+ )
66
+ end
67
+ elsif context.params[:prefix] =~ /posts\//
68
+ @posts.map do |post|
69
+ File.join(
70
+ @prefix,
71
+ "posts",
72
+ post.created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json"),
73
+ )
74
+ end
75
+ end.select do |key|
76
+ key.start_with? context.params[:prefix]
77
+ end.map do |key|
78
+ {
79
+ key: key
80
+ }
81
+ end
82
+ }
83
+ end
84
+
85
+ def stub_get_object(context)
86
+ {
87
+ body: if context.params[:key] =~ /auths\//
88
+ @auths.select do |auth|
89
+ File.join(
90
+ @prefix,
91
+ "auths",
92
+ "team=#{auth.team_id}",
93
+ "channel=#{auth.channel_id}",
94
+ "oauth.json",
95
+ ) == context.params[:key]
96
+ end.first
97
+ elsif context.params[:key] =~ /posts\//
98
+ @posts.select do |post|
99
+ File.join(
100
+ @prefix,
101
+ "posts",
102
+ post.created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json"),
103
+ ) == context.params[:key]
104
+ end.first
105
+ end.to_json
106
+ }
27
107
  end
28
108
  end
29
109
 
30
- class Client < Collection
31
- def subreddit(endpoint:nil, user_agent:nil)
32
- Brutalismbot::R::Brutalism.new endpoint:endpoint, user_agent: user_agent
33
- end
34
-
110
+ class Client < Prefix
35
111
  def auths
36
- AuthCollection.new bucket: @bucket, prefix: "#{@prefix}auths/"
112
+ prefix = File.join @prefix, "auths/"
113
+ AuthCollection.new bucket: @bucket, prefix: prefix, client: @client
37
114
  end
38
115
 
39
116
  def posts
40
- PostCollection.new bucket: @bucket, prefix: "#{@prefix}posts/"
117
+ prefix = File.join @prefix, "posts/"
118
+ PostCollection.new bucket: @bucket, prefix: prefix, client: @client
41
119
  end
42
- end
43
120
 
44
- class AuthCollection < Collection
45
- def each
46
- super do |object|
47
- yield Brutalismbot::Auth[JSON.parse object.get.body.read]
48
- end
121
+ def subreddit(endpoint:nil, user_agent:nil)
122
+ R::Brutalism.new endpoint:endpoint, user_agent: user_agent
49
123
  end
124
+ end
50
125
 
51
- def remove(team:, dryrun:nil)
52
- prefix = "#{@prefix}team=#{team}/"
53
- Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
54
- @bucket.objects(prefix: prefix).map do |object|
55
- if dryrun
56
- Brutalismbot.logger.info "DELETE DRYRUN s3://#{@bucket.name}/#{object.key}"
57
- else
58
- Brutalismbot.logger.info "DELETE s3://#{@bucket.name}/#{object.key}"
59
- object.delete
60
- end
61
- end
126
+ class AuthCollection < Prefix
127
+ def each
128
+ super{|x| yield Auth[JSON.parse x.get.body.read] }
62
129
  end
63
130
 
64
- def mirror(body:, dryrun:nil)
65
- map{|auth| auth.post body: body, dryrun: dryrun }
131
+ def key_for(auth)
132
+ File.join @prefix, "team=#{auth.team_id}/channel=#{auth.channel_id}/oauth.json"
66
133
  end
67
134
 
68
- def put(auth:, dryrun:nil)
69
- key = "#{@prefix}team=#{auth.team_id}/channel=#{auth.channel_id}/oauth.json"
70
- super key: key, body: auth.to_json, dryrun: dryrun
135
+ def mirror(post, options = {})
136
+ options[:body] = post.to_slack.to_json
137
+ map{|x| x.post options }
71
138
  end
72
139
  end
73
140
 
74
- class PostCollection < Collection
141
+ class PostCollection < Prefix
75
142
  def each
76
- super do |object|
77
- yield Brutalismbot::Post[JSON.parse object.get.body.read]
78
- end
143
+ super{|x| yield Post[JSON.parse x.get.body.read] }
79
144
  end
80
145
 
81
- def latest
82
- Brutalismbot::Post[JSON.parse max_key.get.body.read]
146
+ def key_for(post)
147
+ File.join @prefix, post.created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
83
148
  end
84
149
 
85
- def max_key
150
+ def last
86
151
  # Dig for max key
87
- prefix = prefix_for time: Time.now.utc
88
- Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
152
+ prefix = Time.now.utc.strftime "#{@prefix}year=%Y/month=%Y-%m/day=%Y-%m-%d/"
153
+ Brutalismbot.logger.info "GET s3://#{@bucket}/#{prefix}*"
89
154
 
90
155
  # Go up a level in prefix if no keys found
91
- until (keys = @bucket.objects(prefix: prefix)).any?
156
+ until (keys = bucket.objects(prefix: prefix)).any?
92
157
  prefix = prefix.split(/[^\/]+\/\z/).first
93
- Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
158
+ Brutalismbot.logger.info "GET s3://#{@bucket}/#{prefix}*"
94
159
  end
95
160
 
96
- # Return max by key
97
- keys.max{|a,b| a.key <=> b.key }
98
- end
99
-
100
- def max_time
101
- max_key.key.match(/(\d+).json\z/).to_a.last.to_i
102
- end
103
-
104
- def prefix_for(time:)
105
- time = Time.at(time.to_i).utc
106
- year = time.strftime '%Y'
107
- month = time.strftime '%Y-%m'
108
- day = time.strftime '%Y-%m-%d'
109
- "#{@prefix}year=#{year}/month=#{month}/day=#{day}/"
110
- end
161
+ # Get max by key
162
+ max_key = keys.max{|a,b| a.key <=> b.key }
111
163
 
112
- def put(post:, dryrun:nil)
113
- key = "#{prefix_for time: post.created_utc}#{post.created_utc.to_i}.json"
114
- super key: key, body: post.to_json, dryrun: dryrun
164
+ # Parse as Post
165
+ Post[JSON.parse max_key.get.body.read]
115
166
  end
116
167
 
117
- def update(posts:, dryrun:nil)
118
- posts.map{|post| put post: post, dryrun: dryrun }
168
+ def pull(options = {})
169
+ options[:before] ||= last.fullname
170
+ R::Brutalism.new.posts(:new, options).reverse_each.map{|x| put x }
119
171
  end
120
172
  end
121
173
  end
@@ -1,3 +1,3 @@
1
1
  module Brutalismbot
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/brutalismbot.rb CHANGED
@@ -1,9 +1,12 @@
1
- require "brutalismbot/r"
2
- require "brutalismbot/s3"
3
- require "brutalismbot/version"
4
1
  require "logger"
2
+ require "securerandom"
5
3
  require "net/https"
6
4
 
5
+ require "brutalismbot/auth"
6
+ require "brutalismbot/post"
7
+ require "brutalismbot/r"
8
+ require "brutalismbot/version"
9
+
7
10
  module Brutalismbot
8
11
  class << self
9
12
  @@config = {}
@@ -28,87 +31,4 @@ module Brutalismbot
28
31
 
29
32
  class Error < StandardError
30
33
  end
31
-
32
- class Auth < Hash
33
- def channel_id
34
- dig "incoming_webhook", "channel_id"
35
- end
36
-
37
- def post(body:, dryrun:nil)
38
- uri = URI.parse webhook_url
39
- ssl = uri.scheme == "https"
40
- Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
41
- if dryrun
42
- Brutalismbot.logger.info "POST DRYRUN #{uri}"
43
- else
44
- Brutalismbot.logger.info "POST #{uri}"
45
- req = Net::HTTP::Post.new uri, "content-type" => "application/json"
46
- req.body = body
47
- http.request req
48
- end
49
- end
50
- end
51
-
52
- def team_id
53
- dig "team_id"
54
- end
55
-
56
- def webhook_url
57
- dig "incoming_webhook", "url"
58
- end
59
- end
60
-
61
- class Post < Hash
62
- def created_after(time:)
63
- created_utc.to_i > time.to_i
64
- end
65
-
66
- def created_utc
67
- Time.at(dig("data", "created_utc").to_i).utc
68
- end
69
-
70
- def permalink
71
- dig "data", "permalink"
72
- end
73
-
74
- def title
75
- dig "data", "title"
76
- end
77
-
78
- def to_slack
79
- {
80
- blocks: [
81
- {
82
- type: "image",
83
- title: {
84
- type: "plain_text",
85
- text: "/r/brutalism",
86
- emoji: true,
87
- },
88
- image_url: url,
89
- alt_text: title,
90
- },
91
- {
92
- type: "context",
93
- elements: [
94
- {
95
- type: "mrkdwn",
96
- text: "<https://reddit.com#{permalink}|#{title}>",
97
- },
98
- ],
99
- },
100
- ],
101
- }
102
- end
103
-
104
- def url
105
- images = dig "data", "preview", "images"
106
- source = images.map{|x| x["source"] }.compact.max do |a,b|
107
- a.slice("width", "height").values <=> b.slice("width", "height").values
108
- end
109
- CGI.unescapeHTML source.dig("url")
110
- rescue NoMethodError
111
- dig("data", "media_metadata")&.values&.first&.dig("s", "u")
112
- end
113
- end
114
34
  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: 0.4.0
4
+ version: 0.5.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: 2019-06-15 00:00:00.000000000 Z
11
+ date: 2019-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-s3
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.16'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.6'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.6'
111
125
  description: 'A Slack app that mirrors posts from /r/brutalism to a #channel of your
112
126
  choosing using incoming webhooks.'
113
127
  email:
@@ -119,6 +133,8 @@ files:
119
133
  - LICENSE.txt
120
134
  - README.md
121
135
  - lib/brutalismbot.rb
136
+ - lib/brutalismbot/auth.rb
137
+ - lib/brutalismbot/post.rb
122
138
  - lib/brutalismbot/r.rb
123
139
  - lib/brutalismbot/s3.rb
124
140
  - lib/brutalismbot/version.rb