kopia lustrzana https://github.com/snarfed/bridgy-fed
delete XRPC method handlers, they're unused
rodzic
ceb855f5f8
commit
f03b97e44a
|
@ -23,7 +23,6 @@ jobs:
|
|||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y apt-transport-https ca-certificates gnupg google-cloud-sdk google-cloud-sdk-datastore-emulator default-jre
|
||||
git clone --depth=1 https://github.com/bluesky-social/atproto.git ../atproto
|
||||
|
||||
- run:
|
||||
name: Python dependencies
|
||||
|
|
2
app.py
2
app.py
|
@ -6,7 +6,7 @@ registered.
|
|||
from flask_app import app
|
||||
|
||||
# import all modules to register their Flask handlers
|
||||
import activitypub, convert, follow, pages, redirect, superfeedr, ui, webfinger, web, xrpc_actor, xrpc_feed, xrpc_graph
|
||||
import activitypub, convert, follow, pages, redirect, superfeedr, ui, webfinger, web
|
||||
|
||||
import models
|
||||
models.reset_protocol_properties()
|
||||
|
|
|
@ -9,14 +9,13 @@ TODO
|
|||
"""
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from arroba import did
|
||||
from arroba.datastore_storage import DatastoreStorage
|
||||
from arroba.repo import Repo, Write
|
||||
from arroba.storage import Action
|
||||
from arroba.util import next_tid, new_key, parse_at_uri
|
||||
from arroba.util import lexicons, next_tid, new_key, parse_at_uri
|
||||
from flask import abort, g, request
|
||||
from google.cloud import ndb
|
||||
from granary import as1, bluesky
|
||||
|
@ -38,13 +37,9 @@ from protocol import Protocol
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
lexicons = []
|
||||
for filename in (Path(__file__).parent / 'lexicons').glob('**/*.json'):
|
||||
with open(filename) as f:
|
||||
lexicons.append(json.load(f))
|
||||
|
||||
storage = DatastoreStorage()
|
||||
|
||||
|
||||
class ATProto(User, Protocol):
|
||||
"""AT Protocol class.
|
||||
|
||||
|
|
|
@ -9,10 +9,18 @@ activitypub
|
|||
-----------
|
||||
.. automodule:: activitypub
|
||||
|
||||
atproto
|
||||
-------
|
||||
.. automodule:: atproto
|
||||
|
||||
common
|
||||
------
|
||||
.. automodule:: common
|
||||
|
||||
convert
|
||||
-------
|
||||
.. automodule:: convert
|
||||
|
||||
follow
|
||||
------
|
||||
.. automodule:: follow
|
||||
|
@ -41,23 +49,10 @@ superfeedr
|
|||
----------
|
||||
.. automodule:: superfeedr
|
||||
|
||||
web
|
||||
---
|
||||
.. automodule:: web
|
||||
|
||||
webfinger
|
||||
---------
|
||||
.. automodule:: webfinger
|
||||
|
||||
webmention
|
||||
----------
|
||||
.. automodule:: webmention
|
||||
|
||||
xrpc_actor
|
||||
----------
|
||||
.. automodule:: xrpc
|
||||
|
||||
xrpc_feed
|
||||
---------
|
||||
.. automodule:: xrpc
|
||||
|
||||
xrpc_graph
|
||||
----------
|
||||
.. automodule:: xrpc
|
||||
|
||||
|
|
|
@ -59,12 +59,3 @@ app.wsgi_app = flask_util.ndb_context_middleware(
|
|||
cache = Cache(app)
|
||||
|
||||
util.set_user_agent(USER_AGENT)
|
||||
|
||||
# XRPC server
|
||||
lexicons = []
|
||||
for filename in (app_dir / 'lexicons').glob('**/*.json'):
|
||||
with open(filename) as f:
|
||||
lexicons.append(json.load(f))
|
||||
|
||||
xrpc_server = Server(lexicons, validate=False)
|
||||
init_flask(xrpc_server, app)
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
"""Unit tests for actor.py."""
|
||||
from unittest import skip
|
||||
|
||||
# import first so that Fake is defined before URL routes are registered
|
||||
from . import testutil
|
||||
|
||||
from .test_activitypub import ACTOR
|
||||
|
||||
|
||||
@skip
|
||||
class XrpcActorTest(testutil.TestCase):
|
||||
|
||||
def test_getProfile(self):
|
||||
actor = {
|
||||
**ACTOR,
|
||||
'summary': "I'm a person",
|
||||
'image': [{'type': 'Image', 'url': 'http://user.com/header.png'}],
|
||||
}
|
||||
self.make_user('user.com', has_hcard=True, actor_as2=actor)
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||
query_string={'actor': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'$type': 'app.bsky.actor.defs#profileView',
|
||||
'handle': 'mas.to/users/swentel',
|
||||
'did': 'did:web:mas.to:users:swentel',
|
||||
'displayName': 'Mrs. ☕ Foo',
|
||||
'description': "I'm a person",
|
||||
'avatar': 'https://user.com/me.jpg',
|
||||
'banner': 'http://user.com/header.png',
|
||||
}, resp.json)
|
||||
|
||||
def test_getProfile_unset(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.getProfile')
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getProfile_not_domain(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||
query_string={'actor': 'not a domain'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getProfile_no_user(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||
query_string={'actor': 'user.com'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getSuggestions(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.getSuggestions')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'actors': [],
|
||||
}, resp.json)
|
||||
|
||||
def test_search(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.searchActors',
|
||||
query_string={'term': 'foo'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'actors': [],
|
||||
}, resp.json)
|
||||
|
||||
def test_searchTypeahead(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.actor.searchActorsTypeahead',
|
||||
query_string={'term': 'foo'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'actors': [],
|
||||
}, resp.json)
|
|
@ -1,179 +0,0 @@
|
|||
"""Unit tests for feed.py."""
|
||||
from unittest import skip
|
||||
|
||||
from granary import as2, bluesky
|
||||
from granary.tests.test_as1 import COMMENT, NOTE
|
||||
from granary.tests.test_bluesky import (
|
||||
POST_BSKY,
|
||||
POST_AS,
|
||||
REPLY_BSKY,
|
||||
REPLY_AS,
|
||||
REPOST_AS,
|
||||
)
|
||||
|
||||
# import first so that Fake is defined before URL routes are registered
|
||||
from . import testutil
|
||||
|
||||
from models import Object
|
||||
from .test_activitypub import ACTOR
|
||||
|
||||
POST_THREAD_AS = {
|
||||
**POST_AS,
|
||||
'replies': {
|
||||
'items': [{
|
||||
'objectType': 'comment',
|
||||
'id': 'http://bob.org/reply',
|
||||
'content': 'Uh huh',
|
||||
'author': {
|
||||
'objectType': 'person',
|
||||
'displayName': 'Bob',
|
||||
'url': 'http://bob.org/',
|
||||
},
|
||||
}],
|
||||
},
|
||||
}
|
||||
POST_THREAD_BSKY = {
|
||||
'thread': {
|
||||
'$type': 'app.bsky.feed.defs#threadViewPost',
|
||||
'post': POST_BSKY['post'],
|
||||
'replies': [{
|
||||
'$type': 'app.bsky.feed.defs#threadViewPost',
|
||||
'post': {
|
||||
'$type': 'app.bsky.feed.defs#postView',
|
||||
'uri': 'http://bob.org/reply',
|
||||
'cid': 'TODO',
|
||||
'record': {
|
||||
'$type': 'app.bsky.feed.post',
|
||||
'text': 'Uh huh',
|
||||
'createdAt': '',
|
||||
},
|
||||
'author': {
|
||||
'$type': 'app.bsky.actor.defs#profileViewBasic',
|
||||
'did': 'did:web:bob.org',
|
||||
'displayName': 'Bob',
|
||||
'handle': 'bob.org',
|
||||
'description': None,
|
||||
},
|
||||
'replyCount': 0,
|
||||
'repostCount': 0,
|
||||
'upvoteCount': 0,
|
||||
'downvoteCount': 0,
|
||||
'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||
},
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@skip
|
||||
class XrpcFeedTest(testutil.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.make_user('user.com', has_hcard=True, actor_as2=ACTOR)
|
||||
|
||||
def test_getAuthorFeed(self):
|
||||
post_as2 = as2.from_as1(POST_AS)
|
||||
|
||||
Object(id='a', domains=['user.com'], labels=['user'], as2=post_as2).put()
|
||||
Object(id='b', domains=['user.com'], labels=['user'],
|
||||
as2=as2.from_as1(REPLY_AS)).put()
|
||||
# not outbound from user
|
||||
Object(id='d', domains=['user.com'], labels=['feed'], as2=post_as2).put()
|
||||
# deleted
|
||||
Object(id='e', domains=['user.com'], labels=['user'], as2=post_as2,
|
||||
deleted=True).put()
|
||||
# other user's
|
||||
Object(id='f', domains=['bar.org'], labels=['user'], as2=post_as2).put()
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||
query_string={'author': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code, resp.get_data(as_text=True))
|
||||
self.assert_equals({
|
||||
'feed': [REPLY_BSKY, POST_BSKY],
|
||||
}, resp.json, ignore=['did'])
|
||||
|
||||
def test_getAuthorFeed_no_author_param(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed')
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getAuthorFeed_not_domain(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||
query_string={'author': 'not a domain'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getAuthorFeed_no_user(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||
query_string={'author': 'no.com'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getAuthorFeed_no_objects(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||
query_string={'author': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assert_equals({'feed': []}, resp.json)
|
||||
|
||||
def test_getPostThread(self):
|
||||
Object(id='http://a/post', domains=['user.com'], labels=['user'],
|
||||
as2=as2.from_as1(POST_THREAD_AS)).put()
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getPostThread',
|
||||
query_string={'uri': 'http://a/post'})
|
||||
self.assertEqual(200, resp.status_code, resp.get_data(as_text=True))
|
||||
self.assertEqual(POST_THREAD_BSKY, resp.json)
|
||||
|
||||
def test_getPostThread_no_uri_param(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getPostThread')
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getPostThread_no_post(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getPostThread',
|
||||
query_string={'uri': 'http://no/post'})
|
||||
self.assertEqual(400, resp.status_code, resp.get_data(as_text=True))
|
||||
|
||||
def test_getRepostedBy(self):
|
||||
Object(id='repost/1', domains=['user.com'], as2=as2.from_as1({
|
||||
**REPOST_AS,
|
||||
'object': 'http://a/post',
|
||||
})).put()
|
||||
Object(id='repost/2', domains=['user.com'], as2=as2.from_as1({
|
||||
**REPOST_AS,
|
||||
'object': 'http://a/post',
|
||||
'actor': as2.to_as1(ACTOR),
|
||||
})).put()
|
||||
|
||||
got = self.client.get('/xrpc/app.bsky.feed.getRepostedBy',
|
||||
query_string={'uri': 'http://a/post'})
|
||||
self.assertEqual({
|
||||
'uri': 'http://orig/post',
|
||||
'repostBy': [{
|
||||
'$type': 'app.bsky.feed.getRepostedBy#repostedBy',
|
||||
'description': None,
|
||||
'did': 'did:web:mas.to:users:swentel',
|
||||
'handle': 'mas.to/users/swentel',
|
||||
'displayName': 'Mrs. ☕ Foo',
|
||||
'avatar': 'https://user.com/me.jpg',
|
||||
}, {
|
||||
'$type': 'app.bsky.feed.getRepostedBy#repostedBy',
|
||||
'description': None,
|
||||
'did': 'did:web:bsky.app:profile:bob.com',
|
||||
'handle': 'bsky.app/profile/bob.com',
|
||||
'displayName': 'Bob',
|
||||
}],
|
||||
}, got.json)
|
||||
|
||||
def test_getTimeline(self):
|
||||
self.add_objects()
|
||||
|
||||
got = self.client.get('/xrpc/app.bsky.feed.getTimeline')
|
||||
self.assertEqual({
|
||||
'feed': [bluesky.from_as1(COMMENT), bluesky.from_as1(NOTE)],
|
||||
}, got.json)
|
||||
|
||||
def test_getLikes(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.feed.getLikes',
|
||||
query_string={'uri': 'http://a/post'})
|
||||
self.assertEqual({
|
||||
'uri': 'http://a/post',
|
||||
'likes': [],
|
||||
}, resp.json)
|
|
@ -1,134 +0,0 @@
|
|||
"""Unit tests for graph.py."""
|
||||
# import first so that Fake is defined before URL routes are registered
|
||||
from . import testutil
|
||||
|
||||
from .test_activitypub import FOLLOW, FOLLOW_WITH_ACTOR, FOLLOW_WITH_OBJECT
|
||||
from models import Follower
|
||||
from unittest import skip
|
||||
|
||||
SUBJECT = {
|
||||
'$type': 'app.bsky.actor.defs#profileView',
|
||||
'did': 'did:web:user.com',
|
||||
'handle': 'user.com',
|
||||
'description': None,
|
||||
}
|
||||
FOLLOWERS_BSKY = [{
|
||||
'$type': 'app.bsky.graph.getFollowers#follower',
|
||||
'did': 'did:web:other',
|
||||
'handle': 'yoozer@other',
|
||||
'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||
'description': None,
|
||||
}, {
|
||||
'$type': 'app.bsky.graph.getFollowers#follower',
|
||||
'did': 'did:web:mas.to:users:swentel',
|
||||
'handle': 'mas.to/users/swentel',
|
||||
'displayName': 'Mrs. ☕ Foo',
|
||||
'avatar': 'https://user.com/me.jpg',
|
||||
'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||
'description': None,
|
||||
}]
|
||||
|
||||
|
||||
@skip
|
||||
class XrpcGraphTest(testutil.TestCase):
|
||||
|
||||
def test_getProfile_no_user(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollowers')
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getFollowers_not_domain(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollowers',
|
||||
query_string={'user': 'not a domain'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getFollowers_no_user(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollowers',
|
||||
query_string={'user': 'no.com'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getFollowers_empty(self):
|
||||
self.make_user('user.com')
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollowers',
|
||||
query_string={'user': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'subject': SUBJECT,
|
||||
'cursor': '',
|
||||
'followers': [],
|
||||
}, resp.json)
|
||||
|
||||
def test_getFollowers(self):
|
||||
self.make_user('user.com')
|
||||
|
||||
other_follow = {
|
||||
**FOLLOW,
|
||||
'actor': {
|
||||
'type': 'Person',
|
||||
'url': 'http://other',
|
||||
'preferredUsername': 'yoozer',
|
||||
},
|
||||
}
|
||||
|
||||
Follower.get_or_create('user.com', 'https://no/stored/follow')
|
||||
Follower.get_or_create('user.com', 'https://masto/user',
|
||||
last_follow=FOLLOW_WITH_ACTOR)
|
||||
Follower.get_or_create('user.com', 'http://other',
|
||||
last_follow=other_follow)
|
||||
Follower.get_or_create('nope.com', 'http://nope',
|
||||
last_follow=other_follow)
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollowers',
|
||||
query_string={'user': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'subject': SUBJECT,
|
||||
'cursor': '',
|
||||
'followers': FOLLOWERS_BSKY,
|
||||
}, resp.json)
|
||||
|
||||
def test_getFollows_not_domain(self):
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollows',
|
||||
query_string={'user': 'not a domain'})
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_getFollows_empty(self):
|
||||
self.make_user('user.com')
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollows',
|
||||
query_string={'user': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'subject': SUBJECT,
|
||||
'cursor': '',
|
||||
'follows': [],
|
||||
}, resp.json)
|
||||
|
||||
def test_getFollows(self):
|
||||
self.make_user('user.com')
|
||||
|
||||
other_follow = {
|
||||
**FOLLOW,
|
||||
'object': {
|
||||
'type': 'Person',
|
||||
'url': 'http://other',
|
||||
'preferredUsername': 'yoozer',
|
||||
},
|
||||
}
|
||||
|
||||
Follower.get_or_create('https://no/stored/follow', 'user.com')
|
||||
Follower.get_or_create('https://masto/user', 'user.com',
|
||||
last_follow=FOLLOW_WITH_OBJECT)
|
||||
Follower.get_or_create('http://other', 'user.com',
|
||||
last_follow=other_follow)
|
||||
Follower.get_or_create('http://nope', 'nope.com',
|
||||
last_follow=other_follow)
|
||||
|
||||
resp = self.client.get('/xrpc/app.bsky.graph.getFollows',
|
||||
query_string={'user': 'user.com'})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({
|
||||
'subject': SUBJECT,
|
||||
'cursor': '',
|
||||
'follows': FOLLOWERS_BSKY,
|
||||
}, resp.json)
|
|
@ -1,64 +0,0 @@
|
|||
"""app.bsky.actor.* XRPC methods."""
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
|
||||
from flask import g
|
||||
from granary import bluesky
|
||||
from oauth_dropins.webutil import util
|
||||
|
||||
from flask_app import xrpc_server
|
||||
from web import Web
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.actor.getProfile')
|
||||
def getProfile(input, actor=None):
|
||||
"""
|
||||
lexicons/app/bsky/actor/getProfile.json
|
||||
"""
|
||||
# TODO: actor is either handle or DID
|
||||
# see actorWhereClause in atproto/packages/pds/src/db/util.ts
|
||||
if not actor or not re.match(util.DOMAIN_RE, actor):
|
||||
raise ValueError(f'{actor} is not a domain')
|
||||
|
||||
g.user = Web.get_by_id(actor)
|
||||
if not g.user:
|
||||
raise ValueError(f'User {actor} not found')
|
||||
elif not g.user.obj.as1:
|
||||
return ValueError(f'User {actor} not fully set up')
|
||||
|
||||
actor_as1 = g.user.obj.as1
|
||||
logger.info(f'AS1 actor: {json.dumps(actor_as1, indent=2)}')
|
||||
|
||||
profile = bluesky.from_as1(actor_as1)
|
||||
logger.info(f'Bluesky profile: {json.dumps(profile, indent=2)}')
|
||||
return profile
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.actor.getSuggestions')
|
||||
def getSuggestions(input):
|
||||
"""
|
||||
lexicons/app/bsky/actor/getSuggestions.json
|
||||
"""
|
||||
# TODO based on stored users
|
||||
return {'actors': []}
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.actor.searchActors')
|
||||
def searchActors(input, term=None, limit=None, before=None):
|
||||
"""
|
||||
lexicons/app/bsky/actor/searchActors.json
|
||||
"""
|
||||
# TODO based on stored users
|
||||
return {'actors': []}
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.actor.searchActorsTypeahead')
|
||||
def searchActorsTypeahead(input, term=None, limit=None):
|
||||
"""
|
||||
lexicons/app/bsky/actor/searchActorsTypeahead.json
|
||||
"""
|
||||
# TODO based on stored users
|
||||
return {'actors': []}
|
120
xrpc_feed.py
120
xrpc_feed.py
|
@ -1,120 +0,0 @@
|
|||
"""app.bsky.feed.* XRPC methods."""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
from flask import g
|
||||
from granary import bluesky
|
||||
from oauth_dropins.webutil import util
|
||||
|
||||
from flask_app import xrpc_server
|
||||
from models import Object, PAGE_SIZE
|
||||
from web import Web
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.feed.getAuthorFeed')
|
||||
def getAuthorFeed(input, author=None, limit=None, before=None):
|
||||
"""
|
||||
lexicons/app/bsky/feed/getAuthorFeed.json, feedViewPost.json
|
||||
"""
|
||||
if not author or not re.match(util.DOMAIN_RE, author):
|
||||
raise ValueError(f'{author} is not a domain')
|
||||
|
||||
g.user = Web.get_by_id(author)
|
||||
if not g.user:
|
||||
raise ValueError(f'User {author} not found')
|
||||
elif not g.user.obj.as1:
|
||||
return ValueError(f'User {author} not fully set up')
|
||||
|
||||
# TODO: unify with pages.feed?
|
||||
limit = min(limit or PAGE_SIZE, PAGE_SIZE)
|
||||
objects, _, _ = Object.query(Object.domains == author, Object.labels == 'user') \
|
||||
.order(-Object.created) \
|
||||
.fetch_page(limit)
|
||||
activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||
logger.info(f'AS1 activities: {json.dumps(activities, indent=2)}')
|
||||
|
||||
return {'feed': [bluesky.from_as1(a) for a in activities]}
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.feed.getPostThread')
|
||||
def getPostThread(input, uri=None, depth=None):
|
||||
"""
|
||||
lexicons/app/bsky/feed/getPostThread.json
|
||||
"""
|
||||
if not uri:
|
||||
raise ValueError('Missing uri')
|
||||
|
||||
obj = Object.get_by_id(uri)
|
||||
if not obj:
|
||||
raise ValueError(f'{uri} not found')
|
||||
|
||||
logger.info(f'AS1: {json.dumps(obj.as1, indent=2)}')
|
||||
|
||||
return {
|
||||
'thread': {
|
||||
'$type': 'app.bsky.feed.defs#threadViewPost',
|
||||
'post': bluesky.from_as1(obj.as1)['post'],
|
||||
'replies': [{
|
||||
'$type': 'app.bsky.feed.defs#threadViewPost',
|
||||
'post': bluesky.from_as1(reply)['post'],
|
||||
} for reply in obj.as1.get('replies', {}).get('items', [])],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.feed.getRepostedBy')
|
||||
def getRepostedBy(input, uri=None, cid=None, limit=None, before=None):
|
||||
"""
|
||||
TODO: implement before, as query filter. what's input type? str or datetime?
|
||||
lexicons/app/bsky/feed/getRepostedBy.json
|
||||
"""
|
||||
if not uri:
|
||||
raise ValueError('Missing uri')
|
||||
|
||||
limit = min(limit or PAGE_SIZE, PAGE_SIZE)
|
||||
objects, _, _ = Object.query(Object.object_ids == uri) \
|
||||
.order(-Object.created) \
|
||||
.fetch_page(limit)
|
||||
activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||
logger.info(f'AS1 activities: {json.dumps(activities, indent=2)}')
|
||||
|
||||
return {
|
||||
'uri': 'http://orig/post',
|
||||
'repostBy': [{
|
||||
**bluesky.from_as1(a['actor']),
|
||||
'$type': 'app.bsky.feed.getRepostedBy#repostedBy',
|
||||
} for a in activities if a.get('actor')],
|
||||
}
|
||||
|
||||
|
||||
# TODO: cursor
|
||||
@xrpc_server.method('app.bsky.feed.getTimeline')
|
||||
def getTimeline(input, algorithm=None, limit=50, before=None):
|
||||
"""
|
||||
lexicons/app/bsky/feed/getTimeline.json
|
||||
"""
|
||||
# TODO: how to get authed user?
|
||||
domain = 'user.com'
|
||||
|
||||
# TODO: de-dupe with pages.feed()
|
||||
logger.info(f'Fetching {limit} objects for {domain}')
|
||||
objects, _, _ = Object.query(Object.domains == domain, Object.labels == 'feed') \
|
||||
.order(-Object.created) \
|
||||
.fetch_page(limit)
|
||||
|
||||
return {'feed': [bluesky.from_as1(obj.as1) for obj in objects if not obj.deleted]}
|
||||
|
||||
|
||||
# TODO
|
||||
@xrpc_server.method('app.bsky.feed.getLikes')
|
||||
def getLikes(input, uri=None, direction=None, cid=None, limit=None, before=None):
|
||||
"""
|
||||
lexicons/app/bsky/feed/getLikes.json
|
||||
"""
|
||||
return {
|
||||
'uri': uri,
|
||||
'likes': [],
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
"""app.bsky.graph.* XRPC methods."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from granary import bluesky
|
||||
from oauth_dropins.webutil import util
|
||||
|
||||
from flask_app import xrpc_server
|
||||
from models import Follower
|
||||
from web import Web
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_followers(query_prop, output_field, user=None, limit=50, before=None):
|
||||
"""Runs the getFollowers or getFollows method. (They're almost identical.)
|
||||
|
||||
Args:
|
||||
query_prop: str, property of Follower class to query
|
||||
output_field: str, field in output to populate followers into
|
||||
|
||||
Returns:
|
||||
dict, XRPC method output
|
||||
"""
|
||||
# TODO: what is user?
|
||||
if not user or not re.match(util.DOMAIN_RE, user):
|
||||
raise ValueError(f'{user} is not a domain')
|
||||
elif not Web.get_by_id(user):
|
||||
raise ValueError(f'Unknown user {user}')
|
||||
|
||||
collection = 'followers' if output_field == 'followers' else 'following'
|
||||
followers, before, after = Follower.fetch_page(user, collection)
|
||||
|
||||
actors = []
|
||||
for follower in followers:
|
||||
actor = follower.to_as1()
|
||||
if actor:
|
||||
actors.append({
|
||||
**bluesky.from_as1(actor),
|
||||
'$type': 'app.bsky.graph.getFollowers#follower',
|
||||
'indexedAt': util.now().isoformat(),
|
||||
})
|
||||
|
||||
return {
|
||||
'subject': bluesky.from_as1({
|
||||
'objectType': 'person',
|
||||
'url': f'https://{user}/',
|
||||
}),
|
||||
output_field: actors,
|
||||
'cursor': '',
|
||||
}
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.graph.getFollowers')
|
||||
def getFollowers(input, **kwargs):
|
||||
"""
|
||||
lexicons/app/bsky/graph/getFollowers.json
|
||||
"""
|
||||
return get_followers(Follower.dest, 'followers', **kwargs)
|
||||
|
||||
|
||||
@xrpc_server.method('app.bsky.graph.getFollows')
|
||||
def getFollows(input, **kwargs):
|
||||
"""
|
||||
lexicons/app/bsky/graph/getFollows.json
|
||||
"""
|
||||
return get_followers(Follower.src, 'follows', **kwargs)
|
Ładowanie…
Reference in New Issue