activitypub-rails 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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +22 -0
  5. data/app/controllers/activitypub/rails/application_controller.rb +7 -0
  6. data/app/helpers/activitypub/rails/application_helper.rb +6 -0
  7. data/app/jobs/activitypub/application_job.rb +4 -0
  8. data/app/jobs/activitypub/delivery_job.rb +38 -0
  9. data/app/jobs/activitypub/distribution_job.rb +42 -0
  10. data/app/jobs/activitypub/processing_job.rb +11 -0
  11. data/app/jobs/activitypub/undo_vote_distribution_job.rb +47 -0
  12. data/app/jobs/activitypub/update_distribution_job.rb +37 -0
  13. data/app/jobs/activitypub/vote_distribution_job.rb +38 -0
  14. data/app/models/activitypub/application_record.rb +5 -0
  15. data/app/models/activitypub/dict.rb +30 -0
  16. data/app/models/activitypub/rails/actor.rb +7 -0
  17. data/app/models/concerns/activitypub/rails/actor_concern.rb +69 -0
  18. data/app/models/concerns/activitypub/rails/object_concern.rb +23 -0
  19. data/app/serializers/activitypub/accept_follow_serializer.rb +20 -0
  20. data/app/serializers/activitypub/activity_serializer.rb +24 -0
  21. data/app/serializers/activitypub/actor_serializer.rb +37 -0
  22. data/app/serializers/activitypub/base_serializer.rb +39 -0
  23. data/app/serializers/activitypub/comment_serializer.rb +43 -0
  24. data/app/serializers/activitypub/follow_serializer.rb +17 -0
  25. data/app/serializers/activitypub/image_serializer.rb +11 -0
  26. data/app/serializers/activitypub/like_serializer.rb +25 -0
  27. data/app/serializers/activitypub/outbox_serializer.rb +49 -0
  28. data/app/serializers/activitypub/story_serializer.rb +56 -0
  29. data/app/serializers/activitypub/undo_like_serializer.rb +16 -0
  30. data/app/serializers/activitypub/update_serializer.rb +32 -0
  31. data/app/views/layouts/activitypub/application.html.erb +16 -0
  32. data/config/initializers/inflections.rb +3 -0
  33. data/config/routes.rb +2 -0
  34. data/db/migrate/20181214172927_create_activitypub_rails_actors.rb +29 -0
  35. data/lib/activitypub/rails.rb +17 -0
  36. data/lib/activitypub/rails/engine.rb +7 -0
  37. data/lib/activitypub/rails/version.rb +5 -0
  38. data/lib/tasks/activitypub/rails_tasks.rake +4 -0
  39. metadata +109 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f23f289b5c2543accc61329e0bd31a9c92effce1e5f93d06b926f28727ac7d5
