astro-atmosphere-comments 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +36 -27
- package/components/AtmosphereComments.astro +378 -117
- package/components/AtmosphereReactions.astro +145 -98
- package/components/Composer.astro +5 -2
- package/components/SignInForm.astro +65 -7
- package/components/atmosphere.css +267 -2
- package/index.d.ts +11 -0
- package/index.mjs +53 -32
- package/lib/client/account.ts +661 -0
- package/lib/client/composer.ts +23 -10
- package/lib/client/subscribe.ts +106 -0
- package/lib/http.ts +28 -0
- package/lib/permissions.ts +132 -0
- package/lib/public-settings.ts +20 -0
- package/lib/quoted.ts +4 -0
- package/lib/settings.ts +2 -0
- package/lib/thread.ts +36 -6
- package/lib/uris.ts +39 -0
- package/locales/de.json +20 -4
- package/locales/en.json +48 -13
- package/locales/es.json +20 -4
- package/locales/fr.json +20 -4
- package/locales/it.json +20 -4
- package/locales/ja.json +20 -4
- package/locales/nl.json +20 -4
- package/locales/pt.json +20 -4
- package/package.json +2 -7
- package/routes/callback.astro +42 -0
- package/routes/client-metadata.json.ts +3 -2
- package/routes/following.ts +28 -0
- package/routes/identity.ts +69 -0
- package/routes/media.ts +7 -10
- package/routes/post.ts +2 -1
- package/routes/profile.ts +20 -0
- package/routes/richtext.ts +30 -0
- package/routes/thread.ts +23 -3
- package/routes/typeahead.ts +7 -23
- package/virtual.d.ts +5 -0
- package/bin/cli.mjs +0 -31
- package/lib/oauth.ts +0 -110
- package/lib/reader.ts +0 -130
- package/lib/store.ts +0 -46
- package/lib/visitor.ts +0 -42
- package/routes/callback.ts +0 -30
- package/routes/comment.ts +0 -97
- package/routes/delete.ts +0 -41
- package/routes/follow.ts +0 -71
- package/routes/jwks.json.ts +0 -13
- package/routes/login.ts +0 -58
- package/routes/logout.ts +0 -20
- package/routes/me.ts +0 -12
- package/routes/react.ts +0 -48
- package/routes/state.ts +0 -24
package/locales/en.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"signIn.heading": "Sign in to leave a comment
|
|
3
|
-
"signIn.anyAccount": "
|
|
2
|
+
"signIn.heading": "Sign in to leave a comment",
|
|
3
|
+
"signIn.anyAccount": "Use {{bluesky}} or any other {{atmosphereAccount}}. {{createAccount}}",
|
|
4
4
|
"signIn.atmosphereAccount": "Atmosphere account",
|
|
5
|
+
"signIn.more": "How does signing in work?",
|
|
5
6
|
"signIn.button": "Sign in with Atmosphere",
|
|
6
7
|
"signIn.matchingAccounts": "Matching accounts",
|
|
7
|
-
"signIn.privacy": "You
|
|
8
|
-
"signIn.createAccount": "Create
|
|
9
|
-
"elsewhere.text": "Rather not sign in
|
|
8
|
+
"signIn.privacy": "You sign in on your account's own site (Bluesky's, for most people) and approve what this site can do. Your password is never shared here.",
|
|
9
|
+
"signIn.createAccount": "Create one for free",
|
|
10
|
+
"elsewhere.text": "Rather not sign in? {{link}}",
|
|
10
11
|
"elsewhere.link": "Reply on Bluesky instead →",
|
|
11
12
|
"actions.label": "Respond to this post",
|
|
13
|
+
"actions.likeAndRecommend": "Like on Bluesky, and recommend in apps such as Leaflet",
|
|
12
14
|
"actions.like": "Like",
|
|
13
15
|
"actions.liked": "Liked",
|
|
14
16
|
"actions.repost": "Repost",
|
|
@@ -49,25 +51,29 @@
|
|
|
49
51
|
"comment.deleteConfirm": "Delete this comment from Bluesky?",
|
|
50
52
|
"comment.deleting": "Deleting…",
|
|
51
53
|
"comment.deleted": "Your comment was deleted.",
|
|
52
|
-
"comment.deletePermission": "To delete comments here, this site needs one more permission from your account.
|
|
54
|
+
"comment.deletePermission": "To delete comments here, this site needs one more permission from your Atmosphere account. Alternatively, you can delete it {{link}}.",
|
|
53
55
|
"comment.continue": "Continue to approve",
|
|
54
56
|
"comment.cancel": "Cancel",
|
|
55
|
-
"signInFirst.reply": "Sign in to reply
|
|
56
|
-
"signInFirst.like": "Sign in to like
|
|
57
|
-
"signInFirst.repost": "Sign in to repost
|
|
57
|
+
"signInFirst.reply": "Sign in to reply",
|
|
58
|
+
"signInFirst.like": "Sign in to like this comment",
|
|
59
|
+
"signInFirst.repost": "Sign in to repost this comment",
|
|
60
|
+
"signInFirst.likePost": "Sign in to like this post",
|
|
61
|
+
"signInFirst.repostPost": "Sign in to repost this post",
|
|
62
|
+
"signInFirst.quotePost": "Sign in to quote this post",
|
|
63
|
+
"comment.prompt": "Write a comment…",
|
|
64
|
+
"comment.signIn": "Sign in",
|
|
58
65
|
"status.liked": "Liked.",
|
|
59
66
|
"status.likeRemoved": "Like removed.",
|
|
60
67
|
"status.reposted": "Reposted.",
|
|
61
68
|
"status.repostRemoved": "Repost removed.",
|
|
62
69
|
"status.signedOut": "You're signed out.",
|
|
63
|
-
"messages.signedIn": "You're signed in.
|
|
70
|
+
"messages.signedIn": "You're signed in.",
|
|
64
71
|
"messages.signInFailed": "Signing in didn't finish. Please try again.",
|
|
65
72
|
"messages.accountNotFound": "We couldn't find that account. Check the handle and try again.",
|
|
66
73
|
"messages.invalidHandle": "That doesn't look like a handle. It's usually something like alice.bsky.social.",
|
|
67
74
|
"messages.signedOut": "You've been signed out. Please sign in again.",
|
|
68
|
-
"messages.needsPermission": "
|
|
75
|
+
"messages.needsPermission": "That needs one more permission from your account.",
|
|
69
76
|
"messages.notAllowed": "That can't be done here.",
|
|
70
|
-
"messages.newPermissions": "This site can now also like and repost for you, so it needs your permission again. Please sign in again.",
|
|
71
77
|
"messages.tooLong": "Posts can be up to {{max}} characters.",
|
|
72
78
|
"messages.tooMany": "You're going quickly. Please wait a minute and try again.",
|
|
73
79
|
"messages.error": "That didn't work. Please try again.",
|
|
@@ -91,5 +97,34 @@
|
|
|
91
97
|
"follow.following": "Following",
|
|
92
98
|
"follow.followAs": "Follow {{handle}} as {{me}}",
|
|
93
99
|
"follow.followingLabel": "You follow {{handle}}",
|
|
94
|
-
"
|
|
100
|
+
"subscribe.subscribe": "Subscribe",
|
|
101
|
+
"subscribe.subscribed": "✓ Subscribed",
|
|
102
|
+
"subscribe.subscribeLabel": "Subscribe with your Atmosphere account, to read new posts in apps such as Leaflet or Standard Reader",
|
|
103
|
+
"subscribe.subscribedLabel": "You're subscribed. Press to unsubscribe",
|
|
104
|
+
"subscribe.signIn": "Sign in to subscribe",
|
|
105
|
+
"subscribe.more": "More ways to follow",
|
|
106
|
+
"subscribe.copyLink": "Copy link",
|
|
107
|
+
"subscribe.copied": "Copied!",
|
|
108
|
+
"subscribe.feed": "RSS feed",
|
|
109
|
+
"subscribe.viewBluesky": "View on Bluesky",
|
|
110
|
+
"subscribe.standardReader": "View on Standard Reader",
|
|
111
|
+
"subscribe.panelText": "Read new posts in apps such as {{leaflet}} or {{standardReader}}.",
|
|
112
|
+
"credit": "Powered by {{link}}",
|
|
113
|
+
"callback.signingIn": "Signing you in…",
|
|
114
|
+
"signIn.access": "Your sign-in is kept only in this browser. This site's server never sees it, so it can't post for you. Signing out here cancels it.",
|
|
115
|
+
"signIn.policy": "How this site handles your data",
|
|
116
|
+
"composer.needsPermission": "To comment here as {{handle}}, this site needs one more permission from your Atmosphere account.",
|
|
117
|
+
"comment.hide": "Hide",
|
|
118
|
+
"comment.hideLabel": "Hide this comment",
|
|
119
|
+
"comment.hideConfirm": "Hide this comment here and on Bluesky? You can show it again in the Bluesky app.",
|
|
120
|
+
"comment.hiding": "Hiding…",
|
|
121
|
+
"comment.hidden": "Comment hidden.",
|
|
122
|
+
"comment.hidePermission": "To hide comments here, this site needs one more permission from your account. You can also hide them in the Bluesky app.",
|
|
123
|
+
"comment.deleteElsewhere": "via the Bluesky website",
|
|
124
|
+
"permission.heading": "One more permission",
|
|
125
|
+
"permission.follow": "To follow {{handle}} from this site, it needs one more permission from your Atmosphere account: to follow and unfollow accounts.",
|
|
126
|
+
"permission.post": "To reply and quote from this site, it needs one more permission from your Atmosphere account: to create posts.",
|
|
127
|
+
"permission.react": "To like and repost from this site, it needs one more permission from your Atmosphere account: to like and repost, and to undo that.",
|
|
128
|
+
"permission.approve": "Your account's own site (Bluesky's, for most people) will ask you to approve it, and may ask you to sign in there first. Then you'll come straight back here.",
|
|
129
|
+
"comment.withheld": "Not shown here: its author asks apps not to show their posts to people who aren't signed in."
|
|
95
130
|
}
|
package/locales/es.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"comment.deleteConfirm": "¿Eliminar este comentario de Bluesky?",
|
|
53
53
|
"comment.deleting": "Eliminando…",
|
|
54
54
|
"comment.deleted": "Tu comentario se ha eliminado.",
|
|
55
|
-
"comment.deletePermission": "Para eliminar comentarios aquí, este sitio necesita un permiso más de tu cuenta. También puedes eliminarlo
|
|
55
|
+
"comment.deletePermission": "Para eliminar comentarios aquí, este sitio necesita un permiso más de tu cuenta de Atmosphere. También puedes eliminarlo {{link}}.",
|
|
56
56
|
"comment.continue": "Continuar para aprobar",
|
|
57
57
|
"comment.cancel": "Cancelar",
|
|
58
58
|
"signInFirst.reply": "Inicia sesión para responder.",
|
|
@@ -68,9 +68,8 @@
|
|
|
68
68
|
"messages.accountNotFound": "No encontramos esa cuenta. Revisa el identificador e inténtalo de nuevo.",
|
|
69
69
|
"messages.invalidHandle": "Eso no parece un identificador. Suele ser algo como alice.bsky.social.",
|
|
70
70
|
"messages.signedOut": "Se ha cerrado tu sesión. Vuelve a iniciar sesión.",
|
|
71
|
-
"messages.needsPermission": "
|
|
71
|
+
"messages.needsPermission": "Eso necesita un permiso más de tu cuenta.",
|
|
72
72
|
"messages.notAllowed": "Eso no se puede hacer aquí.",
|
|
73
|
-
"messages.newPermissions": "Este sitio ahora también puede dar me gusta y compartir por ti, así que necesita tu permiso de nuevo. Vuelve a iniciar sesión.",
|
|
74
73
|
"messages.tooLong": "Las publicaciones pueden tener hasta {{max}} caracteres.",
|
|
75
74
|
"messages.tooMany": "Vas muy rápido. Espera un minuto e inténtalo de nuevo.",
|
|
76
75
|
"messages.error": "No ha funcionado. Inténtalo de nuevo.",
|
|
@@ -94,5 +93,22 @@
|
|
|
94
93
|
"follow.following": "Siguiendo",
|
|
95
94
|
"follow.followAs": "Seguir a {{handle}} como {{me}}",
|
|
96
95
|
"follow.followingLabel": "Sigues a {{handle}}",
|
|
97
|
-
"credit": "Funciona con {{link}}"
|
|
96
|
+
"credit": "Funciona con {{link}}",
|
|
97
|
+
"callback.signingIn": "Iniciando sesión…",
|
|
98
|
+
"signIn.access": "Tu sesión se queda en este navegador: el servidor de este sitio nunca la ve ni puede publicar por ti, y al cerrar sesión se cancela.",
|
|
99
|
+
"signIn.policy": "Cómo trata este sitio tus datos",
|
|
100
|
+
"composer.needsPermission": "Para comentar aquí como {{handle}}, este sitio necesita un permiso más de tu cuenta de Atmosphere.",
|
|
101
|
+
"comment.hide": "Ocultar",
|
|
102
|
+
"comment.hideLabel": "Ocultar este comentario",
|
|
103
|
+
"comment.hideConfirm": "¿Ocultar este comentario aquí y en Bluesky? Puedes volver a mostrarlo en la aplicación de Bluesky.",
|
|
104
|
+
"comment.hiding": "Ocultando…",
|
|
105
|
+
"comment.hidden": "Comentario oculto.",
|
|
106
|
+
"comment.hidePermission": "Para ocultar comentarios aquí, este sitio necesita un permiso más de tu cuenta. También puedes ocultarlos en la aplicación de Bluesky.",
|
|
107
|
+
"comment.deleteElsewhere": "en la web de Bluesky",
|
|
108
|
+
"permission.heading": "Un permiso más",
|
|
109
|
+
"permission.follow": "Para seguir a {{handle}} desde este sitio, necesita un permiso más de tu cuenta de Atmosphere: seguir y dejar de seguir cuentas.",
|
|
110
|
+
"permission.post": "Para responder y citar desde este sitio, necesita un permiso más de tu cuenta de Atmosphere: crear publicaciones.",
|
|
111
|
+
"permission.react": "Para dar me gusta y republicar desde este sitio, necesita un permiso más de tu cuenta de Atmosphere: dar me gusta y republicar, y deshacerlo.",
|
|
112
|
+
"permission.approve": "El sitio de tu cuenta (el de Bluesky, para la mayoría) te pedirá que lo apruebes, y quizá que inicies sesión allí primero. Después volverás directamente aquí.",
|
|
113
|
+
"comment.withheld": "No se muestra aquí: su autor pide a las aplicaciones que no muestren sus publicaciones a quien no haya iniciado sesión."
|
|
98
114
|
}
|
package/locales/fr.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"comment.deleteConfirm": "Supprimer ce commentaire de Bluesky ?",
|
|
53
53
|
"comment.deleting": "Suppression…",
|
|
54
54
|
"comment.deleted": "Ton commentaire a été supprimé.",
|
|
55
|
-
"comment.deletePermission": "Pour supprimer des commentaires ici, ce site a besoin d'une autorisation de plus de ton compte.
|
|
55
|
+
"comment.deletePermission": "Pour supprimer des commentaires ici, ce site a besoin d'une autorisation de plus de ton compte Atmosphere. Sinon, tu peux le supprimer {{link}}.",
|
|
56
56
|
"comment.continue": "Continuer pour autoriser",
|
|
57
57
|
"comment.cancel": "Annuler",
|
|
58
58
|
"signInFirst.reply": "Connecte-toi pour répondre.",
|
|
@@ -68,9 +68,8 @@
|
|
|
68
68
|
"messages.accountNotFound": "Ce compte est introuvable. Vérifie l'identifiant et réessaie.",
|
|
69
69
|
"messages.invalidHandle": "Ça ne ressemble pas à un identifiant. C'est généralement quelque chose comme alice.bsky.social.",
|
|
70
70
|
"messages.signedOut": "Tu as été déconnecté. Connecte-toi à nouveau.",
|
|
71
|
-
"messages.needsPermission": "
|
|
71
|
+
"messages.needsPermission": "Cela nécessite une autorisation de plus de ton compte.",
|
|
72
72
|
"messages.notAllowed": "Ce n'est pas possible ici.",
|
|
73
|
-
"messages.newPermissions": "Ce site peut désormais aussi aimer et republier pour toi ; il a donc de nouveau besoin de ton autorisation. Connecte-toi à nouveau.",
|
|
74
73
|
"messages.tooLong": "Les publications peuvent contenir jusqu'à {{max}} caractères.",
|
|
75
74
|
"messages.tooMany": "Tu vas un peu vite. Attends une minute et réessaie.",
|
|
76
75
|
"messages.error": "Ça n'a pas marché. Réessaie.",
|
|
@@ -94,5 +93,22 @@
|
|
|
94
93
|
"follow.following": "Abonné",
|
|
95
94
|
"follow.followAs": "Suivre {{handle}} en tant que {{me}}",
|
|
96
95
|
"follow.followingLabel": "Tu suis {{handle}}",
|
|
97
|
-
"credit": "Propulsé par {{link}}"
|
|
96
|
+
"credit": "Propulsé par {{link}}",
|
|
97
|
+
"callback.signingIn": "Connexion en cours…",
|
|
98
|
+
"signIn.access": "Ta connexion reste dans ce navigateur : le serveur de ce site ne la voit jamais et ne peut pas publier à ta place, et te déconnecter l'annule.",
|
|
99
|
+
"signIn.policy": "Comment ce site traite tes données",
|
|
100
|
+
"composer.needsPermission": "Pour commenter ici en tant que {{handle}}, ce site a besoin d'une autorisation de plus de ton compte Atmosphere.",
|
|
101
|
+
"comment.hide": "Masquer",
|
|
102
|
+
"comment.hideLabel": "Masquer ce commentaire",
|
|
103
|
+
"comment.hideConfirm": "Masquer ce commentaire ici et sur Bluesky ? Tu pourras le réafficher dans l'application Bluesky.",
|
|
104
|
+
"comment.hiding": "Masquage…",
|
|
105
|
+
"comment.hidden": "Commentaire masqué.",
|
|
106
|
+
"comment.hidePermission": "Pour masquer des commentaires ici, ce site a besoin d'une autorisation de plus de ton compte. Tu peux aussi les masquer dans l'application Bluesky.",
|
|
107
|
+
"comment.deleteElsewhere": "sur le site de Bluesky",
|
|
108
|
+
"permission.heading": "Une autorisation de plus",
|
|
109
|
+
"permission.follow": "Pour suivre {{handle}} depuis ce site, il a besoin d'une autorisation de plus de ton compte Atmosphere : suivre des comptes et ne plus les suivre.",
|
|
110
|
+
"permission.post": "Pour répondre et citer depuis ce site, il a besoin d'une autorisation de plus de ton compte Atmosphere : créer des publications.",
|
|
111
|
+
"permission.react": "Pour aimer et republier depuis ce site, il a besoin d'une autorisation de plus de ton compte Atmosphere : aimer et republier, et l'annuler.",
|
|
112
|
+
"permission.approve": "Le site de ton compte (celui de Bluesky, pour la plupart des gens) te demandera de l'approuver, et peut-être de t'y connecter d'abord. Ensuite, tu reviendras directement ici.",
|
|
113
|
+
"comment.withheld": "Non affiché ici : son auteur demande aux applications de ne pas montrer ses publications aux personnes non connectées."
|
|
98
114
|
}
|
package/locales/it.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"comment.deleteConfirm": "Eliminare questo commento da Bluesky?",
|
|
53
53
|
"comment.deleting": "Eliminazione…",
|
|
54
54
|
"comment.deleted": "Il tuo commento è stato eliminato.",
|
|
55
|
-
"comment.deletePermission": "Per eliminare i commenti qui, questo sito ha bisogno di un'altra autorizzazione dal tuo account.
|
|
55
|
+
"comment.deletePermission": "Per eliminare i commenti qui, questo sito ha bisogno di un'altra autorizzazione dal tuo account Atmosphere. In alternativa, puoi eliminarlo {{link}}.",
|
|
56
56
|
"comment.continue": "Continua per approvare",
|
|
57
57
|
"comment.cancel": "Annulla",
|
|
58
58
|
"signInFirst.reply": "Accedi per rispondere.",
|
|
@@ -68,9 +68,8 @@
|
|
|
68
68
|
"messages.accountNotFound": "Non abbiamo trovato quell'account. Controlla l'handle e riprova.",
|
|
69
69
|
"messages.invalidHandle": "Non sembra un handle. Di solito è qualcosa come alice.bsky.social.",
|
|
70
70
|
"messages.signedOut": "Sei stato disconnesso. Accedi di nuovo.",
|
|
71
|
-
"messages.needsPermission": "
|
|
71
|
+
"messages.needsPermission": "Serve un'altra autorizzazione dal tuo account.",
|
|
72
72
|
"messages.notAllowed": "Qui non si può fare.",
|
|
73
|
-
"messages.newPermissions": "Ora questo sito può anche mettere mi piace e ripubblicare per te, quindi ha di nuovo bisogno della tua autorizzazione. Accedi di nuovo.",
|
|
74
73
|
"messages.tooLong": "I post possono avere fino a {{max}} caratteri.",
|
|
75
74
|
"messages.tooMany": "Stai andando un po' veloce. Aspetta un minuto e riprova.",
|
|
76
75
|
"messages.error": "Non ha funzionato. Riprova.",
|
|
@@ -94,5 +93,22 @@
|
|
|
94
93
|
"follow.following": "Segui già",
|
|
95
94
|
"follow.followAs": "Segui {{handle}} come {{me}}",
|
|
96
95
|
"follow.followingLabel": "Segui {{handle}}",
|
|
97
|
-
"credit": "Realizzato con {{link}}"
|
|
96
|
+
"credit": "Realizzato con {{link}}",
|
|
97
|
+
"callback.signingIn": "Accesso in corso…",
|
|
98
|
+
"signIn.access": "Il tuo accesso resta in questo browser: il server di questo sito non lo vede mai e non può pubblicare al posto tuo, e uscendo lo annulli.",
|
|
99
|
+
"signIn.policy": "Come questo sito tratta i tuoi dati",
|
|
100
|
+
"composer.needsPermission": "Per commentare qui come {{handle}}, questo sito ha bisogno di un'altra autorizzazione dal tuo account Atmosphere.",
|
|
101
|
+
"comment.hide": "Nascondi",
|
|
102
|
+
"comment.hideLabel": "Nascondi questo commento",
|
|
103
|
+
"comment.hideConfirm": "Nascondere questo commento qui e su Bluesky? Puoi mostrarlo di nuovo nell'app di Bluesky.",
|
|
104
|
+
"comment.hiding": "Nascondo…",
|
|
105
|
+
"comment.hidden": "Commento nascosto.",
|
|
106
|
+
"comment.hidePermission": "Per nascondere i commenti qui, questo sito ha bisogno di un'altra autorizzazione dal tuo account. Puoi anche nasconderli nell'app di Bluesky.",
|
|
107
|
+
"comment.deleteElsewhere": "dal sito di Bluesky",
|
|
108
|
+
"permission.heading": "Un'altra autorizzazione",
|
|
109
|
+
"permission.follow": "Per seguire {{handle}} da questo sito, serve un'altra autorizzazione dal tuo account Atmosphere: seguire account e smettere di seguirli.",
|
|
110
|
+
"permission.post": "Per rispondere e citare da questo sito, serve un'altra autorizzazione dal tuo account Atmosphere: creare post.",
|
|
111
|
+
"permission.react": "Per mettere mi piace e ripubblicare da questo sito, serve un'altra autorizzazione dal tuo account Atmosphere: mettere mi piace e ripubblicare, e annullarlo.",
|
|
112
|
+
"permission.approve": "Il sito del tuo account (quello di Bluesky, per la maggior parte delle persone) ti chiederà di approvare, e forse prima di accedere. Poi tornerai subito qui.",
|
|
113
|
+
"comment.withheld": "Non mostrato qui: l'autore chiede alle app di non mostrare i suoi post a chi non ha effettuato l'accesso."
|
|
98
114
|
}
|
package/locales/ja.json
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"comment.deleteConfirm": "このコメントをBlueskyから削除しますか?",
|
|
47
47
|
"comment.deleting": "削除中…",
|
|
48
48
|
"comment.deleted": "コメントを削除しました。",
|
|
49
|
-
"comment.deletePermission": "
|
|
49
|
+
"comment.deletePermission": "ここでコメントを削除するには、このサイトにAtmosphereアカウントからの許可がもうひとつ必要です。{{link}}削除することもできます。",
|
|
50
50
|
"comment.continue": "許可に進む",
|
|
51
51
|
"comment.cancel": "キャンセル",
|
|
52
52
|
"signInFirst.reply": "返信するにはログインしてください。",
|
|
@@ -62,9 +62,8 @@
|
|
|
62
62
|
"messages.accountNotFound": "そのアカウントが見つかりませんでした。ハンドルを確認して、もう一度お試しください。",
|
|
63
63
|
"messages.invalidHandle": "ハンドルではないようです。通常は alice.bsky.social のような形式です。",
|
|
64
64
|
"messages.signedOut": "ログアウトされました。もう一度ログインしてください。",
|
|
65
|
-
"messages.needsPermission": "
|
|
65
|
+
"messages.needsPermission": "それにはアカウントからの許可がもうひとつ必要です。",
|
|
66
66
|
"messages.notAllowed": "ここではその操作はできません。",
|
|
67
|
-
"messages.newPermissions": "このサイトはいいねとリポストもできるようになったため、改めて許可が必要です。もう一度ログインしてください。",
|
|
68
67
|
"messages.tooLong": "投稿は{{max}}文字までです。",
|
|
69
68
|
"messages.tooMany": "操作が早すぎます。1分待ってから、もう一度お試しください。",
|
|
70
69
|
"messages.error": "うまくいきませんでした。もう一度お試しください。",
|
|
@@ -88,5 +87,22 @@
|
|
|
88
87
|
"follow.following": "フォロー中",
|
|
89
88
|
"follow.followAs": "{{me}}として{{handle}}をフォロー",
|
|
90
89
|
"follow.followingLabel": "{{handle}}をフォローしています",
|
|
91
|
-
"credit": "{{link}}で動作しています"
|
|
90
|
+
"credit": "{{link}}で動作しています",
|
|
91
|
+
"callback.signingIn": "ログインしています…",
|
|
92
|
+
"signIn.access": "ログイン情報はこのブラウザの中だけに保存されます。このサイトのサーバーがそれを見ることはなく、あなたの代わりに投稿することもできません。ログアウトすると取り消されます。",
|
|
93
|
+
"signIn.policy": "このサイトでのデータの取り扱い",
|
|
94
|
+
"composer.needsPermission": "ここで{{handle}}としてコメントするには、このサイトにAtmosphereアカウントからの許可がもうひとつ必要です。",
|
|
95
|
+
"comment.hide": "非表示",
|
|
96
|
+
"comment.hideLabel": "このコメントを非表示にする",
|
|
97
|
+
"comment.hideConfirm": "このコメントをこことBlueskyで非表示にしますか?Blueskyのアプリで再表示できます。",
|
|
98
|
+
"comment.hiding": "非表示にしています…",
|
|
99
|
+
"comment.hidden": "コメントを非表示にしました。",
|
|
100
|
+
"comment.hidePermission": "ここでコメントを非表示にするには、このサイトにアカウントからの許可がもうひとつ必要です。Blueskyのアプリでも非表示にできます。",
|
|
101
|
+
"comment.deleteElsewhere": "Blueskyのウェブサイトから",
|
|
102
|
+
"permission.heading": "もうひとつの許可",
|
|
103
|
+
"permission.follow": "このサイトから{{handle}}をフォローするには、Atmosphereアカウントからの許可がもうひとつ必要です(アカウントのフォローとフォロー解除)。",
|
|
104
|
+
"permission.post": "このサイトから返信や引用をするには、Atmosphereアカウントからの許可がもうひとつ必要です(投稿の作成)。",
|
|
105
|
+
"permission.react": "このサイトからいいねやリポストをするには、Atmosphereアカウントからの許可がもうひとつ必要です(いいねとリポスト、その取り消し)。",
|
|
106
|
+
"permission.approve": "アカウントのサイト(ほとんどの方はBluesky)で承認を求められます。先にそこでログインを求められることもあります。その後、すぐにここへ戻ります。",
|
|
107
|
+
"comment.withheld": "ここには表示されません:投稿者は、ログインしていない人に投稿を表示しないようアプリに求めています。"
|
|
92
108
|
}
|
package/locales/nl.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"comment.deleteConfirm": "Deze reactie van Bluesky verwijderen?",
|
|
50
50
|
"comment.deleting": "Verwijderen…",
|
|
51
51
|
"comment.deleted": "Je reactie is verwijderd.",
|
|
52
|
-
"comment.deletePermission": "Om hier reacties te verwijderen, heeft deze site nog één toestemming van je account nodig. Je kunt hem ook
|
|
52
|
+
"comment.deletePermission": "Om hier reacties te verwijderen, heeft deze site nog één toestemming van je Atmosphere-account nodig. Je kunt hem ook {{link}} verwijderen.",
|
|
53
53
|
"comment.continue": "Doorgaan om toe te staan",
|
|
54
54
|
"comment.cancel": "Annuleren",
|
|
55
55
|
"signInFirst.reply": "Log in om te reageren.",
|
|
@@ -65,9 +65,8 @@
|
|
|
65
65
|
"messages.accountNotFound": "We konden dat account niet vinden. Controleer de handle en probeer het opnieuw.",
|
|
66
66
|
"messages.invalidHandle": "Dat lijkt geen handle. Meestal is het zoiets als alice.bsky.social.",
|
|
67
67
|
"messages.signedOut": "Je bent uitgelogd. Log opnieuw in.",
|
|
68
|
-
"messages.needsPermission": "
|
|
68
|
+
"messages.needsPermission": "Daarvoor is nog één toestemming van je account nodig.",
|
|
69
69
|
"messages.notAllowed": "Dat kan hier niet.",
|
|
70
|
-
"messages.newPermissions": "Deze site kan nu ook voor je liken en reposten en heeft daarom opnieuw je toestemming nodig. Log opnieuw in.",
|
|
71
70
|
"messages.tooLong": "Berichten mogen maximaal {{max}} tekens lang zijn.",
|
|
72
71
|
"messages.tooMany": "Je gaat wel snel. Wacht een minuut en probeer het opnieuw.",
|
|
73
72
|
"messages.error": "Dat is niet gelukt. Probeer het opnieuw.",
|
|
@@ -91,5 +90,22 @@
|
|
|
91
90
|
"follow.following": "Volgend",
|
|
92
91
|
"follow.followAs": "{{handle}} volgen als {{me}}",
|
|
93
92
|
"follow.followingLabel": "Je volgt {{handle}}",
|
|
94
|
-
"credit": "Mogelijk gemaakt door {{link}}"
|
|
93
|
+
"credit": "Mogelijk gemaakt door {{link}}",
|
|
94
|
+
"callback.signingIn": "Bezig met inloggen…",
|
|
95
|
+
"signIn.access": "Je login blijft in deze browser: de server van deze site ziet hem nooit en kan niet namens jou posten, en uitloggen trekt hem in.",
|
|
96
|
+
"signIn.policy": "Hoe deze site met je gegevens omgaat",
|
|
97
|
+
"composer.needsPermission": "Om hier als {{handle}} te reageren, heeft deze site nog één toestemming van je Atmosphere-account nodig.",
|
|
98
|
+
"comment.hide": "Verbergen",
|
|
99
|
+
"comment.hideLabel": "Deze reactie verbergen",
|
|
100
|
+
"comment.hideConfirm": "Deze reactie hier en op Bluesky verbergen? Je kunt hem in de Bluesky-app weer tonen.",
|
|
101
|
+
"comment.hiding": "Verbergen…",
|
|
102
|
+
"comment.hidden": "Reactie verborgen.",
|
|
103
|
+
"comment.hidePermission": "Om hier reacties te verbergen, heeft deze site nog één toestemming van je account nodig. Je kunt ze ook in de Bluesky-app verbergen.",
|
|
104
|
+
"comment.deleteElsewhere": "via de Bluesky-website",
|
|
105
|
+
"permission.heading": "Nog één toestemming",
|
|
106
|
+
"permission.follow": "Om {{handle}} vanaf deze site te volgen, is nog één toestemming van je Atmosphere-account nodig: accounts volgen en ontvolgen.",
|
|
107
|
+
"permission.post": "Om vanaf deze site te reageren en te citeren, is nog één toestemming van je Atmosphere-account nodig: berichten plaatsen.",
|
|
108
|
+
"permission.react": "Om vanaf deze site te liken en te reposten, is nog één toestemming van je Atmosphere-account nodig: liken en reposten, en dat ongedaan maken.",
|
|
109
|
+
"permission.approve": "De site van je account (voor de meeste mensen die van Bluesky) vraagt je dit goed te keuren, en misschien eerst daar in te loggen. Daarna kom je meteen hierheen terug.",
|
|
110
|
+
"comment.withheld": "Hier niet getoond: de schrijver vraagt apps om zijn of haar berichten niet te tonen aan mensen die niet zijn ingelogd."
|
|
95
111
|
}
|
package/locales/pt.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"comment.deleteConfirm": "Excluir este comentário do Bluesky?",
|
|
53
53
|
"comment.deleting": "Excluindo…",
|
|
54
54
|
"comment.deleted": "Seu comentário foi excluído.",
|
|
55
|
-
"comment.deletePermission": "Para excluir comentários aqui, este site precisa de mais uma permissão da sua conta.
|
|
55
|
+
"comment.deletePermission": "Para excluir comentários aqui, este site precisa de mais uma permissão da sua conta Atmosphere. Ou você pode excluí-lo {{link}}.",
|
|
56
56
|
"comment.continue": "Continuar para aprovar",
|
|
57
57
|
"comment.cancel": "Cancelar",
|
|
58
58
|
"signInFirst.reply": "Entre para responder.",
|
|
@@ -68,9 +68,8 @@
|
|
|
68
68
|
"messages.accountNotFound": "Não encontramos essa conta. Confira o identificador e tente de novo.",
|
|
69
69
|
"messages.invalidHandle": "Isso não parece um identificador. Normalmente é algo como alice.bsky.social.",
|
|
70
70
|
"messages.signedOut": "Você foi desconectado. Entre de novo.",
|
|
71
|
-
"messages.needsPermission": "
|
|
71
|
+
"messages.needsPermission": "Isso precisa de mais uma permissão da sua conta.",
|
|
72
72
|
"messages.notAllowed": "Isso não pode ser feito aqui.",
|
|
73
|
-
"messages.newPermissions": "Este site agora também pode curtir e repostar por você, então precisa da sua permissão de novo. Entre de novo.",
|
|
74
73
|
"messages.tooLong": "As publicações podem ter até {{max}} caracteres.",
|
|
75
74
|
"messages.tooMany": "Você está indo rápido demais. Espere um minuto e tente de novo.",
|
|
76
75
|
"messages.error": "Não deu certo. Tente de novo.",
|
|
@@ -94,5 +93,22 @@
|
|
|
94
93
|
"follow.following": "Seguindo",
|
|
95
94
|
"follow.followAs": "Seguir {{handle}} como {{me}}",
|
|
96
95
|
"follow.followingLabel": "Você segue {{handle}}",
|
|
97
|
-
"credit": "Feito com {{link}}"
|
|
96
|
+
"credit": "Feito com {{link}}",
|
|
97
|
+
"callback.signingIn": "Entrando…",
|
|
98
|
+
"signIn.access": "Seu login fica neste navegador: o servidor deste site nunca o vê nem pode publicar por você, e sair o cancela.",
|
|
99
|
+
"signIn.policy": "Como este site trata seus dados",
|
|
100
|
+
"composer.needsPermission": "Para comentar aqui como {{handle}}, este site precisa de mais uma permissão da sua conta Atmosphere.",
|
|
101
|
+
"comment.hide": "Ocultar",
|
|
102
|
+
"comment.hideLabel": "Ocultar este comentário",
|
|
103
|
+
"comment.hideConfirm": "Ocultar este comentário aqui e no Bluesky? Você pode mostrá-lo de novo no app do Bluesky.",
|
|
104
|
+
"comment.hiding": "Ocultando…",
|
|
105
|
+
"comment.hidden": "Comentário ocultado.",
|
|
106
|
+
"comment.hidePermission": "Para ocultar comentários aqui, este site precisa de mais uma permissão da sua conta. Você também pode ocultá-los no app do Bluesky.",
|
|
107
|
+
"comment.deleteElsewhere": "no site do Bluesky",
|
|
108
|
+
"permission.heading": "Mais uma permissão",
|
|
109
|
+
"permission.follow": "Para seguir {{handle}} por este site, ele precisa de mais uma permissão da sua conta Atmosphere: seguir e deixar de seguir contas.",
|
|
110
|
+
"permission.post": "Para responder e citar por este site, ele precisa de mais uma permissão da sua conta Atmosphere: criar publicações.",
|
|
111
|
+
"permission.react": "Para curtir e repostar por este site, ele precisa de mais uma permissão da sua conta Atmosphere: curtir e repostar, e desfazer isso.",
|
|
112
|
+
"permission.approve": "O site da sua conta (o do Bluesky, para a maioria das pessoas) vai pedir que você aprove, e talvez que entre lá primeiro. Depois você volta direto para cá.",
|
|
113
|
+
"comment.withheld": "Não exibido aqui: o autor pede aos apps que não mostrem suas publicações a quem não está conectado."
|
|
98
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-atmosphere-comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Atmosphere Comments for Astro: Bluesky comments, likes, reposts and quote posts for Astro sites; readers sign in with their Atmosphere (AT Protocol) account and respond without leaving the page.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,14 +34,10 @@
|
|
|
34
34
|
"./AtmosphereReactions.astro": "./components/AtmosphereReactions.astro"
|
|
35
35
|
},
|
|
36
36
|
"types": "./index.d.ts",
|
|
37
|
-
"bin": {
|
|
38
|
-
"astro-atmosphere-comments": "bin/cli.mjs"
|
|
39
|
-
},
|
|
40
37
|
"files": [
|
|
41
38
|
"index.mjs",
|
|
42
39
|
"index.d.ts",
|
|
43
40
|
"virtual.d.ts",
|
|
44
|
-
"bin",
|
|
45
41
|
"components",
|
|
46
42
|
"lib",
|
|
47
43
|
"routes",
|
|
@@ -59,7 +55,6 @@
|
|
|
59
55
|
},
|
|
60
56
|
"dependencies": {
|
|
61
57
|
"@atproto/api": "^0.25.0",
|
|
62
|
-
"@atproto/oauth-client": "^0.
|
|
63
|
-
"@atproto/oauth-client-node": "^0.5.13"
|
|
58
|
+
"@atproto/oauth-client-browser": "^0.5.11"
|
|
64
59
|
}
|
|
65
60
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Where a reader's account host sends them back after they approve (or
|
|
3
|
+
// decline) signing in. The browser finishes signing in here, keeping the
|
|
4
|
+
// sign-in in its own storage, then goes back to the page the reader was on
|
|
5
|
+
// (lib/client/account.ts). The approval arrives in the address's #fragment,
|
|
6
|
+
// which browsers never send to the server.
|
|
7
|
+
import settings from "../lib/settings";
|
|
8
|
+
import { messagesFor, pickLocale, translate } from "../lib/i18n";
|
|
9
|
+
|
|
10
|
+
const lang = pickLocale(undefined, settings.locale, Astro.currentLocale);
|
|
11
|
+
const t = (key: string) => translate(messagesFor(lang, settings.messages), lang, key);
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!doctype html>
|
|
15
|
+
<html lang={lang}>
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8" />
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
+
<meta name="robots" content="noindex" />
|
|
20
|
+
<meta name="referrer" content="no-referrer" />
|
|
21
|
+
<title>{t("callback.signingIn")}</title>
|
|
22
|
+
<style>
|
|
23
|
+
:root {
|
|
24
|
+
color-scheme: light dark;
|
|
25
|
+
}
|
|
26
|
+
body {
|
|
27
|
+
display: grid;
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
margin: 0;
|
|
30
|
+
place-items: center;
|
|
31
|
+
font: 1rem/1.5 system-ui, sans-serif;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<p role="status">{t("callback.signingIn")}</p>
|
|
37
|
+
<script>
|
|
38
|
+
import { finishSignIn } from "../lib/client/account";
|
|
39
|
+
void finishSignIn();
|
|
40
|
+
</script>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The site's OAuth client metadata. Account hosts fetch this address (it is
|
|
2
2
|
// the site's client ID) to learn its name, logo, callback address and scope.
|
|
3
3
|
import type { APIRoute } from "astro";
|
|
4
|
-
import { clientMetadata } from "../lib/
|
|
4
|
+
import { clientMetadata } from "../lib/permissions";
|
|
5
|
+
import settings from "../lib/settings";
|
|
5
6
|
|
|
6
7
|
export const GET: APIRoute = () =>
|
|
7
|
-
Response.json(clientMetadata(), { headers: { "Cache-Control": "public, max-age=600" } });
|
|
8
|
+
Response.json(clientMetadata(settings), { headers: { "Cache-Control": "public, max-age=600" } });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Whether an account follows one of the site's own accounts, for the Follow
|
|
2
|
+
// button: ?actor=<reader's DID>&subject=<DID in allowedAuthors> gives
|
|
3
|
+
// { following } (the follow record's AT URI, or ""). From Bluesky's public
|
|
4
|
+
// AppView: who follows whom is public.
|
|
5
|
+
import type { APIRoute } from "astro";
|
|
6
|
+
import { addressOf, json, rateLimit } from "../lib/http";
|
|
7
|
+
import settings from "../lib/settings";
|
|
8
|
+
|
|
9
|
+
const DID = /^did:[a-z0-9]+:[a-zA-Z0-9._:%-]+$/;
|
|
10
|
+
const perAddress = rateLimit(30, 300);
|
|
11
|
+
|
|
12
|
+
export const GET: APIRoute = async (context) => {
|
|
13
|
+
const actor = context.url.searchParams.get("actor") ?? "";
|
|
14
|
+
const subject = context.url.searchParams.get("subject") ?? "";
|
|
15
|
+
if (!DID.test(actor) || !settings.allowedAuthors.includes(subject)) return json({ error: "not-allowed" }, 400);
|
|
16
|
+
if (perAddress(addressOf(context))) return json({ error: "too-many" }, 429);
|
|
17
|
+
try {
|
|
18
|
+
const query = new URLSearchParams({ actor, others: subject });
|
|
19
|
+
const response = await fetch(`https://public.api.bsky.app/xrpc/app.bsky.graph.getRelationships?${query}`, {
|
|
20
|
+
signal: AbortSignal.timeout(5000),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) throw new Error(`getRelationships ${response.status}`);
|
|
23
|
+
const data = (await response.json()) as { relationships?: Array<{ did?: string; following?: string }> };
|
|
24
|
+
return json({ following: data.relationships?.find((relationship) => relationship.did === subject)?.following ?? "" });
|
|
25
|
+
} catch {
|
|
26
|
+
return json({ error: "unavailable" }, 502);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Looks up an account for signing in: ?id=<handle or DID> gives its DID, its
|
|
2
|
+
// DID document (which names the reader's account host) and its handle, as
|
|
3
|
+
// the browser's OAuth library needs (lib/client/account.ts). Done here, so a
|
|
4
|
+
// reader's browser contacts only their own account host while signing in,
|
|
5
|
+
// not Bluesky's servers or the PLC directory. Nothing is stored; answers are
|
|
6
|
+
// remembered for ten minutes, and requests are limited per address.
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
import { addressOf, json, rateLimit } from "../lib/http";
|
|
9
|
+
|
|
10
|
+
const APPVIEW = "https://public.api.bsky.app/xrpc/";
|
|
11
|
+
const HANDLE = /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$/;
|
|
12
|
+
const DID_PLC = /^did:plc:[a-z2-7]{24}$/;
|
|
13
|
+
const DID_WEB = /^did:web:([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$/;
|
|
14
|
+
const REMEMBER_MS = 10 * 60 * 1000;
|
|
15
|
+
|
|
16
|
+
const perAddress = rateLimit(20, 200);
|
|
17
|
+
const overall = rateLimit(300, 6000);
|
|
18
|
+
const recent = new Map<string, { answer: Identity; at: number }>();
|
|
19
|
+
|
|
20
|
+
type DidDocument = { id: string; alsoKnownAs?: string[]; service?: { id: string; type: string; serviceEndpoint: unknown }[] };
|
|
21
|
+
type Identity = { did: string; didDoc: DidDocument; handle: string };
|
|
22
|
+
|
|
23
|
+
async function getJson<T>(url: string): Promise<T> {
|
|
24
|
+
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
|
|
25
|
+
if (!response.ok) throw new Error(`${response.status}`);
|
|
26
|
+
if (Number(response.headers.get("content-length") || 0) > 100_000) throw new Error("too big");
|
|
27
|
+
return (await response.json()) as T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function didForHandle(handle: string): Promise<string> {
|
|
31
|
+
const { did } = await getJson<{ did?: string }>(`${APPVIEW}com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`);
|
|
32
|
+
if (!did || !(DID_PLC.test(did) || DID_WEB.test(did))) throw new Error("no DID");
|
|
33
|
+
return did;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function documentFor(did: string): Promise<DidDocument> {
|
|
37
|
+
const document = await getJson<DidDocument>(
|
|
38
|
+
did.startsWith("did:web:") ? `https://${did.slice("did:web:".length)}/.well-known/did.json` : `https://plc.directory/${did}`,
|
|
39
|
+
);
|
|
40
|
+
if (document.id !== did) throw new Error("wrong document");
|
|
41
|
+
return document;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function lookUp(id: string): Promise<Identity> {
|
|
45
|
+
const did = id.startsWith("did:") ? id : await didForHandle(id);
|
|
46
|
+
const didDoc = await documentFor(did);
|
|
47
|
+
// The handle counts only if the document claims it and it points back here.
|
|
48
|
+
const claimed = didDoc.alsoKnownAs?.find((alias) => alias.startsWith("at://"))?.slice("at://".length).toLowerCase();
|
|
49
|
+
let handle = "handle.invalid";
|
|
50
|
+
if (claimed && claimed === id) handle = claimed;
|
|
51
|
+
else if (claimed && HANDLE.test(claimed)) handle = (await didForHandle(claimed).catch(() => "")) === did ? claimed : handle;
|
|
52
|
+
return { did, didDoc, handle };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const GET: APIRoute = async (context) => {
|
|
56
|
+
const id = (context.url.searchParams.get("id") ?? "").trim().toLowerCase();
|
|
57
|
+
if (!(HANDLE.test(id) || DID_PLC.test(id) || DID_WEB.test(id)) || id.length > 253) return json({ error: "bad-request" }, 400);
|
|
58
|
+
const known = recent.get(id);
|
|
59
|
+
if (known && Date.now() - known.at < REMEMBER_MS) return json(known.answer);
|
|
60
|
+
if (perAddress(addressOf(context)) || overall("all")) return json({ error: "too-many" }, 429);
|
|
61
|
+
try {
|
|
62
|
+
const answer = await lookUp(id);
|
|
63
|
+
if (recent.size >= 500) recent.delete(recent.keys().next().value!);
|
|
64
|
+
recent.set(id, { answer, at: Date.now() });
|
|
65
|
+
return json(answer);
|
|
66
|
+
} catch {
|
|
67
|
+
return json({ error: "not-found" }, 404);
|
|
68
|
+
}
|
|
69
|
+
};
|
package/routes/media.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
// Passes on a few images from Bluesky's image server, so readers' browsers
|
|
2
2
|
// never load anything from Bluesky directly:
|
|
3
|
-
// ?avatar=<did>
|
|
4
|
-
//
|
|
3
|
+
// ?avatar=<did> a site account's avatar, or one shown recently (in a
|
|
4
|
+
// thread, suggested for an @mention, or a signed-in reader's
|
|
5
|
+
// own, looked up by routes/profile.ts);
|
|
5
6
|
// ?thumb=<uri> the link-card picture of one of the site's announcements.
|
|
6
7
|
// Nothing else can be fetched through it.
|
|
7
8
|
import type { APIRoute } from "astro";
|
|
8
9
|
import { profileFor } from "../lib/profile";
|
|
9
10
|
import { quotedPost } from "../lib/quoted";
|
|
10
|
-
import { isSitePost } from "../lib/
|
|
11
|
+
import { isSitePost } from "../lib/uris";
|
|
11
12
|
import settings from "../lib/settings";
|
|
12
13
|
import { knownAvatar } from "../lib/avatars";
|
|
13
|
-
import { signedInDid } from "../lib/visitor";
|
|
14
14
|
|
|
15
15
|
const IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
|
|
16
16
|
const MAX_BYTES = 3 * 1024 * 1024;
|
|
17
17
|
|
|
18
18
|
const notFound = () => new Response("Not found", { status: 404 });
|
|
19
19
|
|
|
20
|
-
export const GET: APIRoute = async ({ url
|
|
20
|
+
export const GET: APIRoute = async ({ url }) => {
|
|
21
21
|
const avatar = url.searchParams.get("avatar");
|
|
22
22
|
const thumb = url.searchParams.get("thumb");
|
|
23
23
|
let source: string | undefined;
|
|
24
|
-
let shared = true;
|
|
25
24
|
|
|
26
25
|
if (avatar) {
|
|
27
26
|
const isSiteAccount = settings.allowedAuthors.includes(avatar);
|
|
@@ -29,10 +28,9 @@ export const GET: APIRoute = async ({ url, cookies }) => {
|
|
|
29
28
|
if (recentlyShown) {
|
|
30
29
|
source = recentlyShown;
|
|
31
30
|
} else {
|
|
32
|
-
if (!isSiteAccount
|
|
31
|
+
if (!isSiteAccount) return notFound();
|
|
33
32
|
source = (await profileFor(avatar)).avatarSource;
|
|
34
33
|
}
|
|
35
|
-
shared = isSiteAccount || !!recentlyShown;
|
|
36
34
|
} else if (thumb && isSitePost(thumb)) {
|
|
37
35
|
source = (await quotedPost(thumb).catch(() => null))?.external?.thumbSource;
|
|
38
36
|
}
|
|
@@ -47,8 +45,7 @@ export const GET: APIRoute = async ({ url, cookies }) => {
|
|
|
47
45
|
return new Response(body, {
|
|
48
46
|
headers: {
|
|
49
47
|
"Content-Type": type,
|
|
50
|
-
|
|
51
|
-
"Cache-Control": shared ? "public, max-age=3600" : "private, max-age=600",
|
|
48
|
+
"Cache-Control": "public, max-age=3600",
|
|
52
49
|
"Content-Security-Policy": "default-src 'none'; sandbox",
|
|
53
50
|
"X-Content-Type-Options": "nosniff",
|
|
54
51
|
},
|