2021-05-17 18:06:58 +01:00
/ *
GoToSocial
Copyright ( C ) 2021 GoToSocial Authors admin @ gotosocial . org
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2021-05-30 12:12:00 +01:00
package processing
2021-05-08 13:25:55 +01:00
import (
2021-05-15 10:58:11 +01:00
"context"
2021-07-05 12:23:03 +01:00
"errors"
2021-05-08 13:25:55 +01:00
"fmt"
"net/http"
2021-05-21 14:48:26 +01:00
"net/url"
2021-05-08 13:25:55 +01:00
"github.com/go-fed/activity/streams"
2021-06-26 15:21:40 +01:00
"github.com/go-fed/activity/streams/vocab"
2021-05-09 19:34:27 +01:00
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
2021-05-08 13:25:55 +01:00
"github.com/superseriousbusiness/gotosocial/internal/db"
2021-06-13 17:42:28 +01:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2021-05-08 13:25:55 +01:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
2021-05-15 10:58:11 +01:00
"github.com/superseriousbusiness/gotosocial/internal/util"
2021-05-08 13:25:55 +01:00
)
2021-07-05 12:23:03 +01:00
func ( p * processor ) GetFediUser ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-08 13:25:55 +01:00
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-08 13:25:55 +01:00
}
2021-06-26 15:21:40 +01:00
var requestedPerson vocab . ActivityStreamsPerson
2021-07-05 12:23:03 +01:00
if util . IsPublicKeyPath ( requestURL ) {
2021-06-26 15:21:40 +01:00
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
2021-08-25 14:34:33 +01:00
requestedPerson , err = p . tc . AccountToASMinimal ( ctx , requestedAccount )
2021-06-26 15:21:40 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
2021-07-05 12:23:03 +01:00
} else if util . IsUserPath ( requestURL ) {
2021-06-26 15:21:40 +01:00
// if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
2021-07-05 12:23:03 +01:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-26 15:21:40 +01:00
}
2021-08-10 12:32:39 +01:00
// if we're not already handshaking/dereferencing a remote account, dereference it now
2021-08-25 14:34:33 +01:00
if ! p . federator . Handshaking ( ctx , requestedUsername , requestingAccountURI ) {
requestingAccount , _ , err := p . federator . GetRemoteAccount ( ctx , requestedUsername , requestingAccountURI , false )
2021-06-27 10:46:07 +01:00
if err != nil {
return nil , gtserror . NewErrorNotAuthorized ( err )
}
2021-08-25 14:34:33 +01:00
blocked , err := p . db . IsBlocked ( ctx , requestedAccount . ID , requestingAccount . ID , true )
2021-06-27 10:46:07 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if blocked {
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
}
2021-06-26 15:21:40 +01:00
}
2021-08-25 14:34:33 +01:00
requestedPerson , err = p . tc . AccountToAS ( ctx , requestedAccount )
2021-06-26 15:21:40 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
} else {
return nil , gtserror . NewErrorBadRequest ( fmt . Errorf ( "path was not public key path or user path" ) )
2021-05-08 13:25:55 +01:00
}
data , err := streams . Serialize ( requestedPerson )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-08 13:25:55 +01:00
}
return data , nil
}
2021-05-09 19:34:27 +01:00
2021-07-05 12:23:03 +01:00
func ( p * processor ) GetFediFollowers ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-21 14:48:26 +01:00
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-21 14:48:26 +01:00
}
// authenticate the request
2021-07-05 12:23:03 +01:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 10:46:07 +01:00
}
2021-08-25 14:34:33 +01:00
requestingAccount , _ , err := p . federator . GetRemoteAccount ( ctx , requestedUsername , requestingAccountURI , false )
2021-05-21 14:48:26 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-21 14:48:26 +01:00
}
2021-08-25 14:34:33 +01:00
blocked , err := p . db . IsBlocked ( ctx , requestedAccount . ID , requestingAccount . ID , true )
2021-05-21 14:48:26 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 14:48:26 +01:00
}
if blocked {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-21 14:48:26 +01:00
}
requestedAccountURI , err := url . Parse ( requestedAccount . URI )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error parsing url %s: %s" , requestedAccount . URI , err ) )
2021-05-21 14:48:26 +01:00
}
requestedFollowers , err := p . federator . FederatingDB ( ) . Followers ( context . Background ( ) , requestedAccountURI )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error fetching followers for uri %s: %s" , requestedAccountURI . String ( ) , err ) )
2021-05-21 14:48:26 +01:00
}
data , err := streams . Serialize ( requestedFollowers )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 14:48:26 +01:00
}
return data , nil
}
2021-07-05 12:23:03 +01:00
func ( p * processor ) GetFediFollowing ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-23 17:07:04 +01:00
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-23 17:07:04 +01:00
}
// authenticate the request
2021-07-05 12:23:03 +01:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 10:46:07 +01:00
}
2021-08-25 14:34:33 +01:00
requestingAccount , _ , err := p . federator . GetRemoteAccount ( ctx , requestedUsername , requestingAccountURI , false )
2021-05-23 17:07:04 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-23 17:07:04 +01:00
}
2021-08-25 14:34:33 +01:00
blocked , err := p . db . IsBlocked ( ctx , requestedAccount . ID , requestingAccount . ID , true )
2021-05-23 17:07:04 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-23 17:07:04 +01:00
}
if blocked {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-23 17:07:04 +01:00
}
requestedAccountURI , err := url . Parse ( requestedAccount . URI )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error parsing url %s: %s" , requestedAccount . URI , err ) )
2021-05-23 17:07:04 +01:00
}
requestedFollowing , err := p . federator . FederatingDB ( ) . Following ( context . Background ( ) , requestedAccountURI )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error fetching following for uri %s: %s" , requestedAccountURI . String ( ) , err ) )
2021-05-23 17:07:04 +01:00
}
data , err := streams . Serialize ( requestedFollowing )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-23 17:07:04 +01:00
}
return data , nil
}
2021-07-05 12:23:03 +01:00
func ( p * processor ) GetFediStatus ( ctx context . Context , requestedUsername string , requestedStatusID string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-21 22:04:59 +01:00
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-21 22:04:59 +01:00
}
// authenticate the request
2021-07-05 12:23:03 +01:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 10:46:07 +01:00
}
2021-08-25 14:34:33 +01:00
requestingAccount , _ , err := p . federator . GetRemoteAccount ( ctx , requestedUsername , requestingAccountURI , false )
2021-05-21 22:04:59 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-21 22:04:59 +01:00
}
2021-06-17 17:02:33 +01:00
// authorize the request:
// 1. check if a block exists between the requester and the requestee
2021-08-25 14:34:33 +01:00
blocked , err := p . db . IsBlocked ( ctx , requestedAccount . ID , requestingAccount . ID , true )
2021-05-21 22:04:59 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 22:04:59 +01:00
}
if blocked {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-21 22:04:59 +01:00
}
2021-06-17 17:02:33 +01:00
// get the status out of the database here
2021-05-21 22:04:59 +01:00
s := & gtsmodel . Status { }
2021-08-25 14:34:33 +01:00
if err := p . db . GetWhere ( ctx , [ ] db . Where {
2021-05-21 22:04:59 +01:00
{ Key : "id" , Value : requestedStatusID } ,
{ Key : "account_id" , Value : requestedAccount . ID } ,
} , s ) ; err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting status with id %s and account id %s: %s" , requestedStatusID , requestedAccount . ID , err ) )
2021-05-21 22:04:59 +01:00
}
2021-08-25 14:34:33 +01:00
visible , err := p . filter . StatusVisible ( ctx , s , requestingAccount )
2021-06-17 17:02:33 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if ! visible {
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "status with id %s not visible to user with id %s" , s . ID , requestingAccount . ID ) )
}
// requester is authorized to view the status, so convert it to AP representation and serialize it
2021-08-25 14:34:33 +01:00
asStatus , err := p . tc . StatusToAS ( ctx , s )
2021-05-21 22:04:59 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 22:04:59 +01:00
}
data , err := streams . Serialize ( asStatus )
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 22:04:59 +01:00
}
return data , nil
2021-05-21 14:48:26 +01:00
}
2021-08-10 12:32:39 +01:00
func ( p * processor ) GetFediStatusReplies ( ctx context . Context , requestedUsername string , requestedStatusID string , page bool , onlyOtherAccounts bool , minID string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-08-10 12:32:39 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
}
// authenticate the request
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
}
2021-08-25 14:34:33 +01:00
requestingAccount , _ , err := p . federator . GetRemoteAccount ( ctx , requestedUsername , requestingAccountURI , false )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorNotAuthorized ( err )
}
// authorize the request:
// 1. check if a block exists between the requester and the requestee
2021-08-25 14:34:33 +01:00
blocked , err := p . db . IsBlocked ( ctx , requestedAccount . ID , requestingAccount . ID , true )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if blocked {
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
}
// get the status out of the database here
s := & gtsmodel . Status { }
2021-08-25 14:34:33 +01:00
if err := p . db . GetWhere ( ctx , [ ] db . Where {
2021-08-10 12:32:39 +01:00
{ Key : "id" , Value : requestedStatusID } ,
{ Key : "account_id" , Value : requestedAccount . ID } ,
} , s ) ; err != nil {
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting status with id %s and account id %s: %s" , requestedStatusID , requestedAccount . ID , err ) )
}
2021-08-25 14:34:33 +01:00
visible , err := p . filter . StatusVisible ( ctx , s , requestingAccount )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if ! visible {
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "status with id %s not visible to user with id %s" , s . ID , requestingAccount . ID ) )
}
var data map [ string ] interface { }
// now there are three scenarios:
// 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page.
// 2. we're asked for a page but only_other_accounts has not been set in the query -- so we should just return the first page of the collection, with no items.
// 3. we're asked for a page, and only_other_accounts has been set, and min_id has optionally been set -- so we need to return some actual items!
if ! page {
// scenario 1
// get the collection
2021-08-25 14:34:33 +01:00
collection , err := p . tc . StatusToASRepliesCollection ( ctx , s , onlyOtherAccounts )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
data , err = streams . Serialize ( collection )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
} else if page && requestURL . Query ( ) . Get ( "only_other_accounts" ) == "" {
// scenario 2
// get the collection
2021-08-25 14:34:33 +01:00
collection , err := p . tc . StatusToASRepliesCollection ( ctx , s , onlyOtherAccounts )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
// but only return the first page
data , err = streams . Serialize ( collection . GetActivityStreamsFirst ( ) . GetActivityStreamsCollectionPage ( ) )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
} else {
// scenario 3
// get immediate children
2021-08-25 14:34:33 +01:00
replies , err := p . db . GetStatusChildren ( ctx , s , true , minID )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
// filter children and extract URIs
replyURIs := map [ string ] * url . URL { }
for _ , r := range replies {
// only show public or unlocked statuses as replies
if r . Visibility != gtsmodel . VisibilityPublic && r . Visibility != gtsmodel . VisibilityUnlocked {
continue
}
// respect onlyOtherAccounts parameter
if onlyOtherAccounts && r . AccountID == requestedAccount . ID {
continue
}
// only show replies that the status owner can see
2021-08-25 14:34:33 +01:00
visibleToStatusOwner , err := p . filter . StatusVisible ( ctx , r , requestedAccount )
2021-08-10 12:32:39 +01:00
if err != nil || ! visibleToStatusOwner {
continue
}
// only show replies that the requester can see
2021-08-25 14:34:33 +01:00
visibleToRequester , err := p . filter . StatusVisible ( ctx , r , requestingAccount )
2021-08-10 12:32:39 +01:00
if err != nil || ! visibleToRequester {
continue
}
rURI , err := url . Parse ( r . URI )
if err != nil {
continue
}
replyURIs [ r . ID ] = rURI
}
2021-08-25 14:34:33 +01:00
repliesPage , err := p . tc . StatusURIsToASRepliesPage ( ctx , s , onlyOtherAccounts , minID , replyURIs )
2021-08-10 12:32:39 +01:00
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
data , err = streams . Serialize ( repliesPage )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
}
return data , nil
}
2021-07-05 12:23:03 +01:00
func ( p * processor ) GetWebfingerAccount ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( * apimodel . WellKnownResponse , gtserror . WithCode ) {
2021-05-09 19:34:27 +01:00
// get the account the request is referring to
2021-08-25 14:34:33 +01:00
requestedAccount , err := p . db . GetLocalAccountByUsername ( ctx , requestedUsername )
2021-08-20 11:26:56 +01:00
if err != nil {
2021-06-13 17:42:28 +01:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-09 19:34:27 +01:00
}
// return the webfinger representation
2021-06-24 13:26:08 +01:00
return & apimodel . WellKnownResponse {
2021-07-19 17:42:08 +01:00
Subject : fmt . Sprintf ( "acct:%s@%s" , requestedAccount . Username , p . config . AccountDomain ) ,
2021-05-09 19:34:27 +01:00
Aliases : [ ] string {
requestedAccount . URI ,
requestedAccount . URL ,
} ,
2021-06-24 13:26:08 +01:00
Links : [ ] apimodel . Link {
2021-05-09 19:34:27 +01:00
{
Rel : "http://webfinger.net/rel/profile-page" ,
Type : "text/html" ,
Href : requestedAccount . URL ,
} ,
{
Rel : "self" ,
Type : "application/activity+json" ,
Href : requestedAccount . URI ,
} ,
} ,
} , nil
}
2021-05-15 10:58:11 +01:00
2021-08-25 14:34:33 +01:00
func ( p * processor ) GetNodeInfoRel ( ctx context . Context , request * http . Request ) ( * apimodel . WellKnownResponse , gtserror . WithCode ) {
2021-06-24 13:26:08 +01:00
return & apimodel . WellKnownResponse {
Links : [ ] apimodel . Link {
{
Rel : "http://nodeinfo.diaspora.software/ns/schema/2.0" ,
Href : fmt . Sprintf ( "%s://%s/nodeinfo/2.0" , p . config . Protocol , p . config . Host ) ,
} ,
} ,
} , nil
}
2021-08-25 14:34:33 +01:00
func ( p * processor ) GetNodeInfo ( ctx context . Context , request * http . Request ) ( * apimodel . Nodeinfo , gtserror . WithCode ) {
2021-06-24 13:26:08 +01:00
return & apimodel . Nodeinfo {
Version : "2.0" ,
Software : apimodel . NodeInfoSoftware {
Name : "gotosocial" ,
Version : p . config . SoftwareVersion ,
} ,
Protocols : [ ] string { "activitypub" } ,
Services : apimodel . NodeInfoServices {
Inbound : [ ] string { } ,
Outbound : [ ] string { } ,
} ,
OpenRegistrations : p . config . AccountsConfig . OpenRegistration ,
Usage : apimodel . NodeInfoUsage {
Users : apimodel . NodeInfoUsers { } ,
} ,
Metadata : make ( map [ string ] interface { } ) ,
} , nil
}
2021-05-15 10:58:11 +01:00
func ( p * processor ) InboxPost ( ctx context . Context , w http . ResponseWriter , r * http . Request ) ( bool , error ) {
contextWithChannel := context . WithValue ( ctx , util . APFromFederatorChanKey , p . fromFederator )
2021-07-05 12:23:03 +01:00
return p . federator . FederatingActor ( ) . PostInbox ( contextWithChannel , w , r )
2021-05-15 10:58:11 +01:00
}