brutalismbot 0.6.1 → 1.0.0.beta.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: 3f5d9a6bba9f8e46a8bfd482cc6342dc1f550fb37ac6cba15d425042b0d5cb59
4
- data.tar.gz: b7018131ceaadba8f98322a7d2e110e1ecfedd67d442d75beca66aee00d27896
3
+ metadata.gz: bebc26a610a2e90743db822c2915c0a5d66a1f1137b9f96013af498e3a8754a1
4
+ data.tar.gz: a82a2358a5171bc37ff84fdee8535c4136ddf2a47e4a902f789bbad1d3fe4a67
5
5
  SHA512:
6
- metadata.gz: e752b362820e81a6ddfdaa7e9a4b8b3e4f3fce841dbe87de2bc34879eedc760dbac5dc7f5a0930408878dffd92cd5ff8174406a859f2e4eba4e7b76c55fe3254
7
- data.tar.gz: 8d8b6780fe0d0f8de0e0c500cc87a4627477ccd9d73bb4705ff32c8e1cab719921be45efdd11f00e4663f9fab379c6feed74093fb1e7ae3b505d9b6f11a7c0c7
6
+ metadata.gz: c9d9b9da0b23ae47ca6f4e4263064d5e8560c714b75dd3eaafce89503106408f6fc8273dc82628f3adc53eaf9527c92bf5e7e6e2c3b9eae60dab537d1336b166
7
+ data.tar.gz: 2a6035ffe8aa7836314bc3141cc6135e135f19af3c858f3c3477819467140bc6fd8e3c48ad42b7c49d02069baadc82290176b96713054e5963606fb3b7c3b4e3
@@ -0,0 +1,21 @@
1
+ require "forwardable"
2
+ require "json"
3
+
4
+ module Brutalismbot
5
+ module Parser
6
+ def parse(source, opts = {})
7
+ new JSON.parse(source, opts)
8
+ end
9
+ end
10
+
11
+ class Base
12
+ extend Forwardable
13
+ extend Parser
14
+
15
+ def_delegators :@item, :[], :dig, :fetch, :to_h, :to_json
16
+
17
+ def initialize(item = {})
18
+ @item = JSON.parse(item.to_json)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ require "brutalismbot/posts/client"
2
+ require "brutalismbot/reddit/client"
3
+ require "brutalismbot/slack/client"
4
+ require "brutalismbot/twitter/client"
5
+
6
+ module Brutalismbot
7
+ class Client
8
+ attr_reader :posts, :reddit, :slack, :twitter
9
+
10
+ def initialize(posts:nil, reddit:nil, slack:nil, twitter:nil)
11
+ @posts = posts || Posts::Client.new
12
+ @reddit = reddit || Reddit::Client.new
13
+ @slack = slack || Slack::Client.new
14
+ @twitter = twitter || Twitter::Client.new
15
+ end
16
+
17
+ def lag_time
18
+ lag = ENV["BRUTALISMBOT_LAG_TIME"].to_s
19
+ lag.empty? ? 7200 : lag.to_i
20
+ end
21
+
22
+ def pull(min_time:nil, max_time:nil, dryrun:nil)
23
+ # Get time window for new posts
24
+ min_time ||= @posts.max_time
25
+ max_time ||= Time.now.utc.to_i - lag_time
26
+
27
+ # Get posts
28
+ posts = @reddit.list(:new)
29
+ posts = posts.select{|post| post.created_between?(min_time, max_time) }
30
+ posts = posts.sort{|a,b| a.created_utc <=> b.created_utc }
31
+
32
+ # Persist posts
33
+ posts.each{|post| @posts.push(post, dryrun: dryrun) }
34
+ end
35
+
36
+ def push(post, dryrun:nil)
37
+ # Push to Twitter
38
+ @twitter.push(post, dryrun: dryrun)
39
+
40
+ # Push to Slack
41
+ @slack.push(post, dryrun: dryrun)
42
+
43
+ nil
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ require "logger"
2
+
3
+ module Brutalismbot
4
+ module Logger
5
+ def logger
6
+ # @@logger ||= ::Logger.new(File::NULL)
7
+ @@logger ||= ::Logger.new(STDOUT, formatter: -> (*x) { "#{x.last}\n" })
8
+ end
9
+
10
+ def logger=(logger)
11
+ @@logger = logger
12
+ end
13
+ end
14
+
15
+ extend Logger
16
+ end
@@ -0,0 +1,58 @@
1
+ require "aws-sdk-s3"
2
+
3
+ require "brutalismbot/logger"
4
+ require "brutalismbot/s3/client"
5
+ require "brutalismbot/s3/prefix"
6
+ require "brutalismbot/reddit/post"
7
+
8
+ module Brutalismbot
9
+ module Posts
10
+ class Client < S3::Client
11
+ def initialize(bucket:nil, prefix:nil, client:nil)
12
+ bucket ||= ENV["POSTS_S3_BUCKET"] || "brutalismbot"
13
+ prefix ||= ENV["POSTS_S3_PREFIX"] || "data/v1/posts/"
14
+ super
15
+ end
16
+
17
+ def key_for(post)
18
+ File.join(@prefix, post.path)
19
+ end
20
+
21
+ def last
22
+ Reddit::Post.parse(max_key.get.body.read)
23
+ end
24
+
25
+ def list(options = {})
26
+ super(options) do |object|
27
+ Brutalismbot.logger.info("GET s3://#{@bucket.name}/#{object.key}")
28
+ Reddit::Post.parse(object.get.body.read)
29
+ end
30
+ end
31
+
32
+ def max_key
33
+ # Dig for max key
34
+ prefix = Time.now.utc.strftime("#{@prefix}year=%Y/month=%Y-%m/day=%Y-%m-%d/")
35
+ Brutalismbot.logger.info("GET s3://#{@bucket.name}/#{prefix}*")
36
+
37
+ # Go up a level in prefix if no keys found
38
+ until (keys = @bucket.objects(prefix: prefix)).any?
39
+ prefix = prefix.split(/[^\/]+\/\z/).first
40
+ Brutalismbot.logger.info("GET s3://#{@bucket.name}/#{prefix}*")
41
+ end
42
+
43
+ # Return max by key
44
+ keys.max{|a,b| a.key <=> b.key }
45
+ end
46
+
47
+ def max_time
48
+ max_key.key[/(\d+).json\z/, -1].to_i
49
+ end
50
+
51
+ def push(post, dryrun:nil)
52
+ key = key_for(post)
53
+ Brutalismbot.logger.info("PUT #{"DRYRUN " if dryrun}s3://#{@bucket.name}/#{key}")
54
+ @bucket.put_object(key: key, body: post.to_json) unless dryrun
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,28 @@
1
+ require "brutalismbot/reddit/post"
2
+
3
+ module Brutalismbot
4
+ module Posts
5
+ class Client
6
+ class << self
7
+ def stub(&block)
8
+ client = new(prefix: "data/test/posts/")
9
+ client.instance_variable_set(:@stubbed, true)
10
+
11
+ block = -> { [Reddit::Post.stub] } unless block_given?
12
+ items = block.call.map{|x| [client.key_for(x), x.to_h] }.to_h
13
+
14
+ client.client.stub_responses :list_objects, -> (context) do
15
+ keys = items.keys.select{|x| x.start_with? context.params[:prefix] }
16
+ {contents: keys.map{|x| {key:x} }}
17
+ end
18
+
19
+ client.client.stub_responses :get_object, -> (context) do
20
+ {body: StringIO.new(items.fetch(context.params[:key]).to_json)}
21
+ end
22
+
23
+ client
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require "aws-sdk-s3"
2
+
3
+ require "brutalismbot/version"
4
+ require "brutalismbot/reddit/resource"
5
+
6
+ module Brutalismbot
7
+ module Reddit
8
+ class Client
9
+ attr_reader :endpoint, :user_agent
10
+
11
+ def initialize(endpoint:nil, user_agent:nil)
12
+ @endpoint = endpoint || ENV["REDDIT_ENDPOINT"] || "https://www.reddit.com/r/brutalism"
13
+ @user_agent = user_agent || ENV["REDDIT_USER_AGENT"] || "Brutalismbot v#{Brutalismbot::VERSION}"
14
+ end
15
+
16
+ def list(resource, options = {})
17
+ url = File.join(@endpoint, "#{resource}.json")
18
+ qry = URI.encode_www_form(options)
19
+ uri = "#{url}?#{qry}"
20
+ Resource.new(uri: uri, user_agent: @user_agent)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,111 @@
1
+ require "forwardable"
2
+ require "json"
3
+
4
+ require "brutalismbot/base"
5
+
6
+ module Brutalismbot
7
+ module Reddit
8
+ class Post < Base
9
+ def created_after?(time = nil)
10
+ time.nil? || created_utc.to_i > time.to_i
11
+ end
12
+
13
+ def created_before?(time = nil)
14
+ time.nil? || created_utc.to_i < time.to_i
15
+ end
16
+
17
+ def created_between?(start, stop)
18
+ created_after?(start) && created_before?(stop)
19
+ end
20
+
21
+ def created_utc
22
+ Time.at(data["created_utc"].to_i).utc
23
+ end
24
+
25
+ def fullname
26
+ "#{kind}_#{id}"
27
+ end
28
+
29
+ def data
30
+ @item.fetch("data", {})
31
+ end
32
+
33
+ def id
34
+ data["id"]
35
+ end
36
+
37
+ def kind
38
+ @item["kind"]
39
+ end
40
+
41
+ def path
42
+ created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
43
+ end
44
+
45
+ def permalink
46
+ "https://reddit.com#{data["permalink"]}"
47
+ end
48
+
49
+ def title
50
+ CGI.unescapeHTML(data["title"])
51
+ end
52
+
53
+ def url
54
+ images = data.dig("preview", "images") || {}
55
+ source = images.map{|x| x["source"] }.compact.max do |a,b|
56
+ a.slice("width", "height").values <=> b.slice("width", "height").values
57
+ end
58
+ CGI.unescapeHTML(source.dig("url"))
59
+ rescue NoMethodError
60
+ data["media_metadata"]&.values&.first&.dig("s", "u")
61
+ end
62
+
63
+ def to_slack
64
+ {
65
+ blocks: unless url.nil?
66
+ [
67
+ {
68
+ type: "image",
69
+ title: {
70
+ type: "plain_text",
71
+ text: "/r/brutalism",
72
+ emoji: true,
73
+ },
74
+ image_url: url,
75
+ alt_text: title,
76
+ },
77
+ {
78
+ type: "context",
79
+ elements: [
80
+ {
81
+ type: "mrkdwn",
82
+ text: "<#{permalink}|#{title}>",
83
+ },
84
+ ],
85
+ },
86
+ ]
87
+ else
88
+ [
89
+ {
90
+ type: "section",
91
+ text: {
92
+ type: "mrkdwn",
93
+ text: "<#{permalink}|#{title}>",
94
+ },
95
+ accessory: {
96
+ type: "image",
97
+ image_url: "https://brutalismbot.com/logo-red-ppl.png",
98
+ alt_text: "/r/brutalism"
99
+ }
100
+ }
101
+ ]
102
+ end
103
+ }
104
+ end
105
+
106
+ def to_twitter
107
+ [title, permalink].join("\n")
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,42 @@
1
+ require "net/http"
2
+
3
+ require "brutalismbot/logger"
4
+ require "brutalismbot/reddit/post"
5
+
6
+ module Brutalismbot
7
+ module Reddit
8
+ class Resource
9
+ include Enumerable
10
+
11
+ attr_reader :uri, :user_agent
12
+
13
+ def initialize(uri:nil, user_agent:nil)
14
+ @uri = uri || "https://www.reddit.com/r/brutalism/new.json"
15
+ @user_agent = user_agent || "Brutalismbot v#{Brutalismbot::VERSION}"
16
+ end
17
+
18
+ def each
19
+ Brutalismbot.logger.info("GET #{@uri}")
20
+ uri = URI.parse(@uri)
21
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
22
+ request = Net::HTTP::Get.new(uri, "user-agent" => @user_agent)
23
+ response = JSON.parse(http.request(request).body)
24
+ children = response.dig("data", "children") || []
25
+ children.each do |child|
26
+ post = Post.new(child)
27
+ Brutalismbot.logger.warn("NO PHOTO URL for #{post.permalink}") if post.url.nil?
28
+ yield post
29
+ end
30
+ end
31
+ end
32
+
33
+ def all
34
+ to_a
35
+ end
36
+
37
+ def last
38
+ to_a.last
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ require "securerandom"
2
+
3
+ module Brutalismbot
4
+ module Reddit
5
+ class Post
6
+ class << self
7
+ def stub(created_utc:nil, post_id:nil, permalink_id:nil, image_id:nil)
8
+ created_utc ||= Time.now.utc - rand(86400) - 86400
9
+ post_id ||= SecureRandom.alphanumeric(6).downcase
10
+ permalink_id ||= SecureRandom.alphanumeric.downcase
11
+ image_id ||= SecureRandom.alphanumeric
12
+ new(
13
+ kind: "t3",
14
+ data: {
15
+ id: post_id,
16
+ created_utc: created_utc.to_i,
17
+ permalink: "/r/brutalism/comments/#{permalink_id}/test/",
18
+ title: "Post to /r/brutalism",
19
+ preview: {
20
+ images: [
21
+ {
22
+ source: {
23
+ url: "https://preview.redd.it/#{image_id}.jpg",
24
+ width: 1000,
25
+ height: 1000,
26
+ },
27
+ },
28
+ {
29
+ source: {
30
+ url: "https://preview.redd.it/small.jpg",
31
+ width: 500,
32
+ height: 500,
33
+ }
34
+ }
35
+ ],
36
+ },
37
+ },
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ require "aws-sdk-s3"
2
+
3
+ module Brutalismbot
4
+ module S3
5
+ class Client
6
+ attr_reader :bucket, :prefix, :client
7
+
8
+ def initialize(bucket:nil, prefix:nil, client:nil)
9
+ bucket ||= ENV["S3_BUCKET"] || "brutalismbot"
10
+ prefix ||= ENV["S3_PREFIX"] || "data/v1/"
11
+ client ||= Aws::S3::Client.new
12
+ @bucket = Aws::S3::Bucket.new(name: bucket, client: client)
13
+ @client = client
14
+ @prefix = prefix
15
+ end
16
+
17
+ def list(options = {}, &block)
18
+ Brutalismbot.logger.info("LIST s3://#{@bucket.name}/#{@prefix}*")
19
+ prefix = @bucket.objects({prefix: @prefix}.merge(options))
20
+ Prefix.new(prefix, &block)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ require "aws-sdk-s3"
2
+
3
+ require "brutalismbot/logger"
4
+ require "brutalismbot/s3/client"
5
+
6
+ module Brutalismbot
7
+ module S3
8
+ class Prefix
9
+ include Enumerable
10
+
11
+ def initialize(prefix, &block)
12
+ @prefix = prefix
13
+ @block = block if block_given?
14
+ end
15
+
16
+ def each
17
+ @prefix.each do |object|
18
+ yield @block.nil? ? object : @block.call(object)
19
+ end
20
+ end
21
+
22
+ def all
23
+ to_a
24
+ end
25
+
26
+ def last
27
+ to_a.last
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ require "forwardable"
2
+ require "json"
3
+ require "net/http"
4
+
5
+ require "brutalismbot/logger"
6
+ require "brutalismbot/base"
7
+
8
+ module Brutalismbot
9
+ module Slack
10
+ class Auth < Base
11
+ def channel_id
12
+ @item.dig("incoming_webhook", "channel_id")
13
+ end
14
+
15
+ def team_id
16
+ @item.dig("team_id")
17
+ end
18
+
19
+ def webhook_url
20
+ @item.dig("incoming_webhook", "url")
21
+ end
22
+
23
+ def path
24
+ File.join("team=#{team_id}", "channel=#{channel_id}", "oauth.json")
25
+ end
26
+
27
+ def push(post, dryrun:nil)
28
+ body = post.to_slack.to_json
29
+ uri = URI.parse(webhook_url)
30
+ Brutalismbot.logger.info("POST #{"DRYRUN " if dryrun}#{uri}")
31
+ unless dryrun
32
+ ssl = uri.scheme == "https"
33
+ req = Net::HTTP::Post.new(uri, "content-type" => "application/json")
34
+ req.body = body
35
+ Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
36
+ http.request(req)
37
+ end
38
+ else
39
+ Net::HTTPOK.new("1.1", "204", "ok")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ require "aws-sdk-s3"
2
+
3
+ require "brutalismbot/logger"
4
+ require "brutalismbot/s3/client"
5
+ require "brutalismbot/s3/prefix"
6
+ require "brutalismbot/slack/auth"
7
+
8
+ module Brutalismbot
9
+ module Slack
10
+ class Client < S3::Client
11
+ def initialize(bucket:nil, prefix:nil, client:nil)
12
+ bucket ||= ENV["SLACK_S3_BUCKET"] || "brutalismbot"
13
+ prefix ||= ENV["SLACK_S3_PREFIX"] || "data/v1/auths/"
14
+ super
15
+ end
16
+
17
+ def install(auth, dryrun:nil)
18
+ key = key_for(auth)
19
+ Brutalismbot.logger.info("PUT #{"DRYRUN " if dryrun}s3://#{@bucket.name}/#{key}")
20
+ @bucket.put_object(key: key, body: auth.to_json) unless dryrun
21
+ end
22
+
23
+ def key_for(auth)
24
+ File.join(@prefix, auth.path)
25
+ end
26
+
27
+ def list(options = {})
28
+ super(options) do |object|
29
+ Brutalismbot.logger.info("GET s3://#{@bucket.name}/#{object.key}")
30
+ Auth.parse(object.get.body.read)
31
+ end
32
+ end
33
+
34
+ def push(post, dryrun:nil)
35
+ list.map do |auth|
36
+ key = key_for(auth)
37
+ Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}s3://#{@bucket.name}/#{key}")
38
+ auth.push(post, dryrun: dryrun)
39
+ end
40
+ end
41
+
42
+ def uninstall(auth, dryrun:nil)
43
+ key = key_for(auth)
44
+ Brutalismbot.logger.info("DELETE #{"DRYRUN " if dryrun}s3://#{@bucket.name}/#{key}")
45
+ @bucket.delete_objects(delete: {objects: [{key: key}]}) unless dryrun
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ require "securerandom"
2
+
3
+ module Brutalismbot
4
+ module Slack
5
+ class Client
6
+ class << self
7
+ def stub(&block)
8
+ client = new(prefix: "data/test/auths/")
9
+ client.instance_variable_set(:@stubbed, true)
10
+
11
+ block = -> { [Auth.stub] } unless block_given?
12
+ items = block.call.map{|x| [client.key_for(x), x.to_h] }.to_h
13
+
14
+ client.client.stub_responses :list_objects, -> (context) do
15
+ {contents: items.keys.map{|x| {key:x} }}
16
+ end
17
+
18
+ client.client.stub_responses :get_object, -> (context) do
19
+ {body: StringIO.new(items.fetch(context.params[:key]).to_json)}
20
+ end
21
+
22
+ client
23
+ end
24
+ end
25
+ end
26
+
27
+ class Auth
28
+ class << self
29
+ def stub(bot_id:nil, channel_id:nil, team_id:nil, user_id:nil)
30
+ bot_id ||= "B#{SecureRandom.alphanumeric(8).upcase}"
31
+ channel_id ||= "C#{SecureRandom.alphanumeric(8).upcase}"
32
+ team_id ||= "T#{SecureRandom.alphanumeric(8).upcase}"
33
+ user_id ||= "U#{SecureRandom.alphanumeric(8).upcase}"
34
+ new(
35
+ ok: true,
36
+ access_token: "<token>",
37
+ scope: "identify,incoming-webhook",
38
+ user_id: user_id,
39
+ team_name: "My Team",
40
+ team_id: team_id,
41
+ incoming_webhook: {
42
+ channel: "#brutalism",
43
+ channel_id: channel_id,
44
+ configuration_url: "https://my-team.slack.com/services/#{bot_id}",
45
+ url: "https://hooks.slack.com/services/#{team_id}/#{bot_id}/1234567890abcdef12345678",
46
+ },
47
+ scopes: [
48
+ "identify",
49
+ "incoming-webhook",
50
+ ],
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ require "brutalismbot"
2
+ require "brutalismbot/posts/stub"
3
+ require "brutalismbot/reddit/stub"
4
+ require "brutalismbot/slack/stub"
5
+
6
+ Aws.config.update(stub_responses: true)
7
+
8
+ module Brutalismbot
9
+ class Client
10
+ class << self
11
+ def stub
12
+ new(
13
+ posts: Posts::Client.stub,
14
+ slack: Slack::Client.stub,
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ require "open-uri"
2
+ require "tempfile"
3
+
4
+ require "twitter"
5
+
6
+ require "brutalismbot/logger"
7
+
8
+ module Brutalismbot
9
+ module Twitter
10
+ class Client
11
+ attr_reader :client
12
+
13
+ def initialize(client:nil)
14
+ @client = client || ::Twitter::REST::Client.new do |config|
15
+ config.access_token = ENV["TWITTER_ACCESS_TOKEN"]
16
+ config.access_token_secret = ENV["TWITTER_ACCESS_TOKEN_SECRET"]
17
+ config.consumer_key = ENV["TWITTER_CONSUMER_KEY"]
18
+ config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"]
19
+ end
20
+ end
21
+
22
+ def push(post, dryrun:nil)
23
+ Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
24
+ status = post.to_twitter
25
+ if post.url.nil?
26
+ @client.update(status) unless dryrun
27
+ else
28
+ uri = URI.parse(post.url)
29
+ Brutalismbot.logger.info("GET #{uri}")
30
+ uri.open do |media|
31
+ @client.update_with_media(status, media) unless dryrun
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Brutalismbot
2
- VERSION = "0.6.1"
2
+ VERSION = "1.0.0.beta.0"
3
3
  end
data/lib/brutalismbot.rb CHANGED
@@ -1,34 +1,7 @@
1
- require "logger"
2
- require "securerandom"
3
- require "net/https"
4
-
5
- require "brutalismbot/auth"
6
- require "brutalismbot/post"
7
- require "brutalismbot/r"
8
1
  require "brutalismbot/version"
2
+ require "brutalismbot/logger"
3
+ require "brutalismbot/client"
9
4
 
10
5
  module Brutalismbot
11
- class << self
12
- @@config = {}
13
- @@logger = Logger.new File::NULL
14
-
15
- def config
16
- @@config
17
- end
18
-
19
- def config=(config)
20
- @@config = config || {}
21
- end
22
-
23
- def logger
24
- config[:logger] || @@logger
25
- end
26
-
27
- def logger=(logger)
28
- config[:logger] = logger
29
- end
30
- end
31
-
32
- class Error < StandardError
33
- end
6
+ class Error < StandardError; end
34
7
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brutalismbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 1.0.0.beta.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-09-14 00:00:00.000000000 Z
11
+ date: 2019-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: twitter
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: aws-sdk-s3
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
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'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: pry
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -119,10 +147,22 @@ files:
119
147
  - LICENSE.txt
120
148
  - README.md
121
149
  - lib/brutalismbot.rb
122
- - lib/brutalismbot/auth.rb
123
- - lib/brutalismbot/post.rb
124
- - lib/brutalismbot/r.rb
125
- - lib/brutalismbot/s3.rb
150
+ - lib/brutalismbot/base.rb
151
+ - lib/brutalismbot/client.rb
152
+ - lib/brutalismbot/logger.rb
153
+ - lib/brutalismbot/posts/client.rb
154
+ - lib/brutalismbot/posts/stub.rb
155
+ - lib/brutalismbot/reddit/client.rb
156
+ - lib/brutalismbot/reddit/post.rb
157
+ - lib/brutalismbot/reddit/resource.rb
158
+ - lib/brutalismbot/reddit/stub.rb
159
+ - lib/brutalismbot/s3/client.rb
160
+ - lib/brutalismbot/s3/prefix.rb
161
+ - lib/brutalismbot/slack/auth.rb
162
+ - lib/brutalismbot/slack/client.rb
163
+ - lib/brutalismbot/slack/stub.rb
164
+ - lib/brutalismbot/stub.rb
165
+ - lib/brutalismbot/twitter/client.rb
126
166
  - lib/brutalismbot/version.rb
127
167
  homepage: https://brutalismbot.com
128
168
  licenses:
@@ -139,9 +179,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
179
  version: '0'
140
180
  required_rubygems_version: !ruby/object:Gem::Requirement
141
181
  requirements:
142
- - - ">="
182
+ - - ">"
143
183
  - !ruby/object:Gem::Version
144
- version: '0'
184
+ version: 1.3.1
145
185
  requirements: []
146
186
  rubygems_version: 3.0.3
147
187
  signing_key:
@@ -1,57 +0,0 @@
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
@@ -1,102 +0,0 @@
1
- module Brutalismbot
2
- class Post < Hash
3
- def created_after?(time = nil)
4
- time.nil? || created_utc.to_i > time.to_i
5
- end
6
-
7
- def created_before?(time = nil)
8
- time.nil? || created_utc.to_i < time.to_i
9
- end
10
-
11
- def created_between?(start, stop)
12
- created_after?(start) && created_before?(stop)
13
- end
14
-
15
- def created_utc
16
- Time.at(dig("data", "created_utc").to_i).utc
17
- end
18
-
19
- def fullname
20
- "#{kind}_#{id}"
21
- end
22
-
23
- def id
24
- dig "data", "id"
25
- end
26
-
27
- def kind
28
- dig "kind"
29
- end
30
-
31
- def permalink
32
- dig "data", "permalink"
33
- end
34
-
35
- def title
36
- dig "data", "title"
37
- end
38
-
39
- def to_slack
40
- {
41
- blocks: [
42
- {
43
- type: "image",
44
- title: {
45
- type: "plain_text",
46
- text: "/r/brutalism",
47
- emoji: true,
48
- },
49
- image_url: url,
50
- alt_text: title,
51
- },
52
- {
53
- type: "context",
54
- elements: [
55
- {
56
- type: "mrkdwn",
57
- text: "<https://reddit.com#{permalink}|#{title}>",
58
- },
59
- ],
60
- },
61
- ],
62
- }
63
- end
64
-
65
- def url
66
- images = dig "data", "preview", "images"
67
- source = images.map{|x| x["source"] }.compact.max do |a,b|
68
- a.slice("width", "height").values <=> b.slice("width", "height").values
69
- end
70
- CGI.unescapeHTML source.dig("url")
71
- rescue NoMethodError
72
- dig("data", "media_metadata")&.values&.first&.dig("s", "u")
73
- end
74
-
75
- class << self
76
- def stub
77
- created_utc = Time.now.utc - rand(86400) - 86400
78
- post_id = SecureRandom.alphanumeric(6).downcase
79
- permalink_id = SecureRandom.alphanumeric.downcase
80
- image_id = SecureRandom.alphanumeric
81
- Post[{
82
- "kind" => "t3",
83
- "data" => {
84
- "id" => post_id,
85
- "created_utc" => created_utc.to_i,
86
- "permalink" => "/r/brutalism/comments/#{permalink_id}/test/",
87
- "title" => "Post to /r/brutalism",
88
- "preview" => {
89
- "images" => [
90
- {
91
- "source" => {
92
- "url" => "https://preview.redd.it/#{image_id}.jpg",
93
- }
94
- }
95
- ]
96
- }
97
- }
98
- }]
99
- end
100
- end
101
- end
102
- end
@@ -1,54 +0,0 @@
1
- module Brutalismbot
2
- module R
3
- class PostCollection
4
- include Enumerable
5
-
6
- def initialize(uri:, user_agent:)
7
- @uri = uri
8
- @ssl = uri.scheme == "https"
9
- @user_agent = user_agent
10
- end
11
-
12
- def each
13
- Brutalismbot.logger.info "GET #{@uri}"
14
- Net::HTTP.start(@uri.host, @uri.port, use_ssl: @ssl) do |http|
15
- request = Net::HTTP::Get.new @uri, "user-agent" => @user_agent
16
- response = JSON.parse http.request(request).body
17
- children = response.dig("data", "children") || []
18
- children.each{|x| yield Post[x] }
19
- end
20
- end
21
-
22
- def all
23
- to_a
24
- end
25
-
26
- def last
27
- to_a.last
28
- end
29
- end
30
-
31
- class Subreddit
32
- attr_reader :endpoint, :user_agent
33
-
34
- def initialize(endpoint:nil, user_agent:nil)
35
- @endpoint = endpoint
36
- @user_agent = user_agent || "Brutalismbot #{VERSION}"
37
- end
38
-
39
- def posts(resource, params = {})
40
- url = File.join @endpoint, "#{resource}.json"
41
- qry = URI.encode_www_form params
42
- uri = URI.parse "#{url}?#{qry}"
43
- PostCollection.new uri: uri, user_agent: @user_agent
44
- end
45
- end
46
-
47
- class Brutalism < Subreddit
48
- def initialize(endpoint:nil, user_agent:nil)
49
- endpoint ||= "https://www.reddit.com/r/brutalism"
50
- super
51
- end
52
- end
53
- end
54
- end
@@ -1,181 +0,0 @@
1
- require "aws-sdk-s3"
2
-
3
- require "brutalismbot"
4
-
5
- module Brutalismbot
6
- module S3
7
- class Prefix
8
- include Enumerable
9
-
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 }
22
- end
23
- end
24
-
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
- }
107
- end
108
-
109
- def subreddit(endpoint:nil, user_agent:nil)
110
- R::Brutalism.new endpoint:endpoint, user_agent: user_agent
111
- end
112
- end
113
-
114
- class Client < Prefix
115
- def auths
116
- prefix = File.join @prefix, "auths/"
117
- AuthCollection.new bucket: @bucket, prefix: prefix, client: @client
118
- end
119
-
120
- def posts
121
- prefix = File.join @prefix, "posts/"
122
- PostCollection.new bucket: @bucket, prefix: prefix, client: @client
123
- end
124
- end
125
-
126
- class AuthCollection < Prefix
127
- def each
128
- super{|x| yield Auth[JSON.parse x.get.body.read] }
129
- end
130
-
131
- def key_for(auth)
132
- File.join @prefix, "team=#{auth.team_id}/channel=#{auth.channel_id}/oauth.json"
133
- end
134
-
135
- def mirror(post, options = {})
136
- options[:body] = post.to_slack.to_json
137
- map{|x| x.post options }
138
- end
139
- end
140
-
141
- class PostCollection < Prefix
142
- def each
143
- super{|x| yield Post[JSON.parse x.get.body.read] }
144
- end
145
-
146
- def key_for(post)
147
- File.join @prefix, post.created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
148
- end
149
-
150
- def last
151
- Post[JSON.parse max_key.get.body.read]
152
- end
153
-
154
- def max_key
155
- # Dig for max key
156
- prefix = Time.now.utc.strftime "#{@prefix}year=%Y/month=%Y-%m/day=%Y-%m-%d/"
157
- Brutalismbot.logger.info "GET s3://#{@bucket}/#{prefix}*"
158
-
159
- # Go up a level in prefix if no keys found
160
- until (keys = bucket.objects(prefix: prefix)).any?
161
- prefix = prefix.split(/[^\/]+\/\z/).first
162
- Brutalismbot.logger.info "GET s3://#{@bucket}/#{prefix}*"
163
- end
164
-
165
- # Return max by key
166
- keys.max{|a,b| a.key <=> b.key }
167
- end
168
-
169
- def max_time
170
- max_key.key.match(/(\d+).json\z/).to_a.last.to_i
171
- end
172
-
173
- def pull(min_time:nil, max_time:nil, endpoint:nil, user_agent:nil)
174
- posts = subreddit(endpoint: endpoint, user_agent: user_agent).posts(:new)
175
- posts = posts.select{|x| x.created_between?(min_time, max_time) }
176
- posts = posts.sort{|a,b| a.created_utc <=> b.created_utc }
177
- posts.map{|x| put x }
178
- end
179
- end
180
- end
181
- end