kopia lustrzana https://github.com/snarfed/bridgy-fed
Merge snarfed/bridgy-at repo into bridgy-fed
Dropped most files, only kept XRPC implementations and tests.pull/368/head
commit
2cca37af1d
|
@ -0,0 +1 @@
|
||||||
|
../atproto/lexicons
|
|
@ -0,0 +1,77 @@
|
||||||
|
"""Unit tests for actor.py."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from . import testutil
|
||||||
|
|
||||||
|
|
||||||
|
@patch('requests.get')
|
||||||
|
class XrpcActorTest(testutil.TestCase):
|
||||||
|
|
||||||
|
def test_getProfile(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response("""
|
||||||
|
<body class="h-card">
|
||||||
|
<a class="u-url p-name" rel="me" href="/about-me">Mrs. ☕ Foo</a>
|
||||||
|
<img class="u-photo" src="/me.jpg" />
|
||||||
|
<img class="u-featured" src="/header.png" />
|
||||||
|
<span class="u-summary">I'm a person</span>
|
||||||
|
</body>
|
||||||
|
""", url='https://foo.com/')
|
||||||
|
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||||
|
query_string={'actor': 'foo.com'})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({
|
||||||
|
'$type': 'app.bsky.actor.profile',
|
||||||
|
'handle': 'foo.com',
|
||||||
|
'did': 'did:web:foo.com:about-me',
|
||||||
|
'creator': 'did:web:foo.com:about-me',
|
||||||
|
'displayName': 'Mrs. ☕ Foo',
|
||||||
|
'declaration': {
|
||||||
|
'$type': 'app.bsky.system.declRef',
|
||||||
|
'cid': 'TODO',
|
||||||
|
'actorType': 'app.bsky.system.actorUser',
|
||||||
|
},
|
||||||
|
'description': "I'm a person",
|
||||||
|
'avatar': 'https://foo.com/me.jpg',
|
||||||
|
'banner': 'https://foo.com/header.png',
|
||||||
|
'followersCount': 0,
|
||||||
|
'followsCount': 0,
|
||||||
|
'membersCount': 0,
|
||||||
|
'postsCount': 0,
|
||||||
|
'myState': {
|
||||||
|
'follow': 'TODO',
|
||||||
|
'member': 'TODO',
|
||||||
|
},
|
||||||
|
}, resp.json)
|
||||||
|
|
||||||
|
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_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.search',
|
||||||
|
query_string={'term': 'foo'})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({
|
||||||
|
'users': [],
|
||||||
|
}, resp.json)
|
||||||
|
|
||||||
|
def test_searchTypeahead(self, _):
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.actor.searchTypeahead',
|
||||||
|
query_string={'term': 'foo'})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({
|
||||||
|
'users': [],
|
||||||
|
}, resp.json)
|
|
@ -0,0 +1,180 @@
|
||||||
|
"""Unit tests for feed.py."""
|
||||||
|
import copy
|
||||||
|
from unittest import skip
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from granary.tests.test_bluesky import (
|
||||||
|
ACTOR_AS,
|
||||||
|
ACTOR_REF_BSKY,
|
||||||
|
POST_BSKY,
|
||||||
|
POST_HTML,
|
||||||
|
REPLY_BSKY,
|
||||||
|
REPLY_HTML,
|
||||||
|
REPOST_BSKY,
|
||||||
|
REPOST_HTML,
|
||||||
|
)
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
|
import requests
|
||||||
|
from werkzeug.exceptions import BadGateway
|
||||||
|
|
||||||
|
from . import testutil
|
||||||
|
|
||||||
|
AUTHOR_HTML = """
|
||||||
|
<a href="/" class="u-author h-card">
|
||||||
|
<img src="/alice.jpg"> Alice
|
||||||
|
</a>
|
||||||
|
"""
|
||||||
|
|
||||||
|
POST_THREAD_HTML = copy.deepcopy(POST_HTML).replace('</article>', """
|
||||||
|
<div class="u-comment h-cite">
|
||||||
|
<a class="u-author h-card" href="http://bob.org/">Bob</a>
|
||||||
|
<a class="u-url" href="http://bob.org/reply"></a>
|
||||||
|
<p class="p-content">Uh huh</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="u-repost h-cite">
|
||||||
|
<a class="u-author h-card" href="http://eve.net/">Eve</a>
|
||||||
|
<p class="p-content">This</p>
|
||||||
|
<a class="u-repost-of" href="http://orig/post"></a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
""")
|
||||||
|
POST_THREAD_BSKY = {
|
||||||
|
'thread': {
|
||||||
|
'$type': 'app.bsky.feed.getPostThread#threadViewPost',
|
||||||
|
'post': POST_BSKY['post'],
|
||||||
|
'replies': [{
|
||||||
|
'$type': 'app.bsky.feed.getPostThread#threadViewPost',
|
||||||
|
'post': {
|
||||||
|
'$type': 'app.bsky.feed.post#view',
|
||||||
|
'uri': 'http://bob.org/reply',
|
||||||
|
'cid': 'TODO',
|
||||||
|
'record': {
|
||||||
|
'$type': 'app.bsky.feed.post',
|
||||||
|
'text': 'Uh huh',
|
||||||
|
'createdAt': '',
|
||||||
|
},
|
||||||
|
'author': {
|
||||||
|
'$type': 'app.bsky.actor.ref#withInfo',
|
||||||
|
'did': 'did:web:bob.org',
|
||||||
|
'displayName': 'Bob',
|
||||||
|
'handle': 'bob.org',
|
||||||
|
'declaration': {
|
||||||
|
'$type': 'app.bsky.system.declRef',
|
||||||
|
'actorType': 'app.bsky.system.actorUser',
|
||||||
|
'cid': 'TODO',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'replyCount': 0,
|
||||||
|
'repostCount': 0,
|
||||||
|
'upvoteCount': 0,
|
||||||
|
'downvoteCount': 0,
|
||||||
|
'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||||
|
'viewer': {},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@patch('requests.get')
|
||||||
|
class XrpcFeedTest(testutil.TestCase):
|
||||||
|
|
||||||
|
def test_getAuthorFeed(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(f"""\
|
||||||
|
<body class="h-feed">
|
||||||
|
{AUTHOR_HTML}
|
||||||
|
{POST_HTML}
|
||||||
|
{REPLY_HTML}
|
||||||
|
{REPOST_HTML}
|
||||||
|
</body>
|
||||||
|
""", url='https://alice.com/')
|
||||||
|
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||||
|
query_string={'author': 'alice.com'})
|
||||||
|
self.assertEqual(200, resp.status_code, resp.get_data(as_text=True))
|
||||||
|
self.assertEqual({
|
||||||
|
'feed': [POST_BSKY, REPLY_BSKY, REPOST_BSKY],
|
||||||
|
}, resp.json)
|
||||||
|
|
||||||
|
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_fetch_fails(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(status=500)
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||||
|
query_string={'author': 'alice.com'})
|
||||||
|
self.assertEqual(502, resp.status_code)
|
||||||
|
|
||||||
|
def test_getAuthorFeed_no_feed(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(AUTHOR_HTML)
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getAuthorFeed',
|
||||||
|
query_string={'author': 'alice.com'})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({'feed': []}, resp.json)
|
||||||
|
|
||||||
|
def test_getPostThread(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(
|
||||||
|
POST_THREAD_HTML, url='https://alice.com/')
|
||||||
|
|
||||||
|
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.assert_equals(POST_THREAD_BSKY, resp.json)
|
||||||
|
|
||||||
|
|
||||||
|
def test_getPostThread_fetch_fails(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(status=500)
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getPostThread',
|
||||||
|
query_string={'uri': 'http://a/post'})
|
||||||
|
self.assertEqual(502, resp.status_code)
|
||||||
|
|
||||||
|
def test_getAuthorFeed_no_post(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(AUTHOR_HTML)
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getPostThread',
|
||||||
|
query_string={'uri': 'http://a/post'})
|
||||||
|
self.assertEqual(400, resp.status_code, resp.get_data(as_text=True))
|
||||||
|
|
||||||
|
@skip
|
||||||
|
def test_getRepostedBy(self, mock_get):
|
||||||
|
mock_get.return_value = requests_response(POST_THREAD_HTML,
|
||||||
|
url='http://orig/post')
|
||||||
|
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',
|
||||||
|
'did': 'did:web:eve.net',
|
||||||
|
'declaration': {
|
||||||
|
'$type': 'app.bsky.system.declRef',
|
||||||
|
'cid': 'TODO',
|
||||||
|
'actorType': 'app.bsky.system.actorUser',
|
||||||
|
},
|
||||||
|
'handle': 'eve.net',
|
||||||
|
'displayName': 'Eve',
|
||||||
|
'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||||
|
}],
|
||||||
|
}, got.json)
|
||||||
|
|
||||||
|
# def test_getTimeline(self, mock_get):
|
||||||
|
# mock_get.return_value = requests_response("""
|
||||||
|
# <body>
|
||||||
|
# </body>
|
||||||
|
# """, url='https://foo.com/')
|
||||||
|
|
||||||
|
# got = self.client.get('/xrpc/app.bsky.feed.getTimeline',
|
||||||
|
# query_string={'actor': 'foo.com'})
|
||||||
|
# self.assertEqual({
|
||||||
|
# }, got.json)
|
||||||
|
|
||||||
|
def test_getVotes(self, mock_get):
|
||||||
|
resp = self.client.get('/xrpc/app.bsky.feed.getVotes',
|
||||||
|
query_string={'uri': 'http://a/post'})
|
||||||
|
self.assertEqual({
|
||||||
|
'uri': 'http://a/post',
|
||||||
|
'votes': [],
|
||||||
|
}, resp.json)
|
|
@ -0,0 +1,60 @@
|
||||||
|
"""Unit tests for graph.py."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from . import testutil
|
||||||
|
|
||||||
|
|
||||||
|
# @patch('requests.get')
|
||||||
|
# class XrpcGraphTest(testutil.TestCase):
|
||||||
|
|
||||||
|
# def test_getAuthorFeed(self, mock_get):
|
||||||
|
# mock_get.return_value = requests_response("""
|
||||||
|
# <body>
|
||||||
|
# </body>
|
||||||
|
# """, url='https://foo.com/')
|
||||||
|
|
||||||
|
# got = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||||
|
# query_string={'actor': 'foo.com'},
|
||||||
|
# ).json
|
||||||
|
# self.assertEqual({
|
||||||
|
# }, got)
|
||||||
|
|
||||||
|
# def test_getPostThread(self, mock_get):
|
||||||
|
# mock_get.return_value = requests_response("""
|
||||||
|
# <body>
|
||||||
|
# </body>
|
||||||
|
# """, url='https://foo.com/')
|
||||||
|
|
||||||
|
# got = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||||
|
# query_string={'actor': 'foo.com'},
|
||||||
|
# ).json
|
||||||
|
# self.assertEqual({
|
||||||
|
# }, got)
|
||||||
|
|
||||||
|
# def test_getRepostedBy(self, mock_get):
|
||||||
|
# mock_get.return_value = requests_response("""
|
||||||
|
# <body>
|
||||||
|
# </body>
|
||||||
|
# """, url='https://foo.com/')
|
||||||
|
|
||||||
|
# got = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||||
|
# query_string={'actor': 'foo.com'},
|
||||||
|
# ).json
|
||||||
|
# self.assertEqual({
|
||||||
|
# }, got)
|
||||||
|
|
||||||
|
# def test_getTimeline(self, mock_get):
|
||||||
|
# mock_get.return_value = requests_response("""
|
||||||
|
# <body>
|
||||||
|
# </body>
|
||||||
|
# """, url='https://foo.com/')
|
||||||
|
|
||||||
|
# got = self.client.get('/xrpc/app.bsky.actor.getProfile',
|
||||||
|
# query_string={'actor': 'foo.com'},
|
||||||
|
# ).json
|
||||||
|
# self.assertEqual({
|
||||||
|
# }, got)
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""app.bsky.actor.* XRPC methods."""
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from granary import microformats2, bluesky
|
||||||
|
import mf2util
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
|
|
||||||
|
from app import xrpc_server
|
||||||
|
|
||||||
|
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 re.match(util.DOMAIN_RE, actor):
|
||||||
|
raise ValueError(f'{actor} is not a domain')
|
||||||
|
|
||||||
|
url = f'https://{actor}/'
|
||||||
|
mf2 = util.fetch_mf2(url, gateway=True)
|
||||||
|
hcard = mf2util.representative_hcard(mf2, mf2['url'])
|
||||||
|
if not hcard:
|
||||||
|
raise ValueError(f"Couldn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {mf2['url']}")
|
||||||
|
|
||||||
|
logger.info(f'Representative h-card: {json.dumps(hcard, indent=2)}')
|
||||||
|
|
||||||
|
actor = microformats2.json_to_object(hcard)
|
||||||
|
actor.setdefault('url', url)
|
||||||
|
logger.info(f'AS1 actor: {json.dumps(actor, indent=2)}')
|
||||||
|
|
||||||
|
profile = {
|
||||||
|
**bluesky.from_as1(actor),
|
||||||
|
'myState': {
|
||||||
|
# ?
|
||||||
|
'follow': 'TODO',
|
||||||
|
'member': 'TODO',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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.search')
|
||||||
|
def search(input, term=None, limit=None, before=None):
|
||||||
|
"""
|
||||||
|
lexicons/app/bsky/actor/search.json
|
||||||
|
"""
|
||||||
|
# TODO based on stored users
|
||||||
|
return {'users': []}
|
||||||
|
|
||||||
|
|
||||||
|
@xrpc_server.method('app.bsky.actor.searchTypeahead')
|
||||||
|
def searchTypeahead(input, term=None, limit=None):
|
||||||
|
"""
|
||||||
|
lexicons/app/bsky/actor/searchTypeahead.json
|
||||||
|
"""
|
||||||
|
# TODO based on stored users
|
||||||
|
return {'users': []}
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""app.bsky.feed.* XRPC methods."""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from granary import microformats2, bluesky
|
||||||
|
import mf2util
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
|
|
||||||
|
from app import xrpc_server
|
||||||
|
|
||||||
|
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 re.match(util.DOMAIN_RE, author):
|
||||||
|
raise ValueError(f'{author} is not a domain')
|
||||||
|
|
||||||
|
url = f'https://{author}/'
|
||||||
|
mf2 = util.fetch_mf2(url, gateway=True)
|
||||||
|
logger.info(f'Got mf2: {json.dumps(mf2, indent=2)}')
|
||||||
|
|
||||||
|
feed_author = mf2util.find_author(mf2, source_url=url, fetch_mf2_func=util.fetch_mf2)
|
||||||
|
if feed_author:
|
||||||
|
logger.info(f'Authorship found: {feed_author}')
|
||||||
|
actor = {
|
||||||
|
'url': feed_author.get('url') or url,
|
||||||
|
'displayName': feed_author.get('name'),
|
||||||
|
'image': {'url': feed_author.get('photo')},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.info(f'No authorship result on {url} ; generated {feed_author}')
|
||||||
|
actor = {
|
||||||
|
'url': url,
|
||||||
|
'displayName': author,
|
||||||
|
}
|
||||||
|
|
||||||
|
activities = microformats2.json_to_activities(mf2)
|
||||||
|
# default actor to feed author
|
||||||
|
for a in activities:
|
||||||
|
a.setdefault('actor', actor)
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
mf2 = util.fetch_mf2(uri, gateway=True)
|
||||||
|
logger.info(f'Got mf2: {json.dumps(mf2, indent=2)}')
|
||||||
|
|
||||||
|
entry = mf2util.find_first_entry(mf2, ['h-entry'])
|
||||||
|
logger.info(f'Entry: {json.dumps(entry, indent=2)}')
|
||||||
|
if not entry:
|
||||||
|
raise ValueError(f"No h-entry on {uri}")
|
||||||
|
|
||||||
|
obj = microformats2.json_to_object(entry)
|
||||||
|
logger.info(f'AS1: {json.dumps(obj, indent=2)}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'thread': {
|
||||||
|
'$type': 'app.bsky.feed.getPostThread#threadViewPost',
|
||||||
|
'post': bluesky.from_as1(obj)['post'],
|
||||||
|
'replies': [{
|
||||||
|
'$type': 'app.bsky.feed.getPostThread#threadViewPost',
|
||||||
|
'post': bluesky.from_as1(reply)['post'],
|
||||||
|
} for reply in obj.get('replies', {}).get('items', [])],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# what's the mf2 for repost children of an h-entry? u-repost, like u-comment?
|
||||||
|
# nothing about markup on https://indieweb.org/reposts
|
||||||
|
# based on https://indieweb.org/comments-display , it would be u-repost
|
||||||
|
# @xrpc_server.method('app.bsky.feed.getRepostedBy')
|
||||||
|
# def getRepostedBy(input, uri=None, cid=None, limit=None, before=None):
|
||||||
|
# """
|
||||||
|
# lexicons/app/bsky/feed/getRepostedBy.json
|
||||||
|
# """
|
||||||
|
# mf2 = util.fetch_mf2(uri, gateway=True)
|
||||||
|
# logger.info(f'Got mf2: {json.dumps(mf2, indent=2)}')
|
||||||
|
|
||||||
|
# entry = mf2util.find_first_entry(mf2, ['h-entry'])
|
||||||
|
# logger.info(f'Entry: {json.dumps(entry, indent=2)}')
|
||||||
|
# if not entry:
|
||||||
|
# raise ValueError(f"No h-entry on {uri}")
|
||||||
|
|
||||||
|
# obj = microformats2.json_to_object(entry)
|
||||||
|
# logger.info(f'AS1: {json.dumps(obj, indent=2)}')
|
||||||
|
|
||||||
|
# return {
|
||||||
|
# 'uri': 'http://orig/post',
|
||||||
|
# 'repostBy': [{
|
||||||
|
# '$type': 'app.bsky.feed.getRepostedBy#repostedBy',
|
||||||
|
# 'did': 'did:web:eve.net',
|
||||||
|
# 'declaration': {
|
||||||
|
# '$type': 'app.bsky.system.declRef',
|
||||||
|
# 'cid': 'TODO',
|
||||||
|
# 'actorType': 'app.bsky.system.actorUser',
|
||||||
|
# },
|
||||||
|
# 'handle': 'eve.net',
|
||||||
|
# 'displayName': 'Eve',
|
||||||
|
# 'indexedAt': '2022-01-02T03:04:05+00:00',
|
||||||
|
# }],
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
# TODO based on datastore
|
||||||
|
# @xrpc_server.method('app.bsky.feed.getTimeline')
|
||||||
|
# def getTimeline(input):
|
||||||
|
# """
|
||||||
|
# lexicons/app/bsky/feed/getTimeline.json
|
||||||
|
# """
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: use likes as votes?
|
||||||
|
@xrpc_server.method('app.bsky.feed.getVotes')
|
||||||
|
def getVotes(input, uri=None, direction=None, cid=None, limit=None, before=None):
|
||||||
|
"""
|
||||||
|
lexicons/app/bsky/feed/getVotes.json
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'uri': uri,
|
||||||
|
'votes': [],
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""app.bsky.graph.* XRPC methods."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from app import xrpc_server
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# get these from datastore
|
||||||
|
@xrpc_server.method('app.bsky.graph.getFollowers')
|
||||||
|
def getFollowers(input):
|
||||||
|
"""
|
||||||
|
lexicons/app/bsky/graph/getFollowers.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
@xrpc_server.method('app.bsky.graph.getFollows')
|
||||||
|
def getFollows(input):
|
||||||
|
"""
|
||||||
|
lexicons/app/bsky/graph/getFollows.json
|
||||||
|
"""
|
||||||
|
|
Ładowanie…
Reference in New Issue