mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
federation: handle remote subscriptions and unsubscriptions
This commit is contained in:
parent
c3d36cfb86
commit
c9beb0c0da
8 changed files with 52 additions and 16 deletions
|
@ -77,7 +77,7 @@ def nodeinfo2():
|
|||
nodeinfo_data = {
|
||||
"version": "2.0",
|
||||
"software": {
|
||||
"name": "pyfedi",
|
||||
"name": "PieFed",
|
||||
"version": "0.1"
|
||||
},
|
||||
"protocols": [
|
||||
|
@ -151,18 +151,18 @@ def community_profile(actor):
|
|||
# don't provide activitypub info for remote communities
|
||||
if 'application/ld+json' in request.headers.get('Accept', ''):
|
||||
abort(404)
|
||||
community = Community.query.filter_by(ap_id=actor, banned=False).first()
|
||||
community: Community = Community.query.filter_by(ap_id=actor, banned=False).first()
|
||||
else:
|
||||
community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
||||
community: Community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
||||
if community is not None:
|
||||
if 'application/ld+json' in request.headers.get('Accept', ''):
|
||||
server = current_app.config['SERVER_NAME']
|
||||
actor_data = {"@context": default_context(),
|
||||
"type": "Group",
|
||||
"id": f"https://{server}/c/{actor}",
|
||||
"name": actor.title,
|
||||
"summary": actor.description,
|
||||
"sensitive": True if actor.nsfw or actor.nsfl else False,
|
||||
"name": community.title,
|
||||
"summary": community.description,
|
||||
"sensitive": True if community.nsfw or community.nsfl else False,
|
||||
"preferredUsername": actor,
|
||||
"inbox": f"https://{server}/c/{actor}/inbox",
|
||||
"outbox": f"https://{server}/c/{actor}/outbox",
|
||||
|
@ -170,7 +170,7 @@ def community_profile(actor):
|
|||
"moderators": f"https://{server}/c/{actor}/moderators",
|
||||
"featured": f"https://{server}/c/{actor}/featured",
|
||||
"attributedTo": f"https://{server}/c/{actor}/moderators",
|
||||
"postingRestrictedToMods": actor.restricted_to_mods,
|
||||
"postingRestrictedToMods": community.restricted_to_mods,
|
||||
"url": f"https://{server}/c/{actor}",
|
||||
"publicKey": {
|
||||
"id": f"https://{server}/c/{actor}#main-key",
|
||||
|
@ -180,13 +180,13 @@ def community_profile(actor):
|
|||
"endpoints": {
|
||||
"sharedInbox": f"https://{server}/inbox"
|
||||
},
|
||||
"published": community.created.isoformat(),
|
||||
"published": community.created_at.isoformat(),
|
||||
"updated": community.last_active.isoformat(),
|
||||
}
|
||||
if community.avatar_id is not None:
|
||||
if community.icon_id is not None:
|
||||
actor_data["icon"] = {
|
||||
"type": "Image",
|
||||
"url": f"https://{server}/avatars/{community.avatar.file_path}"
|
||||
"url": f"https://{server}/avatars/{community.icon.file_path}"
|
||||
}
|
||||
resp = jsonify(actor_data)
|
||||
resp.content_type = 'application/activity+json'
|
||||
|
@ -411,6 +411,22 @@ def shared_inbox():
|
|||
community.subscriptions_count += 1
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
elif request_json['type'] == 'Undo':
|
||||
if request_json['object']['type'] == 'Follow': # Unsubscribe from a community
|
||||
community_ap_id = request_json['actor']
|
||||
user_ap_id = request_json['object']['actor']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
community = find_actor_or_create(community_ap_id)
|
||||
if user and community:
|
||||
member = CommunityMember.query.filter_by(user_id=user.id, community_id=community.id).first()
|
||||
db.session.delete(member)
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
elif request_json['object']['type'] == 'Like': # Undoing an upvote
|
||||
...
|
||||
elif request_json['object']['type'] == 'Dislike': # Undoing a downvote
|
||||
...
|
||||
|
||||
else:
|
||||
activity_log.exception_message = 'Instance banned'
|
||||
else:
|
||||
|
@ -422,6 +438,7 @@ def shared_inbox():
|
|||
activity_log.result = 'failure'
|
||||
db.session.add(activity_log)
|
||||
db.session.commit()
|
||||
return ''
|
||||
|
||||
|
||||
@bp.route('/c/<actor>/outbox', methods=['GET'])
|
||||
|
|
|
@ -236,6 +236,7 @@ class HttpSignature:
|
|||
headers_string,
|
||||
public_key,
|
||||
)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def signed_request(
|
||||
|
@ -298,7 +299,7 @@ class HttpSignature:
|
|||
)
|
||||
|
||||
# Announce ourselves with an agent similar to Mastodon
|
||||
headers["User-Agent"] = 'pyfedi'
|
||||
headers["User-Agent"] = 'PieFed'
|
||||
|
||||
# Send the request with all those headers except the pseudo one
|
||||
del headers["(request-target)"]
|
||||
|
|
|
@ -214,7 +214,7 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
return None
|
||||
user = User.query.filter_by(
|
||||
ap_profile_id=actor).first() # finds users formatted like https://kbin.social/u/tables
|
||||
if user.banned:
|
||||
if user and user.banned:
|
||||
return None
|
||||
if user is None:
|
||||
user = Community.query.filter_by(ap_profile_id=actor).first()
|
||||
|
@ -225,6 +225,7 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
params={'resource': f"acct:{address}@{server}"})
|
||||
if webfinger_data.status_code == 200:
|
||||
webfinger_json = webfinger_data.json()
|
||||
webfinger_data.close()
|
||||
for links in webfinger_json['links']:
|
||||
if 'rel' in links and links['rel'] == 'self': # this contains the URL of the activitypub profile
|
||||
type = links['type'] if 'type' in links else 'application/activity+json'
|
||||
|
@ -233,11 +234,12 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
# to see the structure of the json contained in actor_data, do a GET to https://lemmy.world/c/technology with header Accept: application/activity+json
|
||||
if actor_data.status_code == 200:
|
||||
activity_json = actor_data.json()
|
||||
actor_data.close()
|
||||
if activity_json['type'] == 'Person':
|
||||
user = User(user_name=activity_json['preferredUsername'],
|
||||
email=f"{address}@{server}",
|
||||
about=parse_summary(activity_json),
|
||||
created_at=activity_json['published'],
|
||||
created=activity_json['published'],
|
||||
ap_id=f"{address}@{server}",
|
||||
ap_public_url=activity_json['id'],
|
||||
ap_profile_id=activity_json['id'],
|
||||
|
|
|
@ -14,6 +14,18 @@ class AddLocalCommunity(FlaskForm):
|
|||
nsfw = BooleanField('18+ NSFW')
|
||||
submit = SubmitField(_l('Create'))
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
if not super().validate():
|
||||
return False
|
||||
if self.url.data.strip() == '':
|
||||
self.url.errors.append(_('Url is required.'))
|
||||
return False
|
||||
else:
|
||||
if '-' in self.url.data.strip():
|
||||
self.url.errors.append(_('- cannot be in Url. Use _ instead?'))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class SearchRemoteCommunity(FlaskForm):
|
||||
address = StringField(_l('Server address'), validators=[DataRequired()])
|
||||
|
|
|
@ -15,6 +15,7 @@ from app.models import Community, CommunityMember
|
|||
@bp.route('/', methods=['GET', 'POST'])
|
||||
@bp.route('/index', methods=['GET', 'POST'])
|
||||
def index():
|
||||
raise Exception('cowbell')
|
||||
verification_warning()
|
||||
return render_template('index.html')
|
||||
|
||||
|
|
|
@ -297,7 +297,6 @@ class User(UserMixin, db.Model):
|
|||
{'user_id': self.id})
|
||||
|
||||
|
||||
|
||||
class ActivityLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||
|
|
|
@ -177,5 +177,5 @@ function setupHideButtons() {
|
|||
|
||||
function titleToURL(title) {
|
||||
// Convert the title to lowercase and replace spaces with hyphens
|
||||
return title.toLowerCase().replace(/\s+/g, '-');
|
||||
return title.toLowerCase().replace(/\s+/g, '_');
|
||||
}
|
|
@ -33,8 +33,12 @@ def getmtime(filename):
|
|||
|
||||
# do a GET request to a uri, return the result
|
||||
def get_request(uri, params=None, headers=None) -> requests.Response:
|
||||
if headers is None:
|
||||
headers = {'User-Agent': 'PieFed/1.0'}
|
||||
else:
|
||||
headers.update({'User-Agent': 'PieFed/1.0'})
|
||||
try:
|
||||
response = requests.get(uri, params=params, headers=headers, timeout=1, allow_redirects=True)
|
||||
response = requests.get(uri, params=params, headers=headers, timeout=5, allow_redirects=True)
|
||||
except requests.exceptions.SSLError as invalid_cert:
|
||||
# Not our problem if the other end doesn't have proper SSL
|
||||
current_app.logger.info(f"{uri} {invalid_cert}")
|
||||
|
|
Loading…
Reference in a new issue