4
+ data.tar.gz: 541c426383ddaa992346a67126a9ea9bc7fc804792a29789dab17c1d9a89ff17
5
+ SHA512:
6
+ metadata.gz: e4543b00629ccfb172a61ba1b929750f075eda8055663631256cfbfeef2d460b0c96e045914893c2e4999c04dcc2f9860b766749256eb2079dd9e5e3d83814e9
7
+ data.tar.gz: 2e19e853af10e0978f28f6ebde659ee42204a25c54f61764ce469f95dabee5b9799aca464658620fa4f775792a7ad9648c221be1a08493b70b2a099fbaeccc49
@@ -0,0 +1,20 @@
1
+ Copyright 2018 mbajur
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # ActivityPub
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'activity_pub'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install activity_pub
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActivityPub'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
@@ -0,0 +1,7 @@
1
+ module ActivityPub
2
+ module Rails
3
+ class ApplicationController < ActionController::Base
4
+ protect_from_forgery with: :exception
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module ActivityPub
2
+ module Rails
3
+ module ApplicationHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module ActivityPub
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,38 @@
1
+ class ActivityPub::DeliveryJob < ApplicationJob
2
+ queue_as :push
3
+
4
+ HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
5
+
6
+ def perform(json, source_account_id, inbox_url, options = {})
7
+ @json = json
8
+ @source_account = ::Account.find(source_account_id)
9
+ @inbox_url = inbox_url
10
+ @options = options.with_indifferent_access
11
+
12
+ perform_request
13
+ rescue => e
14
+ raise e.class, "Delivery failed for #{inbox_url}: #{e.message}", e.backtrace[0]
15
+ end
16
+
17
+ private
18
+
19
+ def build_request
20
+ request = Request.new(:post, @inbox_url, body: @json)
21
+ request.on_behalf_of(@source_account, :uri)
22
+ request.add_headers(HEADERS)
23
+ end
24
+
25
+ def perform_request
26
+ build_request.perform do |response|
27
+ raise Prismo::Exceptions::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
28
+ end
29
+ end
30
+
31
+ def response_successful?(response)
32
+ (200...300).cover?(response.code)
33
+ end
34
+
35
+ def response_error_unsalvageable?(response)
36
+ (400...500).cover?(response.code) && response.code != 429
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ class ActivityPub::DistributionJob < ApplicationJob
2
+ queue_as :push
3
+
4
+ def self.call(resource)
5
+ ActivityPub::DistributionJob.perform_later(resource.class.name, resource.id)
6
+ end
7
+
8
+ def perform(resource_type, resource_id)
9
+ @resource = resource_type.constantize.find(resource_id)
10
+ @account = @resource.account
11
+
12
+ inboxes.each do |inbox_url|
13
+ ActivityPub::DeliveryJob.perform_later(
14
+ signed_payload, @account.id, inbox_url
15
+ )
16
+ end
17
+
18
+ rescue ActiveRecord::RecordNotFound
19
+ true
20
+ end
21
+
22
+ private
23
+
24
+ def inboxes
25
+ case @resource
26
+ when Comment
27
+ @resource.story.comments.remote.map { |c| c.account.inbox_url }.reject(&:blank?)
28
+ when Story
29
+ @resource.account.followers.inboxes
30
+ else
31
+ []
32
+ end
33
+ end
34
+
35
+ def signed_payload
36
+ Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
37
+ end
38
+
39
+ def payload
40
+ ActivityPub::ActivitySerializer.new(@resource, with_context: true).as_json
41
+ end
42
+ end
@@ -0,0 +1,11 @@
1
+ class ActivityPub::ProcessingJob < ApplicationJob
2
+ queue_as :default
3
+
4
+ def perform(account_id, body)
5
+ ActivityPub::ProcessCollection.run!(
6
+ body: body,
7
+ account: ::Account.find(account_id),
8
+ override_timestamps: true
9
+ )
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ class ActivityPub::UndoVoteDistributionJob < ApplicationJob
2
+ queue_as :push
3
+
4
+ def self.call(vote)
5
+ ActivityPub::UndoVoteDistributionJob.perform_later(
6
+ vote.id,
7
+ vote.voteable_type,
8
+ vote.voteable_id,
9
+ vote.account_id
10
+ )
11
+ end
12
+
13
+ def perform(vote_id, voteable_type, voteable_id, account_id)
14
+ @vote_id = vote_id
15
+ @voteable = voteable_type.constantize.find(voteable_id)
16
+ @account = Account.find(account_id)
17
+
18
+ inboxes.each do |inbox_url|
19
+ ActivityPub::DeliveryJob.perform_later(
20
+ signed_payload, account.id, inbox_url
21
+ )
22
+ end
23
+
24
+ rescue ActiveRecord::RecordNotFound
25
+ true
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :voteable, :account
31
+
32
+ def inboxes
33
+ if voteable.is_a?(Comment)
34
+ [voteable.account.inbox_url]
35
+ else
36
+ []
37
+ end
38
+ end
39
+
40
+ def signed_payload
41
+ @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
42
+ end
43
+
44
+ def payload
45
+ @payload ||= ActivityPub::UndoLikeSerializer.new(voteable, with_context: true).as_json
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ class ActivityPub::UpdateDistributionJob < ApplicationJob
2
+ queue_as :push
3
+
4
+ def self.call_later(resource)
5
+ perform_later(resource.id, resource.class.name)
6
+ end
7
+
8
+ def perform(resource_id, resource_class_name)
9
+ @resource = resource_class_name.constantize.find(resource_id)
10
+
11
+ inboxes.each do |inbox_url|
12
+ ActivityPub::DeliveryJob.perform_later(signed_payload, account.id, inbox_url)
13
+ end
14
+ rescue ActiveRecord::RecordNotFound
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :resource
21
+
22
+ def account
23
+ @account ||= resource.is_a?(Account) ? resource : resource.account
24
+ end
25
+
26
+ def inboxes
27
+ @inboxes ||= account.followers.inboxes
28
+ end
29
+
30
+ def signed_payload
31
+ @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
32
+ end
33
+
34
+ def payload
35
+ ActivityPub::UpdateSerializer.new(resource, with_context: true).as_json
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ class ActivityPub::VoteDistributionJob < ApplicationJob
2
+ queue_as :push
3
+
4
+ def perform(vote_id)
5
+ @vote = Vote.find(vote_id)
6
+ @voteable = vote.voteable
7
+ @account = vote.account
8
+
9
+ inboxes.each do |inbox_url|
10
+ ActivityPub::DeliveryJob.perform_later(
11
+ signed_payload, account.id, inbox_url
12
+ )
13
+ end
14
+
15
+ rescue ActiveRecord::RecordNotFound
16
+ true
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :vote, :account, :voteable
22
+
23
+ def inboxes
24
+ if voteable.is_a?(Comment)
25
+ [voteable.account.inbox_url]
26
+ else
27
+ []
28
+ end
29
+ end
30
+
31
+ def signed_payload
32
+ @signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
33
+ end
34
+
35
+ def payload
36
+ @payload ||= ActivityPub::LikeSerializer.new(voteable, with_context: true).as_json
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module ActivityPub
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ module ActivityPub
2
+ class Dict
3
+ ACTOR_TYPES = {
4
+ application: 'application',
5
+ group: 'group',
6
+ organization: 'organization',
7
+ person: 'person',
8
+ service: 'service'
9
+ }.freeze
10
+
11
+ OBJECT_TYPES = {
12
+ relationship: 'relationship',
13
+ article: 'article',
14
+ document: 'document',
15
+ audio: 'audio',
16
+ image: 'image',
17
+ video: 'video',
18
+ note: 'note',
19
+ page: 'page',
20
+ event: 'event',
21
+ place: 'place',
22
+ profile: 'profile',
23
+ tombstone: 'tombstone',
24
+ }.freeze
25
+
26
+ LINK_TYPES = {
27
+ mention: 'mention',
28
+ }.freeze
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module ActivityPub::Rails
2
+ class Actor < ApplicationRecord
3
+ include ActivityPub::Rails::ActorConcern
4
+
5
+ self.table_name = 'activitypub_rails_actors'
6
+ end
7
+ end
@@ -0,0 +1,69 @@
1
+ module ActivityPub
2
+ module Rails::ActorConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ USERNAME_REGEXP = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i
6
+ MENTION_REGEXP = /(?<=^|[^\/[:word:]])@((#{USERNAME_REGEXP})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
7
+
8
+ included do
9
+ include ActivityPub::Rails::ObjectConcern
10
+
11
+ scope :matches_slug, ->(value) { where(arel_table[:slug].matches("#{value}%")) }
12
+ scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
13
+
14
+ before_create :generate_keys
15
+
16
+ validates :slug, presence: true
17
+ validates :slug, uniqueness: { scope: :domain, case_sensitive: true },
18
+ if: -> { !local? }
19
+ validates :slug, format: { with: /\A[a-z0-9_]+\z/i },
20
+ length: { maximum: 30 }, if: -> { local? },
21
+ uniqueness: { scope: :domain, case_sensitive: true }
22
+ end
23
+
24
+ def acct
25
+ local? ? slug : "#{slug}@#{domain}"
26
+ end
27
+
28
+ def possibly_stale?
29
+ last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
30
+ end
31
+
32
+ def local_slug_and_domain
33
+ "#{slug}@#{ActivityPub.local_domain}"
34
+ end
35
+
36
+ def to_webfinger_s
37
+ "acct:#{local_slug_and_domain}"
38
+ end
39
+
40
+ def keypair
41
+ @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
42
+ end
43
+
44
+ def magic_key
45
+ modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component|
46
+ result = []
47
+
48
+ until component.zero?
49
+ result << [component % 256].pack('C')
50
+ component >>= 8
51
+ end
52
+
53
+ result.reverse.join
54
+ end
55
+
56
+ (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
57
+ end
58
+
59
+ private
60
+
61
+ def generate_keys
62
+ return unless local? && !Rails.env.test?
63
+
64
+ keypair = OpenSSL::PKey::RSA.new(2048)
65
+ self.private_key = keypair.to_pem
66
+ self.public_key = keypair.public_key.to_pem
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ module ActivityPub
2
+ module Rails::ObjectConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :remote, -> { where.not(domain: nil) }
7
+ scope :local, -> { where(domain: nil) }
8
+ scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
9
+
10
+ def self.find_local(id)
11
+ local.find_by(id: id)
12
+ end
13
+ end
14
+
15
+ def local?
16
+ domain.nil?
17
+ end
18
+
19
+ def remote?
20
+ !local?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module ActivityPub
2
+ class AcceptFollowSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: id,
6
+ type: 'Accept',
7
+ actor: ActivityPub::TagManager.instance.uri_for(object.target_account),
8
+ object: ActivityPub::FollowSerializer.new(object).data
9
+ }
10
+ end
11
+
12
+ def id
13
+ [
14
+ ActivityPub::TagManager.instance.uri_for(object.target_account),
15
+ '#accepts/follows/',
16
+ object.id
17
+ ].join
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module ActivityPub
2
+ class ActivitySerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: ActivityPub::TagManager.instance.activity_uri_for(object),
6
+ object: parent_object,
7
+ type: 'Create',
8
+ actor: ActivityPub::TagManager.instance.uri_for(object.account),
9
+ published: object.created_at.iso8601,
10
+ to: ActivityPub::TagManager.instance.to(object),
11
+ cc: ActivityPub::TagManager.instance.cc(object)
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def parent_object
18
+ case object
19
+ when Story then ActivityPub::StorySerializer.new(object).data
20
+ when Comment then ActivityPub::CommentSerializer.new(object).data
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module ActivityPub
2
+ class ActorSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ result = {
5
+ id: account_url(object.username),
6
+ type: 'Person',
7
+ name: object.display_name,
8
+ preferredUsername: object.username,
9
+ summary: object.bio,
10
+ inbox: activitypub_account_inbox_url(object.username),
11
+ outbox: outbox_activitypub_account_url(object.username),
12
+ url: account_url(object.username),
13
+ publicKey: public_key,
14
+ }
15
+
16
+ result[:icon] = icon
17
+
18
+ result
19
+ end
20
+
21
+ private
22
+
23
+ def public_key
24
+ {
25
+ id: ActivityPub::TagManager.instance.uri_for(object) + '#main-key',
26
+ owner: account_url(object.username),
27
+ publicKeyPem: object.public_key
28
+ }
29
+ end
30
+
31
+ def icon
32
+ return nil if !object.avatar_data? || !object.avatar_attacher.stored?
33
+
34
+ ActivityPub::ImageSerializer.new(object.avatar).data
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ module ActivityPub
2
+ class BaseSerializer < Granola::Serializer
3
+ include RoutingHelper
4
+
5
+ CONTEXT = [
6
+ 'https://www.w3.org/ns/activitystreams',
7
+ 'https://w3id.org/security/v1',
8
+
9
+ {
10
+ 'Hashtag' => 'as:Hashtag'
11
+ }, {
12
+ 'votes' => {
13
+ '@id' => 'as:votes',
14
+ '@type' => '@id'
15
+ }
16
+ }
17
+ ].freeze
18
+
19
+ attr_reader :options
20
+
21
+ def initialize(object, options = {})
22
+ @options = options
23
+ super(object)
24
+ end
25
+
26
+ def as_json(opts = {})
27
+ options.merge!(opts)
28
+
29
+ json = data
30
+ json['@context'] = CONTEXT if options[:with_context]
31
+
32
+ json
33
+ end
34
+
35
+ def to_json(opts = {})
36
+ Oj.dump(as_json(opts))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ module ActivityPub
2
+ class CommentSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: ActivityPub::TagManager.instance.uri_for(object),
6
+ type: object.object_type.to_s.capitalize,
7
+ published: object.created_at.iso8601,
8
+ url: url,
9
+ votes: object.votes_count,
10
+ attributedTo: attributed_to,
11
+ to: [ActivityPub::TagManager::COLLECTIONS[:public]],
12
+ inReplyTo: in_reply_to,
13
+ content: object.body_html,
14
+ source: source,
15
+ }
16
+ end
17
+
18
+ def url
19
+ { type: 'Link', mediaType: 'text/html', href: comment_url(object) }
20
+ end
21
+
22
+ def attributed_to
23
+ [
24
+ { type: 'Person', id: account_url(object.account.username) }
25
+ ]
26
+ end
27
+
28
+ def in_reply_to
29
+ if object.parent_id.present?
30
+ ActivityPub::TagManager.instance.uri_for(object.parent)
31
+ else
32
+ ActivityPub::TagManager.instance.uri_for(object.story)
33
+ end
34
+ end
35
+
36
+ def source
37
+ {
38
+ content: object.body,
39
+ mediaType: 'text/markdown'
40
+ }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ module ActivityPub
2
+ class FollowSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: id,
6
+ type: 'Follow',
7
+ actor: ActivityPub::TagManager.instance.uri_for(object.account),
8
+ object: ActivityPub::TagManager.instance.uri_for(object.target_account)
9
+ }
10
+ end
11
+
12
+ def id
13
+ ActivityPub::TagManager.instance.uri_for(object) ||
14
+ [ActivityPub::TagManager.instance.uri_for(object.account), '#follows/', object.id].join
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module ActivityPub
2
+ class ImageSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ type: 'Image',
6
+ url: image_url(object[:size_400].url, host: ActionController::Base.asset_host),
7
+ media_type: object[object.keys.first].mime_type
8
+ }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module ActivityPub
2
+ class LikeSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: id,
6
+ object: parent_object,
7
+ type: 'Like',
8
+ actor: ActivityPub::TagManager.instance.uri_for(object.account),
9
+ }
10
+ end
11
+
12
+ def id
13
+ [ActivityPub::TagManager.instance.uri_for(object.account), '#likes/', object.id].join
14
+ end
15
+
16
+ private
17
+
18
+ def parent_object
19
+ case object
20
+ when Story then ActivityPub::StorySerializer.new(object).data
21
+ when Comment then ActivityPub::CommentSerializer.new(object).data
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module ActivityPub
2
+ class OutboxSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ result = {
5
+ id: object.id,
6
+ type: type
7
+ }
8
+
9
+ result[:totalItems] = total_items if object.size.present?
10
+ result[:next] = object.next if object.next.present?
11
+ result[:prev] = object.prev if object.prev.present?
12
+ result[:part_of] = object.part_of if object.part_of.present?
13
+
14
+ result[:first] = object.first if object.first.present?
15
+ result[:last] = object.last if object.last.present?
16
+
17
+ result[:items] = items if (!object.items.nil? || page?) && !ordered?
18
+ result[:ordered_items] = items if (!object.items.nil? || page?) && ordered?
19
+
20
+ result
21
+ end
22
+
23
+ def type
24
+ if page?
25
+ ordered? ? 'OrderedCollectionPage' : 'CollectionPage'
26
+ else
27
+ ordered? ? 'OrderedCollection' : 'Collection'
28
+ end
29
+ end
30
+
31
+ def total_items
32
+ object.size
33
+ end
34
+
35
+ def items
36
+ ActivityPub::StorySerializer.list(object.items).data
37
+ end
38
+
39
+ private
40
+
41
+ def ordered?
42
+ object.type == :ordered
43
+ end
44
+
45
+ def page?
46
+ object.part_of.present?
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ module ActivityPub
2
+ class StorySerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: ActivityPub::TagManager.instance.uri_for(object),
6
+ type: object.object_type.to_s.capitalize,
7
+ name: object.title,
8
+ published: object.created_at.iso8601,
9
+ content: object.decorate.description_html,
10
+ source: source,
11
+ url: url,
12
+ votes: object.votes_count,
13
+ attributedTo: attributed_to,
14
+ to: [ActivityPub::TagManager::COLLECTIONS[:public]],
15
+ tag: tags,
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def url
22
+ {
23
+ type: 'Link',
24
+ mediaType: 'text/html',
25
+ href: object.article? ? story_url(object) : object.url
26
+ }
27
+ end
28
+
29
+ def attributed_to
30
+ [
31
+ { type: 'Person', id: account_url(object.account.username) }
32
+ ]
33
+ end
34
+
35
+ def tags
36
+ TagSerializer.list(object.tags).data
37
+ end
38
+
39
+ def source
40
+ {
41
+ content: object.description,
42
+ mediaType: 'text/markdown'
43
+ }
44
+ end
45
+
46
+ class TagSerializer < ActivityPub::BaseSerializer
47
+ def data
48
+ {
49
+ type: 'Hashtag',
50
+ href: tag_stories_url(object.name),
51
+ name: "##{object.name}"
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ module ActivityPub
2
+ class UndoLikeSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: id,
6
+ type: 'Undo',
7
+ object: ActivityPub::LikeSerializer.new(object).data,
8
+ actor: ActivityPub::TagManager.instance.uri_for(object.account)
9
+ }
10
+ end
11
+
12
+ def id
13
+ [ActivityPub::TagManager.instance.uri_for(object.account), '#likes/', object.id, '/undo'].join
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ module ActivityPub
2
+ class UpdateSerializer < ActivityPub::BaseSerializer
3
+ def data
4
+ {
5
+ id: id,
6
+ type: 'Update',
7
+ object: object_serialized,
8
+ actor: actor
9
+ }
10
+ end
11
+
12
+ def id
13
+ [ActivityPub::TagManager.instance.uri_for(object), '#updates/', object.updated_at.to_i].join
14
+ end
15
+
16
+ def actor
17
+ resource = object.is_a?(Account) ? object : object.account
18
+ ActivityPub::TagManager.instance.uri_for(resource)
19
+ end
20
+
21
+ def object_serialized
22
+ case object
23
+ when Account
24
+ ActivityPub::ActorSerializer.new(object).data
25
+ when Story
26
+ ActivityPub::StorySerializer.new(object).data
27
+ when Comment
28
+ ActivityPub::CommentSerializer.new(object).data
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Activity pub</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "activity_pub/application", media: "all" %>
9
+ <%= javascript_include_tag "activity_pub/application" %>
10
+ </head>
11
+ <body>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
2
+ inflect.acronym 'ActivityPub'
3
+ end
@@ -0,0 +1,2 @@
1
+ ActivityPub::Rails::Engine.routes.draw do
2
+ end
@@ -0,0 +1,29 @@
1
+ class CreateActivityPubRailsActors < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :activitypub_rails_actors, id: false do |t|
4
+ t.bigint :id
5
+ t.string :slug, null: false, default: ''
6
+ t.string :domain
7
+ t.text :private_key
8
+ t.text :public_key, null: false, default: ''
9
+ t.string :remote_url, null: false, default: ''
10
+ t.string :name, null: false, default: ''
11
+ t.text :summary
12
+ t.string :type
13
+ t.string :uri, null: false, default: ''
14
+ t.string :url
15
+ t.datetime :subscription_expires_at
16
+ t.datetime :last_webfingered_at
17
+ t.string :inbox_url, null: false, default: ''
18
+ t.string :outbox_url, null: false, default: ''
19
+ t.string :shared_inbox_url, null: false, default: ''
20
+ t.string :actor_type
21
+ t.string :followers_url
22
+ t.integer :following_count, default: 0
23
+ t.integer :followers_count, default: 0
24
+
25
+ t.timestamps
26
+ end
27
+ add_index :activitypub_rails_actors, %i[type slug domain], unique: true
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ require "activitypub/rails/engine"
2
+
3
+ module ActivityPub
4
+ module Rails
5
+ class << self
6
+ mattr_accessor :local_domain
7
+
8
+ # add default values of more config vars here
9
+ # self.site_name = "Site Name"
10
+ end
11
+
12
+ # this function maps the vars from your app into your engine
13
+ def self.setup(&block)
14
+ yield self
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module ActivityPub
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace ActivityPub::Rails
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ActivityPub
2
+ module Rails
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :activitypub_rails do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activitypub-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - mbajur
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.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: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: ActivityPub engine for Ruby on Rails
42
+ email:
43
+ - mbajur@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/controllers/activitypub/rails/application_controller.rb
52
+ - app/helpers/activitypub/rails/application_helper.rb
53
+ - app/jobs/activitypub/application_job.rb
54
+ - app/jobs/activitypub/delivery_job.rb
55
+ - app/jobs/activitypub/distribution_job.rb
56
+ - app/jobs/activitypub/processing_job.rb
57
+ - app/jobs/activitypub/undo_vote_distribution_job.rb
58
+ - app/jobs/activitypub/update_distribution_job.rb
59
+ - app/jobs/activitypub/vote_distribution_job.rb
60
+ - app/models/activitypub/application_record.rb
61
+ - app/models/activitypub/dict.rb
62
+ - app/models/activitypub/rails/actor.rb
63
+ - app/models/concerns/activitypub/rails/actor_concern.rb
64
+ - app/models/concerns/activitypub/rails/object_concern.rb
65
+ - app/serializers/activitypub/accept_follow_serializer.rb
66
+ - app/serializers/activitypub/activity_serializer.rb
67
+ - app/serializers/activitypub/actor_serializer.rb
68
+ - app/serializers/activitypub/base_serializer.rb
69
+ - app/serializers/activitypub/comment_serializer.rb
70
+ - app/serializers/activitypub/follow_serializer.rb
71
+ - app/serializers/activitypub/image_serializer.rb
72
+ - app/serializers/activitypub/like_serializer.rb
73
+ - app/serializers/activitypub/outbox_serializer.rb
74
+ - app/serializers/activitypub/story_serializer.rb
75
+ - app/serializers/activitypub/undo_like_serializer.rb
76
+ - app/serializers/activitypub/update_serializer.rb
77
+ - app/views/layouts/activitypub/application.html.erb
78
+ - config/initializers/inflections.rb
79
+ - config/routes.rb
80
+ - db/migrate/20181214172927_create_activitypub_rails_actors.rb
81
+ - lib/activitypub/rails.rb
82
+ - lib/activitypub/rails/engine.rb
83
+ - lib/activitypub/rails/version.rb
84
+ - lib/tasks/activitypub/rails_tasks.rake
85
+ homepage: https://gitlab.com/prismosuite/activitypub-rails
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.7.7
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: ActivityPub engine for Ruby on Rails
109
+ test_files: []