feed2gram 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Dockerfile +14 -0
- data/README.md +40 -2
- data/lib/feed2gram/loads_cache.rb +5 -3
- data/lib/feed2gram/loads_config.rb +3 -2
- data/lib/feed2gram/parses_options.rb +5 -1
- data/lib/feed2gram/publishes_posts.rb +7 -2
- data/lib/feed2gram/refreshes_token.rb +4 -2
- data/lib/feed2gram/updates_cache.rb +3 -2
- data/lib/feed2gram/version.rb +1 -1
- data/lib/feed2gram.rb +11 -7
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c86578cd42608fcc00119f206582ff6c2812f8ba50a92a47a8e11360a635210e
|
4
|
+
data.tar.gz: 4a322434134e5bd515b4985feb53cd03054a36d6df2f9399ec2a08e3518a3816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 025aa21dc8973cf0cc69d9d84cf2b019cf88bdf0cf5e02db48dc7fd69a3d3a9ed1a04f3290f0cb8c4fce8761cb2a4af5c76ed363221f4b4b6b442bbb4b432fbb
|
7
|
+
data.tar.gz: 1a257665bfa5ccf815c3c97039aa16b73d4f9dae3471792828ab0e4345733dd380fe2262f8034122e35efac6ee20140d9a7cbb99765706f52d89ae2f0d2559b0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.3]
|
4
|
+
|
5
|
+
* Fix the `--populate-cache` so that all entries are marked `skipped`
|
6
|
+
* Include a `bin/daemon` script
|
7
|
+
* Ship a Dockerfile to build an image that can run feed2gram on a schedule
|
8
|
+
|
3
9
|
## [0.0.2] - 2023-10-29
|
4
10
|
|
5
11
|
- Initial release
|
data/Dockerfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
FROM ruby:3.2.2
|
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 bundle install
|
11
|
+
ADD . .
|
12
|
+
VOLUME /config
|
13
|
+
CMD ["--config", "/config/feed2gram.yml"]
|
14
|
+
ENTRYPOINT ["/srv/bin/daemon"]
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
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
|
@@ -82,12 +82,23 @@ directory. This file is used internally by feed2gram to keep track of which
|
|
82
82
|
entry URLs in the atom feed have been processed and can be ignored on the next
|
83
83
|
run.
|
84
84
|
|
85
|
+
## Docker
|
86
|
+
|
87
|
+
You can also use Docker to run this on your own automation platform like Proxmox or Kubernetes.
|
88
|
+
|
89
|
+
```
|
90
|
+
docker run --rm -it \
|
91
|
+
-v ./feed2gram.yml:/srv/feed2gram.yml \
|
92
|
+
-v ./feed2gram.cache.yml:/srv/feed2gram.cache.yml \
|
93
|
+
ghcr.io/searls/feed2gram
|
94
|
+
```
|
95
|
+
|
85
96
|
## Options
|
86
97
|
|
87
98
|
For available options, run `feed2gram --help`:
|
88
99
|
|
89
100
|
```
|
90
|
-
$
|
101
|
+
$ feed2gram --help
|
91
102
|
Usage: feed2gram [options]
|
92
103
|
--config PATH Path of feed2gram YAML configuration (default: feed2gram.yml)
|
93
104
|
--cache-path PATH Path of feed2gram's cache file to track processed entries (default: feed2gram.cache.yml)
|
@@ -147,6 +158,32 @@ See more at http://localhost:1313/
|
|
147
158
|
</entry>
|
148
159
|
```
|
149
160
|
|
161
|
+
## Running continuously with Docker
|
162
|
+
|
163
|
+
We publish a Docker image [using GitHub
|
164
|
+
actions](https://github.com/searls/feed2gram/blob/main/.github/workflows/main.yml)
|
165
|
+
tagged as `latest` for every new commit to the `main` branch, as well as with a
|
166
|
+
release tag tracking every release of the gem on
|
167
|
+
[rubygems.org](https://rubygems.org). The images are hosted [here on GitHub's
|
168
|
+
container
|
169
|
+
registry](https://github.com/searls/feed2gram/pkgs/container/feed2gram)
|
170
|
+
|
171
|
+
```
|
172
|
+
$ docker pull ghcr.io/searls/feed2gram:latest
|
173
|
+
```
|
174
|
+
|
175
|
+
To configure your container, there are just three things to know:
|
176
|
+
|
177
|
+
1. A volume containing your configuration and cache files must be mounted to `/config`
|
178
|
+
2. By default, feed2gram will run with `--config /config/feed2gram.yml`, but you can
|
179
|
+
customize this by configuring the command value as needed
|
180
|
+
3. By default, feed2gram is run as a daemon every 60 seconds, and that duration can be overridden
|
181
|
+
by setting a `SLEEP_TIME` environment variable to the number of seconds you'd like
|
182
|
+
to wait between runs
|
183
|
+
4. If you'd rather run `feed2gram` as ad hoc as opposed to via the included daemon
|
184
|
+
(presumably to handle scheduling it yourself), simply change the entrypoint to
|
185
|
+
`/srv/exe/feed2gram`
|
186
|
+
|
150
187
|
## Frequently Asked Questions
|
151
188
|
|
152
189
|
### Why didn't my post show up?
|
@@ -166,3 +203,4 @@ The submitted image with aspect ratio ('719/194',) cannot be published. Please s
|
|
166
203
|
It means your photo is too avant garde for a mainstream normie platform like
|
167
204
|
Instagram. Make sure all images' aspect ratiosa re between 4:5 and 1.91:1 or
|
168
205
|
else the post will fail.
|
206
|
+
|
@@ -6,11 +6,13 @@ module Feed2Gram
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class LoadsCache
|
9
|
-
def load(
|
10
|
-
if File.exist?(cache_path)
|
11
|
-
|
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(
|
21
|
-
|
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,
|
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(
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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
|
data/lib/feed2gram/version.rb
CHANGED
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
|
22
|
-
RefreshesToken.new.refresh!(config, options
|
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
|
25
|
-
|
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
|
-
|
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
|
33
|
+
PublishesPosts.new.publish(posts, config, options)
|
30
34
|
end
|
31
|
-
UpdatesCache.new.update!(cache, results, options
|
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.
|
4
|
+
version: 0.0.3
|
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-
|
11
|
+
date: 2023-11-06 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.
|
76
|
+
rubygems_version: 3.4.17
|
76
77
|
signing_key:
|
77
78
|
specification_version: 4
|
78
79
|
summary: Reads an Atom feed and posts its entries to Instagram
|