brutalismbot 0.6.1 → 1.0.0.beta.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: 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