feed2gram 0.0.2 → 0.0.4

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: 0f8b1df08d5636342c7517691d0f4bce4cc4d89f261368f730d62d9392a6d8a7
4
- data.tar.gz: e617c168c1c999be12ce2d263ca823b90777639f7d51003ac809e7660e9ef0c9
3
+ metadata.gz: c053ce1eafec6ed200dc7ed549fb25e7bc22fcd78fa6b92c773673d47aeba056
4
+ data.tar.gz: 767a90bd8534d43f0e9bef8ef73e8e577bd959ade67ebabe639ac96d6bde4828
5
5
  SHA512:
6
- metadata.gz: 9af47b0dadb5db2cbaa1c0208fd1873fd40e310646433c416635dbad2657ed05e591d212bc13819c6038a144c7d3523b9b61e932d91bb8bc012237cac6df0938
7
- data.tar.gz: f96cfbc1ba2e994b56943179906130211bd9f992828059d59b2da197f7607ad7402ae5699bb67270a944775feccd3cae3a5f184b3d6ec8b87493271fcaaeb428
6
+ metadata.gz: 57c2490cf9aee77fa40d5dffceaeddb3ac71e2d23d8b44d962b87e10caa295079c9e8772d7986b0db37f2a5a826a838c5ec7cdd7355d2c0bb6704acc0b58493d
7
+ data.tar.gz: e6e39cc6033dab9c32e958c413790b2d0c8eacbe1e6d7175108df34ed2894ad2ecbb305e98ab88891fd3e8600243849dbb22dda4b73fbe3c4f0ed76096125055
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.4]
4
+
5
+ * Optimize the size of the Docker image (about 85% reduction)
6
+
7
+ ## [0.0.3]
8
+
9
+ * Fix the `--populate-cache` so that all entries are marked `skipped`
10
+ * Include a `bin/daemon` script
11
+ * Ship a Dockerfile to build an image that can run feed2gram on a schedule
12
+
3
13
  ## [0.0.2] - 2023-10-29
4
14
 
5
15
  - Initial release
data/Dockerfile ADDED
@@ -0,0 +1,17 @@
1
+ FROM ruby:3.2.2-alpine
2
+
3
+ LABEL org.opencontainers.image.source=https://github.com/searls/feed2gram
4
+ LABEL org.opencontainers.image.description="Reads an Atom feed and posts its entries to Instagram (basically feed2toot, but for Instagram)"
5
+ LABEL org.opencontainers.image.licenses=GPLv3
6
+
7
+ WORKDIR /srv
8
+ COPY Gemfile Gemfile.lock feed2gram.gemspec ./
9
+ COPY lib/feed2gram/version.rb lib/feed2gram/
10
+ RUN apk update && \
11
+ apk add autoconf bash git gcc make musl-dev && \
12
+ bundle install && \
13
+ apk del --purge --rdepends git gcc autoconf make musl-dev
14
+ ADD . .
15
+ VOLUME /config
16
+ CMD ["--config", "/config/feed2gram.yml"]
17
+ ENTRYPOINT ["/srv/bin/daemon"]
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Feed2Gram
1
+ # feed2gram
2
2
 
3
3
  I've joined the [POSSE](https://indieweb.org/POSSE) and publish as much as I can
