-
Notifications
You must be signed in to change notification settings - Fork 4
FEATURE: Add nodeinfo endpoint #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# frozen_string_literal: true | ||
|
||
module DiscourseActivityPub | ||
class NodeinfoController < ApplicationController | ||
requires_plugin DiscourseActivityPub::PLUGIN_NAME | ||
|
||
include DiscourseActivityPub::EnabledVerification | ||
|
||
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr | ||
|
||
before_action :ensure_site_enabled | ||
|
||
def index | ||
render json: Nodeinfo.index, content_type: "application/jrd+json" | ||
end | ||
|
||
def show | ||
nodeinfo = Nodeinfo.new(params[:version]) | ||
raise Discourse::NotFound unless nodeinfo.supported_version? | ||
render_serialized(nodeinfo, NodeinfoSerializer, root: false) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# frozen_string_literal: true | ||
|
||
module DiscourseActivityPub | ||
class NodeinfoSerializer < ActiveModel::Serializer | ||
attributes :version, :software, :protocols, :services, :usage, :openRegistrations, :metadata | ||
|
||
def software | ||
format(object.software).as_json | ||
end | ||
|
||
def services | ||
format(object.services).as_json | ||
end | ||
|
||
def usage | ||
format(object.usage).as_json | ||
end | ||
|
||
def openRegistrations | ||
object.open_registrations | ||
end | ||
|
||
def metadata | ||
format(object.metadata).as_json | ||
end | ||
|
||
protected | ||
|
||
def format(hash) | ||
hash.deep_transform_keys do |key| | ||
case key | ||
when :active_half_year | ||
"activeHalfyear" | ||
else | ||
key.to_s.camelize(:lower) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# frozen_string_literal: true | ||
|
||
module DiscourseActivityPub | ||
class Nodeinfo | ||
include ActiveModel::Serialization | ||
|
||
VERSION = "2.1" | ||
SOFTWARE_NAME = "discourse" | ||
|
||
# See https://github.com/jhass/nodeinfo/blob/main/schemas/2.1/schema.json for supported enums. | ||
SUPPORTED_PROTOCOLS = %w[activitypub] | ||
SUPPORTED_INBOUND_SERVICES = %w[rss2.0 pop3] | ||
SUPPORTED_OUTBOUND_SERVICES = %w[rss2.0 smtp] | ||
|
||
attr_reader :version | ||
|
||
def initialize(version) | ||
@version = version.to_s | ||
end | ||
|
||
def supported_version? | ||
version == VERSION | ||
end | ||
|
||
def software | ||
{ name: SOFTWARE_NAME, version: Discourse::VERSION::STRING } | ||
end | ||
|
||
def protocols | ||
SUPPORTED_PROTOCOLS | ||
end | ||
|
||
def services | ||
{ inbound: SUPPORTED_INBOUND_SERVICES, outbound: SUPPORTED_OUTBOUND_SERVICES } | ||
end | ||
|
||
def usage | ||
{ | ||
users: { | ||
total: ::Statistics.nodeinfo[:users_total], | ||
active_month: ::Statistics.nodeinfo[:users_seen_month], | ||
active_half_year: ::Statistics.nodeinfo[:users_seen_half_year], | ||
}, | ||
local_posts: ::Statistics.nodeinfo[:posts_local], | ||
local_comments: ::Statistics.nodeinfo[:replies_local], | ||
} | ||
end | ||
|
||
def open_registrations | ||
!SiteSetting.login_required | ||
end | ||
|
||
# Compare https://mastodon.social/nodeinfo/2.0 | ||
def metadata | ||
{ node_name: SiteSetting.title, node_description: SiteSetting.site_description } | ||
end | ||
|
||
def self.index | ||
{ | ||
links: [ | ||
{ | ||
rel: "http://nodeinfo.diaspora.software/ns/schema/#{VERSION}", | ||
href: "#{Discourse.base_url_no_prefix}/nodeinfo/#{VERSION}", | ||
}, | ||
], | ||
}.as_json | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# frozen_string_literal: true | ||
module DiscourseActivityPub | ||
module Statistics | ||
# https://github.com/jhass/nodeinfo/blob/main/schemas/2.2/schema.json#usage | ||
def nodeinfo | ||
{ | ||
users_total: active_users.count, | ||
users_seen_half_year: active_users.where("last_seen_at > ?", 180.days.ago).count, | ||
users_seen_month: active_users.where("last_seen_at > ?", 30.days.ago).count, | ||
posts_local: local_posts.where("reply_to_post_number IS NULL").count, | ||
replies_local: local_posts.where("reply_to_post_number IS NOT NULL").count, | ||
} | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In core, we cache this data. Can we do that here as well? Avoids this route becoming an abuse vector. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added caching and rate limiting into the controller: angusmcleod@9ec7523 |
||
|
||
def active_users | ||
valid_users.where("staged IS FALSE") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need the staged clause here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this in because staged vs unstaged is the equivalent of remote vs local (for the purposes of activitypub) on the user model itself, i.e. it is a reflection of the distinction that nodeinfo is drawing in its own categorisation of users. It's essentially a clarity/surety measure. |
||
end | ||
|
||
def local_posts | ||
::Post.where("user_id NOT IN (SELECT id FROM users WHERE staged IS TRUE)") | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-04/schema#", | ||
"id": "http://nodeinfo.diaspora.software/ns/schema/2.1#", | ||
"description": "NodeInfo schema version 2.1.", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"required": [ | ||
"version", | ||
"software", | ||
"protocols", | ||
"services", | ||
"openRegistrations", | ||
"usage", | ||
"metadata" | ||
], | ||
"properties": { | ||
"version": { | ||
"description": "The schema version, must be 2.1.", | ||
"enum": [ | ||
"2.1" | ||
] | ||
}, | ||
"software": { | ||
"description": "Metadata about server software in use.", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"required": [ | ||
"name", | ||
"version" | ||
], | ||
"properties": { | ||
"name": { | ||
"description": "The canonical name of this server software.", | ||
"type": "string", | ||
"pattern": "^[a-z0-9-]+$" | ||
}, | ||
"version": { | ||
"description": "The version of this server software.", | ||
"type": "string" | ||
}, | ||
"repository": { | ||
"description": "The url of the source code repository of this server software.", | ||
"type": "string" | ||
}, | ||
"homepage": { | ||
"description": "The url of the homepage of this server software.", | ||
"type": "string" | ||
} | ||
} | ||
}, | ||
"protocols": { | ||
"description": "The protocols supported on this server.", | ||
"type": "array", | ||
"minItems": 1, | ||
"items": { | ||
"enum": [ | ||
"activitypub", | ||
"buddycloud", | ||
"dfrn", | ||
"diaspora", | ||
"libertree", | ||
"ostatus", | ||
"pumpio", | ||
"tent", | ||
"xmpp", | ||
"zot" | ||
] | ||
} | ||
}, | ||
"services": { | ||
"description": "The third party sites this server can connect to via their application API.", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"required": [ | ||
"inbound", | ||
"outbound" | ||
], | ||
"properties": { | ||
"inbound": { | ||
"description": "The third party sites this server can retrieve messages from for combined display with regular traffic.", | ||
"type": "array", | ||
"minItems": 0, | ||
"items": { | ||
"enum": [ | ||
"atom1.0", | ||
"gnusocial", | ||
"imap", | ||
"pnut", | ||
"pop3", | ||
"pumpio", | ||
"rss2.0", | ||
"twitter" | ||
] | ||
} | ||
}, | ||
"outbound": { | ||
"description": "The third party sites this server can publish messages to on the behalf of a user.", | ||
"type": "array", | ||
"minItems": 0, | ||
"items": { | ||
"enum": [ | ||
"atom1.0", | ||
"blogger", | ||
"buddycloud", | ||
"diaspora", | ||
"dreamwidth", | ||
"drupal", | ||
"facebook", | ||
"friendica", | ||
"gnusocial", | ||
"google", | ||
"insanejournal", | ||
"libertree", | ||
"linkedin", | ||
"livejournal", | ||
"mediagoblin", | ||
"myspace", | ||
"pinterest", | ||
"pnut", | ||
"posterous", | ||
"pumpio", | ||
"redmatrix", | ||
"rss2.0", | ||
"smtp", | ||
"tent", | ||
"tumblr", | ||
"twitter", | ||
"wordpress", | ||
"xmpp" | ||
] | ||
} | ||
} | ||
} | ||
}, | ||
"openRegistrations": { | ||
"description": "Whether this server allows open self-registration.", | ||
"type": "boolean" | ||
}, | ||
"usage": { | ||
"description": "Usage statistics for this server.", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"required": [ | ||
"users" | ||
], | ||
"properties": { | ||
"users": { | ||
"description": "statistics about the users of this server.", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"properties": { | ||
"total": { | ||
"description": "The total amount of on this server registered users.", | ||
"type": "integer", | ||
"minimum": 0 | ||
}, | ||
"activeHalfyear": { | ||
"description": "The amount of users that signed in at least once in the last 180 days.", | ||
"type": "integer", | ||
"minimum": 0 | ||
}, | ||
"activeMonth": { | ||
"description": "The amount of users that signed in at least once in the last 30 days.", | ||
"type": "integer", | ||
"minimum": 0 | ||
} | ||
} | ||
}, | ||
"localPosts": { | ||
"description": "The amount of posts that were made by users that are registered on this server.", | ||
"type": "integer", | ||
"minimum": 0 | ||
}, | ||
"localComments": { | ||
"description": "The amount of comments that were made by users that are registered on this server.", | ||
"type": "integer", | ||
"minimum": 0 | ||
} | ||
} | ||
}, | ||
"metadata": { | ||
"description": "Free form key value pairs for software specific values. Clients should not rely on any specific key present.", | ||
"type": "object", | ||
"minProperties": 0, | ||
"additionalProperties": true | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.