activitypub-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []