brutalismbot 1.8.0 → 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: fc9aa96ddf426ba53cd94234a85ab6b3b11237dcdfb8414ca522f983a58c807c
4
- data.tar.gz: 87ccc44e65a2565e31a3e60c399ab2d6725f6594a12da582c273e9b95292020d
3
+ metadata.gz: b7a72ed65ec740aef21cb54256d7f8f9b9e3128a039eb217711ee02c372683bf
4
+ data.tar.gz: 75b2ebef6b98ec94b716fe00cb1f657b386e9d9c40d89211b37443c8f0d98fb0
5
5
  SHA512:
6
- metadata.gz: a8160fd997677a8ab632be035009b1cea925c4791cc45109c9cba086eda92408b4d931394efa92a1b438357a9784206b2479ccf191397dd66f0beeb809af7d6f
7
- data.tar.gz: bba9c3dda2a30a14209d74599a4bcd01e0cce55caa138970135fd001a0a9db79272c8acdb4d0bef6dce89970ac0cda8bbfca181d7d4febb0bcb8103e581e96f0
6
+ metadata.gz: 80fb1a88e38e9d1fc5988d7586aebd1859339cf9c3bcd67b08cb7a587f5b0c6c0ab6c13db295f2569c19088637ade5a5e2a28c7ed7064ed57a63e5329a1747b5
7
+ data.tar.gz: eca017057b7c3a4d0c2d184ea8a328af49e319d96ac02f42e51977a65a5677e31fedd770a484c960c78e5b1f5bcc1ed834c0239ebc9bd12c7d6f320ea24bbe9b
data/README.md CHANGED
@@ -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
@@ -38,7 +38,9 @@ module Brutalismbot
38
38
  @twitter.push(post, dryrun: dryrun)
39
39
 
40
40
  # Push to Slack
41
- @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
42
44
 
43
45
  nil
44
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,131 +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)
52
- end
53
-
54
- def media_url
55
- # Use URL if it's an image
56
- if mime_type.start_with?("image/")
57
- data["url"]
58
-
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
67
- end
68
-
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
79
- end
80
-
81
- def mime_type=(value)
82
- @mime_type = value
83
- end
84
-
85
- def path
86
- created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
54
+ def media_metadata
55
+ data["media_metadata"]
87
56
  end
88
57
 
89
58
  def permalink
90
59
  "https://reddit.com#{data["permalink"]}"
91
60
  end
92
61
 
93
- def title
94
- CGI.unescapeHTML(data["title"])
62
+ def preview_images
63
+ data.dig("preview", "images")
95
64
  end
96
65
 
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
- }
66
+ def title
67
+ CGI.unescape_html(data["title"])
105
68
  end
106
69
 
107
- def to_slack
108
- is_self? ? to_slack_text : to_slack_image
70
+ def url
71
+ data["url"]
109
72
  end
110
73
 
111
- def to_twitter
112
- max = 280 - permalink.length - 1
113
- status = title.length <= max ? title : "#{title[0...max - 1]}…"
114
- status << "\n#{permalink}"
115
- {status: status, media_url: is_self? ? nil : media_url}
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
116
84
  end
117
85
 
118
- def url
119
- data["url"]
86
+ ##
87
+ # S3 path
88
+ def path
89
+ created_utc.strftime("year=%Y/month=%Y-%m/day=%Y-%m-%d/%s.json")
120
90
  end
121
91
 
122
92
  private
123
93
 
124
- def to_slack_image
125
- {
126
- blocks: [
127
- {
128
- type: "image",
129
- title: {
130
- type: "plain_text",
131
- text: "/r/brutalism",
132
- emoji: true,
133
- },
134
- image_url: media_url,
135
- alt_text: title,
136
- },
137
- {
138
- type: "context",
139
- elements: [
140
- {
141
- type: "mrkdwn",
142
- text: "<#{permalink}|#{title}>",
143
- },
144
- ],
145
- },
146
- ],
147
- }
148
- end
149
-
150
- def to_slack_text
151
- {
152
- blocks: [
153
- {
154
- type: "section",
155
- text: {
156
- type: "mrkdwn",
157
- text: "<#{permalink}|#{title}>",
158
- },
159
- accessory: {
160
- type: "image",
161
- image_url: "https://brutalismbot.com/logo-red-ppl.png",
162
- alt_text: "/r/brutalism",
163
- },
164
- },
165
- ],
166
- }
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
101
+ end
102
+
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
167
110
  end
168
111
  end
169
112
  end
@@ -26,8 +26,10 @@ module Brutalismbot
26
26
  children.each do |child|
27
27
  item = child.transform_keys(&:to_sym)
28
28
  post = Post.new(**item)
29
- Brutalismbot.logger.warn("NO PHOTO URL for #{post.permalink}") if post.url.nil?
30
- yield post
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
31
33
  end
32
34
  end
33
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
  },
@@ -21,7 +21,11 @@ module Brutalismbot
21
21
  end
22
22
 
23
23
  def team_id
24
- @item.dig("team_id")
24
+ @item["team_id"] || @item.dig("team", "id")
25
+ end
26
+
27
+ def team_name
28
+ @item["team_name"] || @item.dig("team", "name")
25
29
  end
26
30
 
27
31
  def to_s3(bucket:nil, prefix:nil)
@@ -31,20 +31,26 @@ 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(body:, webhook_url:, dryrun:nil)
40
+ def push(post, webhook_url, dryrun:nil)
41
+ blocks = blocks_for(post)
42
+
41
43
  Brutalismbot.logger.info("POST #{"DRYRUN " if dryrun}#{webhook_url}")
42
44
  unless dryrun
43
45
  uri = URI.parse(webhook_url)
44
46
  ssl = uri.scheme == "https"
45
47
  req = Net::HTTP::Post.new(uri, "content-type" => "application/json")
46
- req.body = body.to_json
47
- Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) {|http| http.request(req) }
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
48
54
  else
49
55
  Net::HTTPOK.new("1.1", "204", "ok")
50
56
  end
@@ -58,6 +64,34 @@ module Brutalismbot
58
64
  object.delete unless dryrun
59
65
  end
60
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
61
95
  end
62
96
  end
63
97
  end
@@ -19,16 +19,55 @@ module Brutalismbot
19
19
  end
20
20
  end
21
21
 
22
- def push(status:, media_url:nil, dryrun:nil)
23
- if media_url.nil?
22
+ def push(post, dryrun:nil)
23
+ opts = {}
24
+ slices_for(post).each_with_index.map do |slice, index|
25
+ status, media = slice
24
26
  Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
25
- @client.update(status) unless dryrun
26
- else
27
- Brutalismbot.logger.info("GET #{media_url}")
28
- Brutalismbot.logger.info("PUSH #{"DRYRUN " if dryrun}twitter://@brutalismbot")
29
- URI.open(media_url) {|media| @client.update_with_media(status, media) } unless dryrun
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
30
39
  end
31
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)
49
+ end
50
+
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)
64
+ end
65
+
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}"
70
+ end
32
71
  end
33
72
  end
34
73
  end
@@ -1,3 +1,3 @@
1
1
  module Brutalismbot
2
- VERSION = "1.8.0"
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.8.0
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-08-17 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