mastodon-social 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 73810aa00a8d27299a5c9446c3cc0a239d5b0076a104688255bac6d70ee203a4
4
+ data.tar.gz: dde89d4d59683de3b0c971dce6139cab466135dbb2c77f4d6265d0422b5468e8
5
+ SHA512:
6
+ metadata.gz: 656f21b8a566eac6f97802ad179521a05710ff071abcfd2066b778821f170362b2ca342ab7cbda02e65847d47dc046aac23cf35eea1fb323ae5209d32d878b7e
7
+ data.tar.gz: 43587680e329dc2b0fd6ed335fd70d8b39a65091109211af7fb0c5c29a13cfa7f050fcaaa6be46ad9957335e06672e50150348397eb4ead81939b42425967e2d
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in mastodon-social.gemspec
6
+ gemspec
7
+
8
+ # We require the latest mastodon-api, since it updated its http dependency to 4.0, but it hasn't been published
9
+ gem "mastodon-api", :git => "git://github.com/mastodon/mastodon-api"
10
+
11
+ gem "rake", "~> 13.0"
12
+
13
+ gem "rubocop", "~> 1.21"
14
+ gem "debug"
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mastodon-social (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ debug (1.7.1)
11
+ irb (>= 1.5.0)
12
+ reline (>= 0.3.1)
13
+ io-console (0.6.0)
14
+ irb (1.6.2)
15
+ reline (>= 0.3.0)
16
+ json (2.6.3)
17
+ parallel (1.22.1)
18
+ parser (3.2.0.0)
19
+ ast (~> 2.4.1)
20
+ rainbow (3.1.1)
21
+ rake (13.0.6)
22
+ regexp_parser (2.6.1)
23
+ reline (0.3.2)
24
+ io-console (~> 0.5)
25
+ rexml (3.2.5)
26
+ rubocop (1.43.0)
27
+ json (~> 2.3)
28
+ parallel (~> 1.10)
29
+ parser (>= 3.2.0.0)
30
+ rainbow (>= 2.2.2, < 4.0)
31
+ regexp_parser (>= 1.8, < 3.0)
32
+ rexml (>= 3.2.5, < 4.0)
33
+ rubocop-ast (>= 1.24.1, < 2.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 2.4.0, < 3.0)
36
+ rubocop-ast (1.24.1)
37
+ parser (>= 3.1.1.0)
38
+ ruby-progressbar (1.11.0)
39
+ unicode-display_width (2.4.2)
40
+
41
+ PLATFORMS
42
+ x86_64-darwin-21
43
+
44
+ DEPENDENCIES
45
+ debug
46
+ mastodon-social!
47
+ rake (~> 13.0)
48
+ rubocop (~> 1.21)
49
+
50
+ BUNDLED WITH
51
+ 2.3.15
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Mastodon::Social
2
+
3
+ A Jekyll plugin to create links to Mastodon for your posts.
4
+
5
+ ## Installation
6
+
7
+ In your `Gemfile` add:
8
+ ```ruby
9
+ group :jekyll_plugins do
10
+ # (other jekyll plugins)
11
+ gem 'mastodon-api'
12
+ end
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ The plugin will add a new `mastodon` command to your jekyll setup:
18
+ ```sh
19
+ $ be jekyll help
20
+ jekyll 3.9.2 -- Jekyll is a blog-aware, static site generator in Ruby
21
+
22
+ Usage:
23
+
24
+ jekyll <subcommand> [options]
25
+
26
+ ...
27
+
28
+ Subcommands:
29
+ ...
30
+ mastodon Connect your blog with the Fediverse
31
+ $
32
+ ```
33
+
34
+ Running `jekyll mastodon help` shows the subcommands:
35
+ ```sh
36
+ jekyll mastodon [command]
37
+
38
+ Commands:
39
+ setup: Initial setup authorizing this plugin to post to your account
40
+ authorize: Enter your password to authorize this plugin to post to your Mastodon account
41
+ mark: Mark all posts as published (to Mastodon)
42
+ post: Publish any new posts to Mastodon
43
+ clean: Erase all cached mastodon data
44
+ ```
45
+
46
+ To use the plugin, you need to add a Mastodon tag somewhere in your page layout. Then use the `setup` and `authorize`
47
+ commands to link the plugin to your Mastodon account. Those are all one-time actions.
48
+
49
+ Whenever you rebuild your blog, you will then need to run the `post` command to send a new status post to Mastodon
50
+ with a link to your new entry.
51
+
52
+ ### Mastodon Link
53
+
54
+ The plugin creates a new tag called `{% mastodon_social %}` You can add that tag to any page and it will create
55
+ a link to the Mastodon status post for this page (assuming it exists). To put a text anchor for the link, add text in
56
+ the block, like so:
57
+ ```html
58
+ {% mastodon_social %}
59
+ Boost this on Mastodon!
60
+ {% end_mastodon_social %}
61
+ ```
62
+
63
+ ### Setup and Authorization
64
+
65
+ Next, you need to tell the plugin which Mastodon instance you use as your home instance. It will connect there
66
+ to make the new status posts. In your `_config.yml` file, add this section:
67
+
68
+ ```yaml
69
+ mastodon-syndication:
70
+ server: https://your.mastodon-server.org
71
+ ```
72
+
73
+ Once you have the gem installed and your config set up, run:
74
+ ```
75
+ jekyll mastodon setup
76
+ ```
77
+ This establishes the plugin as an application on your Mastodon instance.
78
+
79
+ Next, run:
80
+ ```
81
+ jekyll mastodon authorize
82
+ ```
83
+ This will prompt you for your Mastodon password, to authorize the plugin to post on your behalf. The plugin does
84
+ not save your password anywhere; however, it does save the token it uses to login. It's saved in
85
+ `.jekyll-cache/mastodon.yml`. You should protect this; in theory anyone who has that token can use it to post
86
+ mastodon toots as you. You can see the token by loggin in to to Mastodon, then going to
87
+ Preferences > Account > Authorized Apps. You can revoke the token if it is compromised, but then you will need
88
+ to redo the setup and authorization steps again. As long as you don't revoke the token, it's permanent and
89
+ you don't need to run the setup or authorization again.
90
+
91
+ ### Posting to Mastodon
92
+
93
+ There are three commands used to sync your blog with mastodon: mark, clear, and post. For the most part, you
94
+ will only use post.
95
+
96
+ ```
97
+ jekyll mastodon post
98
+ ```
99
+ will sync your mastodon account and your blog. The plugin keeps track of every post that's been sent to Mastodon.
100
+ When you issue the `post` command, it checks for new posts on your blog and makes a toot for each one. That means
101
+ you can run `post` again and if you haven't made any new posts since last time, it won't do anything.
102
+
103
+ ```
104
+ jekyll mastodon clear
105
+ ```
106
+ This will cause the plugin to forget all the Mastodon posts its made. The next time you run `post` it will send
107
+ everything. Unless your blog is new you probably don't want to do this.
108
+
109
+ ```
110
+ jekyll mastodon mark
111
+ ```
112
+ This will mark every post on your blog as having been sent. If you have a lot of posts on your blog you will want
113
+ to do this before your very first sync; otherwise, the plugin will sent a toot for every one of your old posts (and
114
+ probably hit a rate limit).
115
+
116
+ ### Chicken and Egg Problem
117
+
118
+ There's one issue with posting to Mastodon. When you have a link in your toot like the ones the plugin creates,
119
+ Mastodon will visit the link to create a Link Card. This is a nicely-formatted card with things like a title,
120
+ description, and even an image if you have the Open Graph metadata in your post. However, Mastodon can't generate
121
+ the card if your post doesn't exist yet (obviously).
122
+
123
+ This leads to a chicken-and-egg problem. The jekyll plugin that renders links to your mastodon post won't render
124
+ properly if the post isn't there on mastodon, but the Mastodon post won't have its Link Card if the post isn't
125
+ there yet. Each one needs the other to exist.
126
+
127
+ If you aren't using the `mastodon_social` tag in your layout, this isn't a problem. You can post a link
128
+ every time you build your blog by adding one step into your normal posting routine:
129
+
130
+ 1. Create a post and build your blog with `jekyll build`
131
+ 2. Make your new post visible online.
132
+ 3. Post it to Mastodon with `jekyll mastodon post`
133
+
134
+ However, if you're using the `mastodon_social` tag to create links to your mastodon toot (so people can click it
135
+ and boost your post) then you'll need to build your blog *twice*:
136
+
137
+ 1. Create a post and build your blog with `jekyll build`
138
+ 2. Make your new post visible online (so the link card will render properly)
139
+ 3. Post it to Mastodon with `jekyll mastodon post`
140
+ 4. Build your blog again with `jekyll build`. This build will have the proper link.
141
+ 5. Make the new build visible.
142
+
143
+ ## Development
144
+
145
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
146
+
147
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
148
+
149
+ ## Contributing
150
+
151
+ Bug reports and pull requests are welcome on GitHub at https://github.com/reiterate-app/mastodon-social.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,132 @@
1
+ require 'mastodon'
2
+ require 'io/console'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'debug'
7
+
8
+ module Jekyll
9
+ module MastodonSocial
10
+ class MastodonSetup < Jekyll::Command
11
+ class << self
12
+ def init_with_program(prog)
13
+ prog.command(:mastodon) do |c|
14
+ c.syntax('mastodon')
15
+ c.description("Connect your blog with the Fediverse")
16
+ c.action do |args, opts|
17
+ if args.empty?
18
+ do_help
19
+ else
20
+ case args[0]
21
+ when /setup/
22
+ create_client_id opts
23
+ when /mark/
24
+ mark opts
25
+ when /post/
26
+ post opts
27
+ when /clean/
28
+ clean
29
+ when /help/
30
+ do_help
31
+ when /auth/
32
+ get_token
33
+ else
34
+ puts "Error, try 'mastodon help'"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def create_client_id(options = {})
42
+ client = get_client()
43
+ app = client.create_app("jekyll-social", MastodonSocial.site.config["url"], 'read write')
44
+ MastodonSocial.client_id = app.client_id
45
+ MastodonSocial.client_secret = app.client_secret
46
+ MastodonSocial.save_config()
47
+ end
48
+
49
+ def get_token
50
+ puts "Enter your Mastodon credentials for the account you want to post to"
51
+ print "Account email: "
52
+ email = STDIN.cooked(&:gets).chomp
53
+ print "Account password: "
54
+ password = STDIN.noecho(&:gets).chomp
55
+
56
+ client = get_client()
57
+ auth_uri = URI.parse("#{MastodonSocial.config['server']}/oauth/token")
58
+ Net::HTTP.start(auth_uri.host, auth_uri.port, :use_ssl => auth_uri.scheme == 'https') do |http|
59
+ request = Net::HTTP::Post.new(auth_uri, 'Content-Type' => 'application/json')
60
+ request.body = {
61
+ 'client_id': MastodonSocial.client_id,
62
+ 'client_secret': MastodonSocial.client_secret,
63
+ 'grant_type': 'password',
64
+ 'username': email,
65
+ 'password': password,
66
+ 'scope': 'read write'
67
+ }.to_json
68
+ response = http.request request
69
+ json_response = JSON.parse(response.body)
70
+ MastodonSocial.bearer_token = json_response['access_token']
71
+ end
72
+ MastodonSocial.save_config()
73
+ end
74
+
75
+ def mark(options = {})
76
+ client = get_client()
77
+ # Scan through all posts and see which ones need to be posted to Mastodon
78
+ for post_url in MastodonSocial.mastodon_status.keys
79
+ MastodonSocial.mark_as_published(post_url, true)
80
+ end
81
+ MastodonSocial.save_config()
82
+ end
83
+
84
+ def clean
85
+ client = get_client()
86
+ MastodonSocial.clear_status()
87
+ MastodonSocial.save_config()
88
+ end
89
+
90
+ def get_client(options = {})
91
+ options = configuration_from_options(options)
92
+ site = Jekyll::Site.new(options)
93
+ MastodonSocial.setup(site)
94
+ return Mastodon::REST::Client.new(base_url: MastodonSocial.config["server"], bearer_token: MastodonSocial.bearer_token)
95
+ end
96
+
97
+ # Look for any blog posts that haven't been sent to Mastodon, and post a status for each
98
+ def post(options = {})
99
+ client = get_client()
100
+ for post_url, status in MastodonSocial.mastodon_status
101
+ next if status[:mastodon_status]
102
+ puts "Publishing #{post_url} to Mastodon"
103
+ msg_text = <<~ENDMSG
104
+ #{status[:excerpt].gsub("\n", '')}
105
+
106
+ #{MastodonSocial.site.config["url"]}#{post_url}
107
+ ENDMSG
108
+ msg_text += ("\n#" + status[:hashtags].join(' #')) if status[:hashtags]
109
+ status_result = client.create_status(msg_text)
110
+ new_status = {id: status_result.id, url: status_result.url}
111
+ MastodonSocial.mark_as_published(post_url, new_status)
112
+ end
113
+ MastodonSocial.save_config()
114
+ end
115
+
116
+ def do_help
117
+ puts <<-END_HELP
118
+ jekyll mastodon [command]
119
+
120
+ Commands:
121
+ setup: Initial setup authorizing this plugin to post to your account
122
+ authorize: Enter your password to authorize this plugin to post to your Mastodon account
123
+ mark: Mark all posts as published (to Mastodon)
124
+ post: Publish any new posts to Mastodon
125
+ clean: Erase all cached mastodon data
126
+ END_HELP
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
@@ -0,0 +1,16 @@
1
+ require "debug"
2
+
3
+ module Jekyll
4
+ module MastodonSocial
5
+ class GatherPosse < Generator
6
+ def generate(site)
7
+ MastodonSocial.setup(site)
8
+ for post in site.posts.docs
9
+ # If we've never processed this post, add it to the db as unpublished
10
+ MastodonSocial.mark_as_published(post, nil) unless MastodonSocial.mastodon_status[post.url]
11
+ end
12
+ MastodonSocial.save_config()
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ require 'mastodon-social'
2
+
3
+ module Jekyll
4
+ class MastodonLinkTagBlock < Liquid::Block
5
+
6
+ def render(context)
7
+ text = super
8
+ url = MastodonSocial.syndication_info(context["page"]["url"])
9
+ "<a href='#{url}'>#{text}</a>"
10
+ end
11
+ end
12
+ end
13
+
14
+ Liquid::Template.register_tag('mastodon_social', Jekyll::MastodonLinkTagBlock)
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require_relative "version"
5
+ require 'nokogiri'
6
+
7
+ module Jekyll
8
+ module MastodonSocial
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+
13
+ attr_accessor :client_id, :client_secret, :bearer_token
14
+ attr_reader :config, :site, :mastodon_status
15
+
16
+ # Load state from cache dir. Setup cache dir if it doesn't exist
17
+ def setup(site)
18
+ @site = site
19
+ @jekyll_config = site.config
20
+ @config = @jekyll_config["mastodon-syndication"] || {}
21
+
22
+ # Set up the cache folder & files
23
+ setup_config
24
+ cache_data = YAML.load_file(@mastodon_cachefile)
25
+ @mastodon_status = cache_data[:posts]
26
+ @bearer_token = cache_data[:bearer_token]
27
+ @client_id = cache_data[:client_id]
28
+ @client_secret = cache_data[:client_secret]
29
+
30
+ @mastodon_client = Mastodon::REST::Client.new(base_url: @config["server"],
31
+ bearer_token: @bearer_token)
32
+ end
33
+
34
+ def setup_config
35
+ @cache_folder = @site.in_source_dir(@config["cache-folder"] || ".jekyll-cache")
36
+ unless File.exist?(@cache_folder)
37
+ Dir.mkdir(@cache_folder)
38
+ end
39
+ file = Jekyll.sanitized_path(@cache_folder, "mastodon.yml")
40
+ File.open(file, "wb") { |f| f.puts YAML.dump(Hash.new) } unless File.exist? file
41
+ @mastodon_cachefile = file
42
+ end
43
+
44
+ def save_config
45
+ state = {
46
+ posts: @mastodon_status || {},
47
+ bearer_token: @bearer_token,
48
+ client_id: @client_id,
49
+ client_secret: @client_secret
50
+ }
51
+ File.open(@mastodon_cachefile, "wb") { |f|
52
+ f.puts YAML.dump(state)
53
+ }
54
+ end
55
+
56
+ # mastodon_status is one of:
57
+ # nil: this post has not been sent to mastodon
58
+ # true: This post has been sent as a status, but we have no url
59
+ # url: The Mastodon URL for the status linking this post
60
+ def mark_as_published(post, mastodon_status)
61
+ if post.kind_of? String
62
+ post_url = post
63
+ excerpt = ''
64
+ hashtags = nil
65
+ else
66
+ # Don't do anything with posts that are in _drafts
67
+ return if post.path.include? '_drafts'
68
+
69
+ post_url = post.url
70
+ excerpt_html = post.data['excerpt'].to_s
71
+ excerpt = Nokogiri::HTML(excerpt_html).text.strip
72
+ hashtags = post.data['hashtags']
73
+ hashtags = hashtags.split if hashtags.is_a? String
74
+ end
75
+ status = @mastodon_status[post_url]
76
+ if status.nil?
77
+ @mastodon_status[post_url] = {
78
+ mastodon_status: mastodon_status,
79
+ excerpt: excerpt,
80
+ hashtags: hashtags
81
+ }
82
+ else
83
+ status[:mastodon_status] = mastodon_status
84
+ @mastodon_status[post_url] = status
85
+ end
86
+ end
87
+
88
+ def clear_status
89
+ @mastodon_status = {}
90
+ end
91
+
92
+ def syndication_info(url)
93
+ status = @mastodon_status[url]
94
+ return nil unless (status.kind_of? Hash and status[:mastodon_status].kind_of? Hash)
95
+ status[:mastodon_status]
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+ def require_all(group)
103
+ Dir[File.expand_path("#{group}/*.rb", __dir__)].each do |file|
104
+ require file
105
+ end
106
+ end
107
+
108
+ require_all "jekyll/commands"
109
+ require_all "jekyll/generators"
110
+ require_all "jekyll/tags"
111
+
112
+ Jekyll::Hooks.register :site, :after_init do |site|
113
+ Jekyll::MastodonSocial.setup(site)
114
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MastodonSocial
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ module Mastodon
2
+ module Social
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mastodon-social
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Meckler
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-07-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mastodon-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: Add mastodon social links
28
+ email:
29
+ - rattroupe@reiterate-app.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rubocop.yml"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - README.md
38
+ - Rakefile
39
+ - lib/jekyll/commands/mastodon-social.rb
40
+ - lib/jekyll/generators/gather_posse.rb
41
+ - lib/jekyll/tags/mastodon-link.rb
42
+ - lib/mastodon-social.rb
43
+ - lib/version.rb
44
+ - sig/mastodon-social.rbs
45
+ homepage:
46
+ licenses: []
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.6.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.4.10
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Create links for a Jekyll blog that allow reposting on Mastodon
67
+ test_files: []