feed2gram 1.2.3 → 1.3.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: 24a46cff098342888e3997e1dadfc34a6e0526f979ea59b7b2e0687ecc1051e7
4
- data.tar.gz: 63f1559288645c2bb02ddfbe45aa6edd55e5f76437f582f8aec9a12a2c1630fe
3
+ metadata.gz: 73e7d2df466144a3e956cc38742d783cce241a65894beaa90e7b332ee1b99ea6
4
+ data.tar.gz: 9b58a6e07f42adcae0b86969f01d84df77480e2db7831a6b98dd48ab67159ac1
5
5
  SHA512:
6
- metadata.gz: e5eb07918977ad2d034609efbc2372ce3af65f9d75ef6d3766a3eb5db938b955fa3ebe5828091ffc56d0179a138fd433b06d314b3f58e537c71edb5e25ce6ba1
7
- data.tar.gz: ad34cbae69b4148ecd5acb953018ef8bbd6c75e61e2fca50514f47a5ac31d061e167ed801e8182d5a735da93a91181b0a8f35175710203d7fa2e0f3a321f09ee
6
+ metadata.gz: a46c7e7ca41c516c88ed3f12fee71506a5b24f280dd784d53c787cc910027bebb3b37fda1f364ad6b8dd84e9d5460ee569c4e7ff3a2eb480195b3ce6a6ddb8a4
7
+ data.tar.gz: 4a1c63c08f0ddf6693e587f2a2bd78c94e1638cd68471f54a50cd1d1df4c67ac8fbb876a402ac9b8d63e018f20ad5c415ce90b0d487bfc26b1950524c3f07054
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [1.3.0]
2
+
3
+ * Add undocumented methods for using the gem as an API. I leave figuring out its
4
+ usage as an exercise to the reader
5
+
6
+ ## [1.2.4]
7
+
8
+ * When uploads fail, output a message that includes the error code
9
+ (and the URL to look them up)
10
+
1
11
  ## [1.2.3]
2
12
 
