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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +22 -0
- data/app/controllers/activitypub/rails/application_controller.rb +7 -0
- data/app/helpers/activitypub/rails/application_helper.rb +6 -0
- data/app/jobs/activitypub/application_job.rb +4 -0
- data/app/jobs/activitypub/delivery_job.rb +38 -0
- data/app/jobs/activitypub/distribution_job.rb +42 -0
- data/app/jobs/activitypub/processing_job.rb +11 -0
- data/app/jobs/activitypub/undo_vote_distribution_job.rb +47 -0
- data/app/jobs/activitypub/update_distribution_job.rb +37 -0
- data/app/jobs/activitypub/vote_distribution_job.rb +38 -0
- data/app/models/activitypub/application_record.rb +5 -0
- data/app/models/activitypub/dict.rb +30 -0
- data/app/models/activitypub/rails/actor.rb +7 -0
- data/app/models/concerns/activitypub/rails/actor_concern.rb +69 -0
- data/app/models/concerns/activitypub/rails/object_concern.rb +23 -0
- data/app/serializers/activitypub/accept_follow_serializer.rb +20 -0
- data/app/serializers/activitypub/activity_serializer.rb +24 -0
- data/app/serializers/activitypub/actor_serializer.rb +37 -0
- data/app/serializers/activitypub/base_serializer.rb +39 -0
- data/app/serializers/activitypub/comment_serializer.rb +43 -0
- data/app/serializers/activitypub/follow_serializer.rb +17 -0
- data/app/serializers/activitypub/image_serializer.rb +11 -0
- data/app/serializers/activitypub/like_serializer.rb +25 -0
- data/app/serializers/activitypub/outbox_serializer.rb +49 -0
- data/app/serializers/activitypub/story_serializer.rb +56 -0
- data/app/serializers/activitypub/undo_like_serializer.rb +16 -0
- data/app/serializers/activitypub/update_serializer.rb +32 -0
- data/app/views/layouts/activitypub/application.html.erb +16 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20181214172927_create_activitypub_rails_actors.rb +29 -0
- data/lib/activitypub/rails.rb +17 -0
- data/lib/activitypub/rails/engine.rb +7 -0
- data/lib/activitypub/rails/version.rb +5 -0
- data/lib/tasks/activitypub/rails_tasks.rake +4 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
@@ -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,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,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,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,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>
|
data/config/routes.rb
ADDED
@@ -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
|
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: []
|