forked from cybrespace/mastodon
Adding reblogs, favourites, improving atom generation
This commit is contained in:
parent
3b0bc18db9
commit
fa33750105
|
@ -1,4 +1,7 @@
|
||||||
class ProfileController < ApplicationController
|
class ProfileController < ApplicationController
|
||||||
def show
|
def show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def entry
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -93,6 +93,87 @@ module AtomHelper
|
||||||
xml['poco'].note account.note
|
xml['poco'].note account.note
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def in_reply_to(xml, uri, url)
|
||||||
|
xml['thr'].send('in-reply-to', { ref: uri, href: url, type: 'text/html' })
|
||||||
|
end
|
||||||
|
|
||||||
|
def disambiguate_uri(target)
|
||||||
|
if target.local?
|
||||||
|
if target.object_type == :person
|
||||||
|
profile_url(name: target.username)
|
||||||
|
else
|
||||||
|
unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
target.uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def disambiguate_url(target)
|
||||||
|
if target.local?
|
||||||
|
if target.object_type == :person
|
||||||
|
profile_url(name: target.username)
|
||||||
|
else
|
||||||
|
status_url(name: target.stream_entry.account.username, id: target.stream_entry.id)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
target.url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_mention(xml, account)
|
||||||
|
xml.link(rel: 'mentioned', href: disambiguate_uri(account))
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_author(xml, account)
|
||||||
|
object_type xml, :person
|
||||||
|
uri xml, profile_url(name: account.username)
|
||||||
|
name xml, account.username
|
||||||
|
summary xml, account.note
|
||||||
|
link_alternate xml, profile_url(name: account.username)
|
||||||
|
portable_contact xml, account
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_entry(xml, stream_entry)
|
||||||
|
unique_id xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type
|
||||||
|
published_at xml, stream_entry.activity.created_at
|
||||||
|
updated_at xml, stream_entry.activity.updated_at
|
||||||
|
title xml, stream_entry.title
|
||||||
|
content xml, stream_entry.content
|
||||||
|
verb xml, stream_entry.verb
|
||||||
|
link_self xml, atom_entry_url(id: stream_entry.id)
|
||||||
|
object_type xml, stream_entry.object_type
|
||||||
|
|
||||||
|
# Comments need thread element
|
||||||
|
if stream_entry.threaded?
|
||||||
|
in_reply_to xml, disambiguate_uri(stream_entry.thread), disambiguate_url(stream_entry.thread)
|
||||||
|
end
|
||||||
|
|
||||||
|
if stream_entry.targeted?
|
||||||
|
target(xml) do
|
||||||
|
object_type xml, stream_entry.target.object_type
|
||||||
|
simple_id xml, disambiguate_uri(stream_entry.target)
|
||||||
|
title xml, stream_entry.target.title
|
||||||
|
link_alternate xml, disambiguate_url(stream_entry.target)
|
||||||
|
|
||||||
|
# People have summary and portable contacts information
|
||||||
|
if stream_entry.target.object_type == :person
|
||||||
|
summary xml, stream_entry.target.content
|
||||||
|
portable_contact xml, stream_entry.target
|
||||||
|
end
|
||||||
|
|
||||||
|
# Statuses have content
|
||||||
|
if [:note, :comment].include? stream_entry.target.object_type
|
||||||
|
content xml, stream_entry.target.content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stream_entry.mentions.each do |mentioned|
|
||||||
|
link_mention xml, mentioned
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def root_tag(xml, tag, &block)
|
def root_tag(xml, tag, &block)
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Account < ActiveRecord::Base
|
||||||
# Timelines
|
# Timelines
|
||||||
has_many :stream_entries, inverse_of: :account
|
has_many :stream_entries, inverse_of: :account
|
||||||
has_many :statuses, inverse_of: :account
|
has_many :statuses, inverse_of: :account
|
||||||
|
has_many :favourites, inverse_of: :account
|
||||||
|
|
||||||
# Follow relations
|
# Follow relations
|
||||||
has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
|
has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
|
||||||
|
@ -41,7 +42,7 @@ class Account < ActiveRecord::Base
|
||||||
self.username
|
self.username
|
||||||
end
|
end
|
||||||
|
|
||||||
def summary
|
def content
|
||||||
self.note
|
self.note
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
class Favourite < ActiveRecord::Base
|
||||||
|
belongs_to :account, inverse_of: :favourites
|
||||||
|
belongs_to :status, inverse_of: :favourites
|
||||||
|
|
||||||
|
has_one :stream_entry, as: :activity
|
||||||
|
|
||||||
|
def verb
|
||||||
|
:favorite
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
"#{self.account.acct} favourited a status by #{self.status.account.acct}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def content
|
||||||
|
title
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
target.object_type
|
||||||
|
end
|
||||||
|
|
||||||
|
def target
|
||||||
|
self.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def mentions
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def thread
|
||||||
|
target
|
||||||
|
end
|
||||||
|
|
||||||
|
after_create do
|
||||||
|
self.account.stream_entries.create!(activity: self)
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,20 +2,23 @@ class Follow < ActiveRecord::Base
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :target_account, class_name: 'Account'
|
belongs_to :target_account, class_name: 'Account'
|
||||||
|
|
||||||
|
has_one :stream_entry, as: :activity
|
||||||
|
|
||||||
validates :account, :target_account, presence: true
|
validates :account, :target_account, presence: true
|
||||||
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
|
||||||
def verb
|
def verb
|
||||||
:follow
|
:follow
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_type
|
|
||||||
:person
|
|
||||||
end
|
|
||||||
|
|
||||||
def target
|
def target
|
||||||
self.target_account
|
self.target_account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
target.object_type
|
||||||
|
end
|
||||||
|
|
||||||
def content
|
def content
|
||||||
"#{self.account.acct} started following #{self.target_account.acct}"
|
"#{self.account.acct} started following #{self.target_account.acct}"
|
||||||
end
|
end
|
||||||
|
@ -24,6 +27,10 @@ class Follow < ActiveRecord::Base
|
||||||
content
|
content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mentions
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
self.account.stream_entries.create!(activity: self)
|
self.account.stream_entries.create!(activity: self)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,24 +1,56 @@
|
||||||
class Status < ActiveRecord::Base
|
class Status < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :statuses
|
belongs_to :account, inverse_of: :statuses
|
||||||
|
|
||||||
|
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
|
||||||
|
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
|
||||||
|
|
||||||
|
has_one :stream_entry, as: :activity
|
||||||
|
has_many :favourites, inverse_of: :status
|
||||||
|
|
||||||
validates :account, presence: true
|
validates :account, presence: true
|
||||||
|
validates :uri, uniqueness: true, unless: 'local?'
|
||||||
|
|
||||||
|
def local?
|
||||||
|
self.uri.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def reblog?
|
||||||
|
!self.reblog_of_id.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def reply?
|
||||||
|
!self.in_reply_to_id.nil?
|
||||||
|
end
|
||||||
|
|
||||||
def verb
|
def verb
|
||||||
:post
|
reblog? ? :share : :post
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_type
|
def object_type
|
||||||
:note
|
reply? ? :comment : :note
|
||||||
end
|
end
|
||||||
|
|
||||||
def content
|
def content
|
||||||
self.text
|
reblog? ? self.reblog.text : self.text
|
||||||
|
end
|
||||||
|
|
||||||
|
def target
|
||||||
|
self.reblog
|
||||||
end
|
end
|
||||||
|
|
||||||
def title
|
def title
|
||||||
content.truncate(80, omission: "...")
|
content.truncate(80, omission: "...")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mentions
|
||||||
|
m = []
|
||||||
|
|
||||||
|
m << thread.account if reply?
|
||||||
|
m << reblog.account if reblog?
|
||||||
|
|
||||||
|
m
|
||||||
|
end
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
self.account.stream_entries.create!(activity: self)
|
self.account.stream_entries.create!(activity: self)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ class StreamEntry < ActiveRecord::Base
|
||||||
validates :account, :activity, presence: true
|
validates :account, :activity, presence: true
|
||||||
|
|
||||||
def object_type
|
def object_type
|
||||||
self.activity.object_type
|
targeted? ? :activity : self.activity.object_type
|
||||||
end
|
end
|
||||||
|
|
||||||
def verb
|
def verb
|
||||||
|
@ -13,7 +13,7 @@ class StreamEntry < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def targeted?
|
def targeted?
|
||||||
[:follow].include? self.verb
|
[:follow, :share, :favorite].include? verb
|
||||||
end
|
end
|
||||||
|
|
||||||
def target
|
def target
|
||||||
|
@ -27,4 +27,16 @@ class StreamEntry < ActiveRecord::Base
|
||||||
def content
|
def content
|
||||||
self.activity.content
|
self.activity.content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def threaded?
|
||||||
|
[:favorite, :comment].include? verb
|
||||||
|
end
|
||||||
|
|
||||||
|
def thread
|
||||||
|
self.activity.thread
|
||||||
|
end
|
||||||
|
|
||||||
|
def mentions
|
||||||
|
self.activity.mentions
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ class FollowRemoteAccountService
|
||||||
|
|
||||||
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
|
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
|
||||||
account.salmon_url = data.link('salmon').href
|
account.salmon_url = data.link('salmon').href
|
||||||
|
account.url = data.link('http://webfinger.net/rel/profile-page').href
|
||||||
account.public_key = magic_key_to_pem(data.link('magic-public-key').href)
|
account.public_key = magic_key_to_pem(data.link('magic-public-key').href)
|
||||||
account.private_key = nil
|
account.private_key = nil
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ class ProcessInteractionService
|
||||||
body = salmon.unpack(envelope)
|
body = salmon.unpack(envelope)
|
||||||
xml = Nokogiri::XML(body)
|
xml = Nokogiri::XML(body)
|
||||||
|
|
||||||
return if !involves_target_account(xml, target_account) || xml.at_xpath('//author/name').nil? || xml.at_xpath('//author/uri').nil?
|
return if !involves_target_account(xml, target_account) || xml.at_xpath('//xmlns:author/xmlns:name').nil? || xml.at_xpath('//xmlns:author/xmlns:uri').nil?
|
||||||
|
|
||||||
username = xml.at_xpath('//author/name').content
|
username = xml.at_xpath('//xmlns:author/xmlns:name').content
|
||||||
url = xml.at_xpath('//author/uri').content
|
url = xml.at_xpath('//xmlns:author/xmlns:uri').content
|
||||||
domain = Addressable::URI.parse(url).host
|
domain = Addressable::URI.parse(url).host
|
||||||
account = Account.find_by(username: username, domain: domain)
|
account = Account.find_by(username: username, domain: domain)
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,9 @@
|
||||||
Nokogiri::XML::Builder.new do |xml|
|
Nokogiri::XML::Builder.new do |xml|
|
||||||
entry(xml, true) do
|
entry(xml, true) do
|
||||||
unique_id xml, @entry.created_at, @entry.activity_id, @entry.activity_type
|
|
||||||
published_at xml, @entry.activity.created_at
|
|
||||||
updated_at xml, @entry.activity.updated_at
|
|
||||||
title xml, @entry.title
|
|
||||||
content xml, @entry.content
|
|
||||||
verb xml, @entry.verb
|
|
||||||
|
|
||||||
author(xml) do
|
author(xml) do
|
||||||
object_type xml, :person
|
include_author xml, @entry.account
|
||||||
uri xml, profile_url(name: @entry.account.username)
|
|
||||||
name xml, @entry.account.username
|
|
||||||
summary xml, @entry.account.note
|
|
||||||
link_alternate xml, profile_url(name: @entry.account.username)
|
|
||||||
portable_contact xml, @entry.account
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if @entry.targeted?
|
include_entry xml, @entry
|
||||||
target(xml) do
|
|
||||||
object_type xml, @entry.target.object_type
|
|
||||||
simple_id xml, @entry.target.uri
|
|
||||||
title xml, @entry.target.title
|
|
||||||
summary xml, @entry.target.summary
|
|
||||||
link_alternate xml, @entry.target.uri
|
|
||||||
|
|
||||||
if @entry.target.object_type == :person
|
|
||||||
portable_contact xml, @entry.target
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
object_type xml, @entry.object_type
|
|
||||||
end
|
|
||||||
|
|
||||||
link_self xml, atom_entry_url(id: @entry.id)
|
|
||||||
end
|
end
|
||||||
end
|
end.to_xml
|
||||||
|
|
|
@ -6,12 +6,7 @@ Nokogiri::XML::Builder.new do |xml|
|
||||||
updated_at xml, stream_updated_at
|
updated_at xml, stream_updated_at
|
||||||
|
|
||||||
author(xml) do
|
author(xml) do
|
||||||
object_type xml, :person
|
include_author xml, @account
|
||||||
uri xml, profile_url(name: @account.username)
|
|
||||||
name xml, @account.username
|
|
||||||
summary xml, @account.note
|
|
||||||
link_alternate xml, profile_url(name: @account.username)
|
|
||||||
portable_contact xml, @account
|
|
||||||
end
|
end
|
||||||
|
|
||||||
link_alternate xml, profile_url(name: @account.username)
|
link_alternate xml, profile_url(name: @account.username)
|
||||||
|
@ -21,29 +16,7 @@ Nokogiri::XML::Builder.new do |xml|
|
||||||
|
|
||||||
@account.stream_entries.order('id desc').each do |stream_entry|
|
@account.stream_entries.order('id desc').each do |stream_entry|
|
||||||
entry(xml, false) do
|
entry(xml, false) do
|
||||||
unique_id xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type
|
include_entry xml, stream_entry
|
||||||
published_at xml, stream_entry.activity.created_at
|
|
||||||
updated_at xml, stream_entry.activity.updated_at
|
|
||||||
title xml, stream_entry.title
|
|
||||||
content xml, stream_entry.content
|
|
||||||
verb xml, stream_entry.verb
|
|
||||||
link_self xml, atom_entry_url(id: stream_entry.id)
|
|
||||||
|
|
||||||
if stream_entry.targeted?
|
|
||||||
target(xml) do
|
|
||||||
object_type xml, stream_entry.target.object_type
|
|
||||||
simple_id xml, stream_entry.target.uri
|
|
||||||
title xml, stream_entry.target.title
|
|
||||||
summary xml, stream_entry.target.summary
|
|
||||||
link_alternate xml, stream_entry.target.uri
|
|
||||||
|
|
||||||
if stream_entry.target.object_type == :person
|
|
||||||
portable_contact xml, stream_entry.target
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
object_type xml, stream_entry.object_type
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ Rails.application.routes.draw do
|
||||||
get 'atom/entries/:id', to: 'atom#entry', as: :atom_entry
|
get 'atom/entries/:id', to: 'atom#entry', as: :atom_entry
|
||||||
get 'atom/users/:id', to: 'atom#user_stream', as: :atom_user_stream
|
get 'atom/users/:id', to: 'atom#user_stream', as: :atom_user_stream
|
||||||
get 'users/:name', to: 'profile#show', as: :profile
|
get 'users/:name', to: 'profile#show', as: :profile
|
||||||
|
get 'users/:name/:id', to: 'profile#entry', as: :status
|
||||||
|
|
||||||
mount Mastodon::API => '/api/'
|
mount Mastodon::API => '/api/'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddMetadataToStatuses < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :statuses, :in_reply_to_id, :integer, null: true
|
||||||
|
add_column :statuses, :reblog_of_id, :integer, null: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class MakeUrisNullableInStatuses < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
change_column :statuses, :uri, :string, null: true, default: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddUrlToStatuses < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :statuses, :url, :string, null: true, default: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddUrlToAccounts < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :accounts, :url, :string, null: true, default: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateFavourites < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :favourites do |t|
|
||||||
|
t.integer :account_id, null: false
|
||||||
|
t.integer :status_id, null: false
|
||||||
|
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :favourites, [:account_id, :status_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
25
db/schema.rb
25
db/schema.rb
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160222143943) do
|
ActiveRecord::Schema.define(version: 20160223171800) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -31,10 +31,20 @@ ActiveRecord::Schema.define(version: 20160222143943) do
|
||||||
t.text "note", default: "", null: false
|
t.text "note", default: "", null: false
|
||||||
t.string "display_name", default: "", null: false
|
t.string "display_name", default: "", null: false
|
||||||
t.string "uri", default: "", null: false
|
t.string "uri", default: "", null: false
|
||||||
|
t.string "url"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
|
add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
|
||||||
|
|
||||||
|
create_table "favourites", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.integer "status_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "favourites", ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true, using: :btree
|
||||||
|
|
||||||
create_table "follows", force: :cascade do |t|
|
create_table "follows", force: :cascade do |t|
|
||||||
t.integer "account_id", null: false
|
t.integer "account_id", null: false
|
||||||
t.integer "target_account_id", null: false
|
t.integer "target_account_id", null: false
|
||||||
|
@ -45,11 +55,14 @@ ActiveRecord::Schema.define(version: 20160222143943) do
|
||||||
add_index "follows", ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true, using: :btree
|
add_index "follows", ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true, using: :btree
|
||||||
|
|
||||||
create_table "statuses", force: :cascade do |t|
|
create_table "statuses", force: :cascade do |t|
|
||||||
t.string "uri", default: "", null: false
|
t.string "uri"
|
||||||
t.integer "account_id", null: false
|
t.integer "account_id", null: false
|
||||||
t.text "text", default: "", null: false
|
t.text "text", default: "", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.integer "in_reply_to_id"
|
||||||
|
t.integer "reblog_of_id"
|
||||||
|
t.string "url"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
|
add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Favourite, type: :model do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
Loading…
Reference in New Issue