mirror of https://github.com/jointakahe/takahe
Compare commits
7 Commits
be39723551
...
57ec90ee24
Author | SHA1 | Date |
---|---|---|
Osma Ahvenlampi | 57ec90ee24 | |
Henri Dickson | 72eb6a6271 | |
Jamie Bliss | b2223ddf42 | |
Jamie Bliss | 045a499ddf | |
Jamie Bliss | 0fa48578f2 | |
Osma Ahvenlampi | 85ee2692f1 | |
Osma Ahvenlampi | 05ec6cbe74 |
|
@ -3,7 +3,7 @@
|
||||||
A *beta* Fediverse server for microblogging. Not fully polished yet -
|
A *beta* Fediverse server for microblogging. Not fully polished yet -
|
||||||
we're still working towards a 1.0!
|
we're still working towards a 1.0!
|
||||||
|
|
||||||
**Current version: [0.10.1](https://docs.jointakahe.org/en/latest/releases/0.10/)**
|
**Current version: [0.11.0](https://docs.jointakahe.org/en/latest/releases/0.11/)**
|
||||||
|
|
||||||
Key features:
|
Key features:
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ class Account(Schema):
|
||||||
bot: bool
|
bot: bool
|
||||||
group: bool
|
group: bool
|
||||||
discoverable: bool
|
discoverable: bool
|
||||||
|
indexable: bool
|
||||||
moved: Union[None, bool, "Account"]
|
moved: Union[None, bool, "Account"]
|
||||||
suspended: bool = False
|
suspended: bool = False
|
||||||
limited: bool = False
|
limited: bool = False
|
||||||
|
|
|
@ -559,6 +559,7 @@ schemas = {
|
||||||
"@context": {
|
"@context": {
|
||||||
"toot": "http://joinmastodon.org/ns#",
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
"discoverable": "toot:discoverable",
|
"discoverable": "toot:discoverable",
|
||||||
|
"indexable": "toot:indexable",
|
||||||
"devices": "toot:devices",
|
"devices": "toot:devices",
|
||||||
"featured": "toot:featured",
|
"featured": "toot:featured",
|
||||||
"featuredTags": "toot:featuredTags",
|
"featuredTags": "toot:featuredTags",
|
||||||
|
|
|
@ -219,7 +219,7 @@ class HttpSignature:
|
||||||
body_bytes = b""
|
body_bytes = b""
|
||||||
# GET requests get implicit accept headers added
|
# GET requests get implicit accept headers added
|
||||||
if method == "get":
|
if method == "get":
|
||||||
headers["Accept"] = "application/ld+json"
|
headers["Accept"] = "application/activity+json,application/ld+json"
|
||||||
# Sign the headers
|
# Sign the headers
|
||||||
signed_string = "\n".join(
|
signed_string = "\n".join(
|
||||||
f"{name.lower()}: {value}" for name, value in headers.items()
|
f"{name.lower()}: {value}" for name, value in headers.items()
|
||||||
|
|
|
@ -1,21 +1,37 @@
|
||||||
0.11
|
0.11
|
||||||
====
|
====
|
||||||
|
|
||||||
*Released: Not Yet Released*
|
*Released: 2024-02-05*
|
||||||
|
|
||||||
Notes TBD.
|
This is largely a bugfix and catch up release.
|
||||||
|
|
||||||
|
Some highlights:
|
||||||
|
|
||||||
Upgrade Notes
|
* Python 3.10 has been dropped. The new minimum Python version is 3.11
|
||||||
-------------
|
* Jamie (`@astraluma@tacobelllabs.net <https://tacobelllabs.net/@astraluma>`_)
|
||||||
|
has officially joined the project
|
||||||
|
* If your S3 does not use TLS, you must use ``s3-insecure`` in your
|
||||||
|
configuration
|
||||||
|
* Takahē now supports unicode hashtags
|
||||||
|
* Add a Maximum Media Attachments setting
|
||||||
|
* Inverted the pruning command exit codes
|
||||||
|
* Posts are no longer required to have text content
|
||||||
|
|
||||||
VAPID keys and Push notifications
|
And some interoperability bugs:
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Takahē now supports push notifications if you supply a valid VAPID keypair as
|
* Fixed a bug with GoToSocial
|
||||||
the ``TAKAHE_VAPID_PUBLIC_KEY`` and ``TAKAHE_VAPID_PRIVATE_KEY`` environment
|
* Attempted to fix follows from Misskey family
|
||||||
variables. You can generate a keypair via `https://web-push-codelab.glitch.me/`_.
|
* Correctly handle when a federated report doesn't have content
|
||||||
|
|
||||||
Note that users of apps may need to sign out and in again to their accounts for
|
In additions, there's many bugfixes and minor changes, including:
|
||||||
the app to notice that it can now do push notifications. Some apps, like Elk,
|
|
||||||
may cache the fact your server didn't support it for a while.
|
* Several JSON handling improvements
|
||||||
|
* Post pruning now has a random element to it
|
||||||
|
* More specific loggers
|
||||||
|
* Don't make local identities stale
|
||||||
|
* Don't try to unmute when there's no expiration
|
||||||
|
* Don't try to WebFinger local users
|
||||||
|
* Synchronize follow accepting and profile fetching
|
||||||
|
* Perform some basic domain validity
|
||||||
|
* Correctly reject more operations when the identity is deleted
|
||||||
|
* Post edit fanouts for likers/boosters
|
||||||
|
|
|
@ -7,6 +7,7 @@ Versions
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
0.11
|
||||||
0.10
|
0.10
|
||||||
0.9
|
0.9
|
||||||
0.8
|
0.8
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
Upgrade Notes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
VAPID keys and Push notifications
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Takahē now supports push notifications if you supply a valid VAPID keypair as
|
||||||
|
the ``TAKAHE_VAPID_PUBLIC_KEY`` and ``TAKAHE_VAPID_PRIVATE_KEY`` environment
|
||||||
|
variables. You can generate a keypair via `https://web-push-codelab.glitch.me/`_.
|
||||||
|
|
||||||
|
Note that users of apps may need to sign out and in again to their accounts for
|
||||||
|
the app to notice that it can now do push notifications. Some apps, like Elk,
|
||||||
|
may cache the fact your server didn't support it for a while.
|
|
@ -31,6 +31,7 @@
|
||||||
{% include "forms/_field.html" with field=form.domain %}
|
{% include "forms/_field.html" with field=form.domain %}
|
||||||
{% include "forms/_field.html" with field=form.name %}
|
{% include "forms/_field.html" with field=form.name %}
|
||||||
{% include "forms/_field.html" with field=form.discoverable %}
|
{% include "forms/_field.html" with field=form.discoverable %}
|
||||||
|
{% include "forms/_field.html" with field=form.indexable %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button>Create</button>
|
<button>Create</button>
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Privacy</legend>
|
<legend>Privacy</legend>
|
||||||
{% include "forms/_field.html" with field=form.discoverable %}
|
{% include "forms/_field.html" with field=form.discoverable %}
|
||||||
|
{% include "forms/_field.html" with field=form.indexable %}
|
||||||
{% include "forms/_field.html" with field=form.visible_follows %}
|
{% include "forms/_field.html" with field=form.visible_follows %}
|
||||||
{% include "forms/_field.html" with field=form.search_enabled %}
|
{% include "forms/_field.html" with field=form.search_enabled %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -204,6 +204,8 @@ def test_fetch_actor(httpx_mock, config_system):
|
||||||
assert identity.image_uri == "https://example.com/image.jpg"
|
assert identity.image_uri == "https://example.com/image.jpg"
|
||||||
assert identity.summary == "<p>A test user</p>"
|
assert identity.summary == "<p>A test user</p>"
|
||||||
assert "ts-a-faaaake" in identity.public_key
|
assert "ts-a-faaaake" in identity.public_key
|
||||||
|
# convention is that indexability should be opt-in
|
||||||
|
assert not identity.indexable
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.3 on 2023-11-16 13:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("users", "0022_follow_request"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="identity",
|
||||||
|
name="indexable",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -233,6 +233,7 @@ class Identity(StatorModel):
|
||||||
summary = models.TextField(blank=True, null=True)
|
summary = models.TextField(blank=True, null=True)
|
||||||
manually_approves_followers = models.BooleanField(blank=True, null=True)
|
manually_approves_followers = models.BooleanField(blank=True, null=True)
|
||||||
discoverable = models.BooleanField(default=True)
|
discoverable = models.BooleanField(default=True)
|
||||||
|
indexable = models.BooleanField(default=False)
|
||||||
|
|
||||||
profile_uri = models.CharField(max_length=500, blank=True, null=True)
|
profile_uri = models.CharField(max_length=500, blank=True, null=True)
|
||||||
inbox_uri = models.CharField(max_length=500, blank=True, null=True)
|
inbox_uri = models.CharField(max_length=500, blank=True, null=True)
|
||||||
|
@ -597,6 +598,7 @@ class Identity(StatorModel):
|
||||||
"published": self.created.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
"published": self.created.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
"url": self.absolute_profile_uri(),
|
"url": self.absolute_profile_uri(),
|
||||||
"toot:discoverable": self.discoverable,
|
"toot:discoverable": self.discoverable,
|
||||||
|
"toot:indexable": self.indexable,
|
||||||
}
|
}
|
||||||
if self.name:
|
if self.name:
|
||||||
response["name"] = self.name
|
response["name"] = self.name
|
||||||
|
@ -958,6 +960,7 @@ class Identity(StatorModel):
|
||||||
self.icon_uri = get_first_image_url(document.get("icon", None))
|
self.icon_uri = get_first_image_url(document.get("icon", None))
|
||||||
self.image_uri = get_first_image_url(document.get("image", None))
|
self.image_uri = get_first_image_url(document.get("image", None))
|
||||||
self.discoverable = document.get("toot:discoverable", True)
|
self.discoverable = document.get("toot:discoverable", True)
|
||||||
|
self.indexable = document.get("toot:indexable", False)
|
||||||
# Profile links/metadata
|
# Profile links/metadata
|
||||||
self.metadata = []
|
self.metadata = []
|
||||||
for attachment in get_list(document, "attachment"):
|
for attachment in get_list(document, "attachment"):
|
||||||
|
@ -1095,6 +1098,7 @@ class Identity(StatorModel):
|
||||||
"bot": self.actor_type.lower() in ["service", "application"],
|
"bot": self.actor_type.lower() in ["service", "application"],
|
||||||
"group": self.actor_type.lower() == "group",
|
"group": self.actor_type.lower() == "group",
|
||||||
"discoverable": self.discoverable,
|
"discoverable": self.discoverable,
|
||||||
|
"indexable": self.indexable,
|
||||||
"suspended": False,
|
"suspended": False,
|
||||||
"limited": False,
|
"limited": False,
|
||||||
"created_at": format_ld_date(
|
"created_at": format_ld_date(
|
||||||
|
|
|
@ -38,6 +38,7 @@ class IdentityService:
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
name: str,
|
name: str,
|
||||||
discoverable: bool = True,
|
discoverable: bool = True,
|
||||||
|
indexable: bool = False,
|
||||||
) -> Identity:
|
) -> Identity:
|
||||||
identity = Identity.objects.create(
|
identity = Identity.objects.create(
|
||||||
actor_uri=f"https://{domain.uri_domain}/@{username}@{domain.domain}/",
|
actor_uri=f"https://{domain.uri_domain}/@{username}@{domain.domain}/",
|
||||||
|
@ -46,6 +47,7 @@ class IdentityService:
|
||||||
name=name,
|
name=name,
|
||||||
local=True,
|
local=True,
|
||||||
discoverable=discoverable,
|
discoverable=discoverable,
|
||||||
|
indexable=indexable,
|
||||||
)
|
)
|
||||||
identity.users.add(user)
|
identity.users.add(user)
|
||||||
identity.generate_keypair()
|
identity.generate_keypair()
|
||||||
|
|
|
@ -313,6 +313,14 @@ class CreateIdentity(FormView):
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
indexable = forms.BooleanField(
|
||||||
|
help_text="Should this user's activities be indexable on other servers.",
|
||||||
|
initial=False,
|
||||||
|
widget=forms.Select(
|
||||||
|
choices=[(True, "Indexable"), (False, "Not Indexable")]
|
||||||
|
),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -385,6 +393,7 @@ class CreateIdentity(FormView):
|
||||||
domain=domain_instance,
|
domain=domain_instance,
|
||||||
name=form.cleaned_data["name"],
|
name=form.cleaned_data["name"],
|
||||||
discoverable=form.cleaned_data["discoverable"],
|
discoverable=form.cleaned_data["discoverable"],
|
||||||
|
indexable=form.cleaned_data["indexable"],
|
||||||
)
|
)
|
||||||
self.request.session["identity_id"] = identity.id
|
self.request.session["identity_id"] = identity.id
|
||||||
return redirect(identity.urls.view)
|
return redirect(identity.urls.view)
|
||||||
|
|
|
@ -43,6 +43,13 @@ class ProfilePage(FormView):
|
||||||
),
|
),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
indexable = forms.BooleanField(
|
||||||
|
help_text="Opt-in to be indexed for search on other servers.\n(Disabling this does not guarantee third-party servers won't index your posts without permission)",
|
||||||
|
widget=forms.Select(
|
||||||
|
choices=[(True, "Indexable"), (False, "Not Indexable")]
|
||||||
|
),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
visible_follows = forms.BooleanField(
|
visible_follows = forms.BooleanField(
|
||||||
help_text="Whether or not to show your following and follower counts in your profile",
|
help_text="Whether or not to show your following and follower counts in your profile",
|
||||||
widget=forms.Select(choices=[(True, "Visible"), (False, "Hidden")]),
|
widget=forms.Select(choices=[(True, "Visible"), (False, "Hidden")]),
|
||||||
|
@ -93,6 +100,7 @@ class ProfilePage(FormView):
|
||||||
"icon": self.identity.icon and self.identity.icon.url,
|
"icon": self.identity.icon and self.identity.icon.url,
|
||||||
"image": self.identity.image and self.identity.image.url,
|
"image": self.identity.image and self.identity.image.url,
|
||||||
"discoverable": self.identity.discoverable,
|
"discoverable": self.identity.discoverable,
|
||||||
|
"indexable": self.identity.indexable,
|
||||||
"visible_follows": self.identity.config_identity.visible_follows,
|
"visible_follows": self.identity.config_identity.visible_follows,
|
||||||
"metadata": self.identity.metadata or [],
|
"metadata": self.identity.metadata or [],
|
||||||
"search_enabled": self.identity.config_identity.search_enabled,
|
"search_enabled": self.identity.config_identity.search_enabled,
|
||||||
|
@ -104,6 +112,7 @@ class ProfilePage(FormView):
|
||||||
service = IdentityService(self.identity)
|
service = IdentityService(self.identity)
|
||||||
self.identity.name = form.cleaned_data["name"]
|
self.identity.name = form.cleaned_data["name"]
|
||||||
self.identity.discoverable = form.cleaned_data["discoverable"]
|
self.identity.discoverable = form.cleaned_data["discoverable"]
|
||||||
|
self.identity.indexable = form.cleaned_data["indexable"]
|
||||||
service.set_summary(form.cleaned_data["summary"])
|
service.set_summary(form.cleaned_data["summary"])
|
||||||
# Resize images
|
# Resize images
|
||||||
icon = form.cleaned_data.get("icon")
|
icon = form.cleaned_data.get("icon")
|
||||||
|
|
Loading…
Reference in New Issue