3
13
  * Add a retry option after IG continues to fail to download videos correctly. See `RETRIES_AFTER_UPLOAD_TIMEOUT` (default 5 retries)
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # feed2gram
1
+ # feed2gram - Syndicate your site's photos, videos, and carousels to Instagram
2
+
3
+ **feed2gram is for automating Instagram posts, for posting to Threads, see
4
+ [feed2thread](https://github.com/searls/feed2thread)**
2
5
 
3
6
  I've joined the [POSSE](https://indieweb.org/POSSE) and publish as much as I can
4
7
  to [justin.searls.co](https://justin.searls.co) and syndicate it elsewhere. I'm
@@ -169,7 +172,7 @@ We publish a Docker image [using GitHub
169
172
  actions](https://github.com/searls/feed2gram/blob/main/.github/workflows/main.yml)
170
173
  tagged as `latest` for every new commit to the `main` branch, as well as with a
171
174
  release tag tracking every release of the gem on
172
- [rubygems.org](https://rubygems.org). The images are hosted [here on GitHub's
175
+ [rubygems.org](https://rubygems.org/gems/feed2gram). The images are hosted [here on GitHub's
173
176
  container
174
177
  registry](https://github.com/searls/feed2gram/pkgs/container/feed2gram)
175
178
 
data/example/Gemfile CHANGED
@@ -2,5 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gem "rackup"
4
4
  gem "sinatra"
5
+ gem "standard"
5
6
 
6
7
  gem "feed2gram", path: ".."
@@ -31,6 +31,79 @@ module Feed2Gram
31
31
  }
32
32
  end
33
33
 
34
+ # Pseudo-public "API mode"
35
+ def create_single_media_container(post, config)
36
+ media = post.medias.first
37
+ Http.post("/#{config.instagram_id}/media", {
38
+ :media_type => post.media_type,
39
+ :caption => post.caption,
40
+ :access_token => config.access_token,
41
+ :cover_url => media.cover_url,
42
+ media.video? ? :video_url : :image_url => media.url
43
+ }.compact)[:id]
44
+ end
45
+
46
+ # Pseudo-public "API mode"
47
+ def create_carousel_media_container(media, config)
48
+ res = Http.post("/#{config.instagram_id}/media", {
49
+ :media_type => media.media_type,
50
+ :is_carousel_item => true,
51
+ :access_token => config.access_token,
52
+ media.video? ? :video_url : :image_url => media.url
53
+ }.compact)
54
+ res[:id]
55
+ end
56
+
57
+ # Pseudo-public "API mode"
58
+ def create_carousel_container(post, media_containers, config)
59
+ Http.post("/#{config.instagram_id}/media", {
60
+ caption: post.caption,
61
+ media_type: post.media_type,
62
+ children: media_containers.join(","),
63
+ access_token: config.access_token
64
+ })[:id]
65
+ end
66
+
67
+ # Pseudo-public "API mode"
68
+ def upload_finished?(container_id, config)
69
+ res = Http.get("/#{container_id}", {
70
+ fields: "status_code,status",
71
+ access_token: config.access_token
72
+ })
73
+
74
+ if res[:status_code] == "FINISHED"
75
+ true
76
+ elsif res[:status_code] == "IN_PROGRESS"
77
+ false
78
+ else
79
+ raise <<~MSG
80
+ Unexpected status code (#{res[:status_code]}) uploading container: #{container_id}"
81
+
82
+ API sent back this: #{res[:status]}
83
+
84
+ Error codes can be looked up here: https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/error-codes/
85
+ MSG
86
+ end
87
+ end
88
+
89
+ # Pseudo-public "API mode"
90
+ def publish_container(container_id, config)
91
+ res = Http.post("/#{config.instagram_id}/media_publish", {
92
+ creation_id: container_id,
93
+ access_token: config.access_token
94
+ })
95
+ res[:id]
96
+ end
97
+
98
+ # Pseudo-public "API mode"
99
+ def get_media_permalink(media_id, config)
100
+ res = Http.get("/#{media_id}", {
101
+ fields: "permalink",
102
+ access_token: config.access_token
103
+ })
104
+ res[:permalink]
105
+ end
106
+
34
107
  private
35
108
 
36
109
  def retry_if_upload_times_out(times_remaining, post, options, &blk)
@@ -49,55 +122,35 @@ module Feed2Gram
49
122
  media = post.medias.first
50
123
 
51
124
  puts "Creating media resource for URL - #{media.url}" if options.verbose
52
- container_id = Http.post("/#{config.instagram_id}/media", {
53
- :media_type => post.media_type,
54
- :caption => post.caption,
55
- :access_token => config.access_token,
56
- :cover_url => media.cover_url,
57
- media.video? ? :video_url : :image_url => media.url
58
- }.compact)[:id]
125
+ container_id = create_single_media_container(post, config)
59
126
 
60
127
  if media.video?
61
128
  wait_for_media_to_upload!(media.url, container_id, config, options)
62
129
  end
63
130
 
64
131
  puts "Publishing media for URL - #{media.url}" if options.verbose
65
- Http.post("/#{config.instagram_id}/media_publish", {
66
- creation_id: container_id,
67
- access_token: config.access_token
68
- })
132
+ publish_container(container_id, config)
69
133
  Result.new(post: post, status: :posted)
70
134
  end
71
135
 
72
136
  def publish_carousel(post, config, options)
73
137
  media_containers = post.medias.take(10).map { |media|
74
138
  puts "Creating media resource for URL - #{media.url}" if options.verbose
75
- res = Http.post("/#{config.instagram_id}/media", {
76
- :media_type => media.media_type,
77
- :is_carousel_item => true,
78
- :access_token => config.access_token,
79
- media.video? ? :video_url : :image_url => media.url
80
- }.compact)
81
- res[:id]
139
+ create_carousel_media_container(media, config)
82
140
  }
83
- post.medias.select(&:video?).zip(media_containers).each { |media, container_id|
84
- wait_for_media_to_upload!(media.url, container_id, config, options)
141
+ post.medias.zip(media_containers).each { |media, container_id|
142
+ if media.video?
143
+ wait_for_media_to_upload!(media.url, container_id, config, options)
144
+ end
85
145
  }
86
146
 
87
147
  puts "Creating carousel media resource for post - #{post.url}" if options.verbose
88
- carousel_id = Http.post("/#{config.instagram_id}/media", {
89
- caption: post.caption,
90
- media_type: post.media_type,
91
- children: media_containers.join(","),
92
- access_token: config.access_token
93
- })[:id]
148
+ carousel_id = create_carousel_container(post, media_containers, config)
149
+
94
150
  wait_for_media_to_upload!(post.url, carousel_id, config, options)
95
151
 
96
152
  puts "Publishing carousel media for post - #{post.url}" if options.verbose
97
- Http.post("/#{config.instagram_id}/media_publish", {
98
- creation_id: carousel_id,
99
- access_token: config.access_token
100
- })
153
+ publish_container(carousel_id, config)
101
154
  Result.new(post: post, status: :posted)
102
155
  end
103
156
 
@@ -110,18 +163,16 @@ module Feed2Gram
110
163
  raise FacebookSucksAtDownloadingFilesError
111
164
  end
112
165
 
113
- res = Http.get("/#{container_id}", {
114
- fields: "status_code",
115
- access_token: config.access_token
116
- })
117
- puts "Upload status #{res[:status_code]} after waiting #{wait_attempts * SECONDS_PER_UPLOAD_CHECK} seconds for IG to download #{url}" if options.verbose
118
- if res[:status_code] == "FINISHED"
119
- break
120
- elsif res[:status_code] == "IN_PROGRESS"
121
- wait_attempts += 1
122
- sleep SECONDS_PER_UPLOAD_CHECK
123
- else
124
- warn "Unexpected status code (#{res[:status_code]}) uploading: #{url}"
166
+ begin
167
+ puts "Uploading after waiting #{wait_attempts * SECONDS_PER_UPLOAD_CHECK} seconds for IG to download #{url}" if options.verbose
168
+ if upload_finished?(container_id, config)
169
+ break
170
+ else
171
+ wait_attempts += 1
172
+ sleep SECONDS_PER_UPLOAD_CHECK
173
+ end
174
+ rescue => e
175
+ warn e.message
125
176
  break
126
177
  end
127
178
  end
@@ -1,10 +1,22 @@
1
1
  module Feed2Gram
2
2
  class RefreshesToken
3
3
  def refresh!(config, options)
4
+ puts "Refreshing Facebook OAuth token" if options.verbose
5
+ new_access_token = call(config)
6
+ return unless new_access_token
7
+
8
+ config.access_token = new_access_token
9
+ config.access_token_refreshed_at = Time.now.utc
10
+
11
+ puts "Updating Facebook OAuth token in: #{options.config_path}" if options.verbose
12
+ File.write(options.config_path, config.as_yaml)
13
+ end
14
+
15
+ # "API mode"
16
+ def call(config)
4
17
  return unless config.access_token_refreshed_at.nil? ||
5
18
  config.access_token_refreshed_at < Time.now - (60 * 60)
6
19
 
7
- puts "Refreshing Facebook OAuth token" if options.verbose
8
20
  data = Http.get("/oauth/access_token", {
9
21
  grant_type: "fb_exchange_token",
10
22
  client_id: config.facebook_app_id,
@@ -12,11 +24,7 @@ module Feed2Gram
12
24
  fb_exchange_token: config.access_token
13
25
  })
14
26
 
15
- config.access_token = data[:access_token]
16
- config.access_token_refreshed_at = Time.now.utc
17
-
18
- puts "Updating Facebook OAuth token in: #{options.config_path}" if options.verbose
19
- File.write(options.config_path, config.as_yaml)
27
+ data[:access_token]
20
28
  end
21
29
  end
22
30
  end
@@ -1,3 +1,3 @@
1
1
  module Feed2Gram
2
- VERSION = "1.2.3"
2
+ VERSION = "1.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feed2gram
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-07 00:00:00.000000000 Z
11
+ date: 2024-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  - !ruby/object:Gem::Version
78
78
  version: '0'
79
79
  requirements: []
80
- rubygems_version: 3.5.6
80
+ rubygems_version: 3.5.11
81
81
  signing_key:
82
82
  specification_version: 4
83
83
  summary: Reads an Atom feed and posts its entries to Instagram