4
4
  to [justin.searls.co](https://justin.searls.co) and syndicate it elsewhere. I'm
@@ -87,7 +87,7 @@ run.
87
87
  For available options, run `feed2gram --help`:
88
88
 
89
89
  ```
90
- $ ./script/run --help
90
+ $ feed2gram --help
91
91
  Usage: feed2gram [options]
92
92
  --config PATH Path of feed2gram YAML configuration (default: feed2gram.yml)
93
93
  --cache-path PATH Path of feed2gram's cache file to track processed entries (default: feed2gram.cache.yml)
@@ -147,6 +147,58 @@ See more at http://localhost:1313/
147
147
  </entry>
148
148
  ```
149
149
 
150
+ ## Running continuously with Docker
151
+
152
+ We publish a Docker image [using GitHub
153
+ actions](https://github.com/searls/feed2gram/blob/main/.github/workflows/main.yml)
154
+ tagged as `latest` for every new commit to the `main` branch, as well as with a
155
+ release tag tracking every release of the gem on
156
+ [rubygems.org](https://rubygems.org). The images are hosted [here on GitHub's
157
+ container
158
+ registry](https://github.com/searls/feed2gram/pkgs/container/feed2gram)
159
+
160
+
161
+ You can also use Docker to run this on your own automation platform like Proxmox or Kubernetes.
162
+
163
+ ```
164
+ $ docker run --rm -it \
165
+ -v ./your_config_dir:/srv/config
166
+ ghcr.io/searls/feed2gram
167
+ ```
168
+
169
+ To configure the container, there are just four things to know:
170
+
171
+ 1. A volume containing your configuration and cache files must be mounted to `/config`
172
+ 2. By default, feed2gram will run with `--config /config/feed2gram.yml`, but you can
173
+ customize this by configuring the command value as needed
174
+ 3. By default, feed2gram is run as a daemon every 60 seconds, and that duration can be overridden
175
+ by setting a `SLEEP_TIME` environment variable to the number of seconds you'd like
176
+ to wait between runs
177
+ 4. If you'd rather run `feed2gram` as ad hoc as opposed to via the included daemon
178
+ (presumably to handle scheduling it yourself), simply change the entrypoint to
179
+ `/srv/exe/feed2gram`
180
+
181
+ ### Running the docker image specifically on your Synology NAS
182
+
183
+ I run this on my [Synology DS 920+](https://www.pcmag.com/reviews/synology-diskstation-ds920-plus), using the [DSM's Container Manager](https://www.synology.com/en-global/dsm/feature/container-manager) app.
184
+
185
+ There are just a few things to know to set this up:
186
+
187
+ At the time of this writing, the `Action > Import > Add from URL` feature of the Container Manager's
188
+ "Image" tab does not support GitHub Container Registry URLs. However, if you connect [via SSH](https://kb.synology.com/en-my/DSM/tutorial/How_to_login_to_DSM_with_root_permission_via_SSH_Telnet):
189
+
190
+ ```
191
+ $ sudo -s
192
+ # Enter your user password.
193
+ $ docker pull ghcr.io/searls/feed2gram:latest
194
+ ```
195
+
196
+ Once downloaded, the image will appear in the app. From there, select
197
+ `ghcr.io/searls/feed2gram`, hit Run, and complete the wizard, setting any custom
198
+ command line flags (once the container is created, this cannot be edited), as
199
+ well as choosing a location to mount the `/config` volume and setting a
200
+ `SLEEP_TIME` environment variable (these can be changed after the fact).
201
+
150
202
  ## Frequently Asked Questions
151
203
 
152
204
  ### Why didn't my post show up?
@@ -166,3 +218,4 @@ The submitted image with aspect ratio ('719/194',) cannot be published. Please s
166
218
  It means your photo is too avant garde for a mainstream normie platform like
167
219
  Instagram. Make sure all images' aspect ratiosa re between 4:5 and 1.91:1 or
168
220
  else the post will fail.
221
+
@@ -6,11 +6,13 @@ module Feed2Gram
6
6
  end
7
7
 
8
8
  class LoadsCache
9
- def load(cache_path)
10
- if File.exist?(cache_path)
11
- yaml = YAML.load_file(cache_path, permitted_classes: [Time])
9
+ def load(options)
10
+ if File.exist?(options.cache_path)
11
+ puts "Loading cache from: #{options.cache_path}" if options.verbose
12
+ yaml = YAML.load_file(options.cache_path, permitted_classes: [Time])
12
13
  Cache.new(**yaml)
13
14
  else
15
+ puts "No cache found (looked at '#{options.cache_path}'), initializing a new one" if options.verbose
14
16
  Cache.new(posted: [], failed: [], skipped: [])
15
17
  end
16
18
  end
@@ -17,8 +17,9 @@ module Feed2Gram
17
17
  end
18
18
 
19
19
  class LoadsConfig
20
- def load(config_path)
21
- yaml = YAML.load_file(config_path, permitted_classes: [Time])
20
+ def load(options)
21
+ puts "Loading config from: #{options.config_path}" if options.verbose
22
+ yaml = YAML.load_file(options.config_path, permitted_classes: [Time])
22
23
  Config.new(**yaml)
23
24
  end
24
25
  end
@@ -1,7 +1,7 @@
1
1
  require "optparse"
2
2
 
3
3
  module Feed2Gram
4
- Options = Struct.new(:config_path, :cache_path, :limit, :skip_token_refresh, :populate_cache, keyword_init: true) do
4
+ Options = Struct.new(:config_path, :cache_path, :limit, :skip_token_refresh, :populate_cache, :verbose, keyword_init: true) do
5
5
  undef_method :cache_path
6
6
  def cache_path
7
7
  @cache_path || config_path.sub(/\.yml$/, ".cache.yml")
@@ -36,6 +36,10 @@ module Feed2Gram
36
36
  opts.on "--populate-cache", "Populate the cache file with any posts found in the feed WITHOUT posting them to Instagram" do
37
37
  options.populate_cache = true
38
38
  end
39
+
40
+ opts.on "-v", "--verbose", "Enable verbose output" do
41
+ options.verbose = true
42
+ end
39
43
  end.parse!(argv)
40
44
 
41
45
  options
@@ -2,13 +2,18 @@ module Feed2Gram
2
2
  Result = Struct.new(:post, :status, keyword_init: true)
3
3
 
4
4
  class PublishesPosts
5
- def publish(posts, config, limit)
5
+ def publish(posts, config, options)
6
+ post_limit = options.limit || posts.size
7
+ puts "Publishing #{post_limit} posts to Instagram" if options.verbose
8
+
6
9
  # reverse to post oldest first (most Atom feeds are reverse-chronological)
7
- posts.reverse.take(limit || posts.size).map { |post|
10
+ posts.reverse.take(post_limit).map { |post|
8
11
  begin
9
12
  if post.images.size == 1
13
+ puts "Publishing single image post for: #{post.url}" if options.verbose
10
14
  publish_single_image(post, config)
11
15
  else
16
+ puts "Publishing carousel post for: #{post.url}" if options.verbose
12
17
  publish_carousel(post, config)
13
18
  end
14
19
  rescue => e
@@ -1,9 +1,10 @@
1
1
  module Feed2Gram
2
2
  class RefreshesToken
3
- def refresh!(config, config_path)
3
+ def refresh!(config, options)
4
4
  return unless config.access_token_refreshed_at.nil? ||
5
5
  config.access_token_refreshed_at < Time.now - (60 * 60)
6
6
 
7
+ puts "Refreshing Facebook OAuth token" if options.verbose
7
8
  data = Http.get("/oauth/access_token", {
8
9
  grant_type: "fb_exchange_token",
9
10
  client_id: config.facebook_app_id,
@@ -14,7 +15,8 @@ module Feed2Gram
14
15
  config.access_token = data[:access_token]
15
16
  config.access_token_refreshed_at = Time.now.utc
16
17
 
17
- File.write(config_path, config.as_yaml)
18
+ puts "Updating Facebook OAuth token in: #{options.config_path}" if options.verbose
19
+ File.write(options.config_path, config.as_yaml)
18
20
  end
19
21
  end
20
22
  end
@@ -1,6 +1,6 @@
1
1
  module Feed2Gram
2
2
  class UpdatesCache
3
- def update!(cache, results, cache_path)
3
+ def update!(cache, results, options)
4
4
  cache.updated_at = Time.now
5
5
  results.group_by { |result| result.status }
6
6
  .transform_values { |results| results.map { |result| result.post.url } }
@@ -8,7 +8,8 @@ module Feed2Gram
8
8
  cache[status] += urls
9
9
  end
10
10
 
11
- File.write(cache_path, cache.as_yaml)
11
+ puts "Writing updated cache to: #{options.cache_path}" if options.verbose
12
+ File.write(options.cache_path, cache.as_yaml)
12
13
  end
13
14
  end
14
15
  end
@@ -1,3 +1,3 @@
1
1
  module Feed2Gram
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/feed2gram.rb CHANGED
@@ -18,16 +18,20 @@ module Feed2Gram
18
18
  end
19
19
 
20
20
  def self.run(options)
21
- config = LoadsConfig.new.load(options.config_path)
22
- RefreshesToken.new.refresh!(config, options.config_path) unless options.skip_token_refresh
21
+ config = LoadsConfig.new.load(options)
22
+ RefreshesToken.new.refresh!(config, options) unless options.skip_token_refresh
23
23
 
24
- cache = LoadsCache.new.load(options.cache_path)
25
- posts = FiltersPosts.new.filter(ParsesEntries.new.parse(config.feed_url), cache)
24
+ cache = LoadsCache.new.load(options)
25
+ puts "Loading entries from feed: #{config.feed_url}" if options.verbose
26
+ entries = ParsesEntries.new.parse(config.feed_url)
27
+ puts "Found #{entries.size} entries in feed" if options.verbose
28
+ posts = FiltersPosts.new.filter(entries, cache)
26
29
  results = if options.populate_cache
27
- posts.map { |post| Result.new(post: post, status: [:skipped, :failed, :posted].sample) }
30
+ puts "Populating cache, marking #{posts.size} posts as skipped" if options.verbose
31
+ posts.map { |post| Result.new(post: post, status: :skipped) }
28
32
  else
29
- PublishesPosts.new.publish(posts, config, options.limit)
33
+ PublishesPosts.new.publish(posts, config, options)
30
34
  end
31
- UpdatesCache.new.update!(cache, results, options.cache_path)
35
+ UpdatesCache.new.update!(cache, results, options)
32
36
  end
33
37
  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: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-30 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -34,6 +34,7 @@ extra_rdoc_files: []
34
34
  files:
35
35
  - ".standard.yml"
36
36
  - CHANGELOG.md
37
+ - Dockerfile
37
38
  - LICENSE.txt
38
39
  - README.md
39
40
  - Rakefile
@@ -72,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
73
  - !ruby/object:Gem::Version
73
74
  version: '0'
74
75
  requirements: []
75
- rubygems_version: 3.4.6
76
+ rubygems_version: 3.4.21
76
77
  signing_key:
77
78
  specification_version: 4
78
79
  summary: Reads an Atom feed and posts its entries to Instagram