mirror of https://gitlab.com/bashrc2/epicyon
Merge
commit
2fb1cdbac2
131
daemon.py
131
daemon.py
|
|
@ -181,6 +181,11 @@ from webapp_search import htmlSearchEmojiTextEntry
|
|||
from webapp_search import htmlSearch
|
||||
from webapp_hashtagswarm import getHashtagCategoriesFeed
|
||||
from webapp_hashtagswarm import htmlSearchHashtagCategory
|
||||
from webapp_welcome import welcomeScreenIsComplete
|
||||
from webapp_welcome import htmlWelcomeScreen
|
||||
from webapp_welcome import isWelcomeScreenComplete
|
||||
from webapp_welcome_profile import htmlWelcomeProfile
|
||||
from webapp_welcome_final import htmlWelcomeFinal
|
||||
from shares import getSharesFeedForPerson
|
||||
from shares import addShare
|
||||
from shares import removeShare
|
||||
|
|
@ -4042,6 +4047,21 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
' image or font could not be saved to ' +
|
||||
postImageFilename)
|
||||
|
||||
postBytesStr = postBytes.decode('utf-8')
|
||||
redirectPath = ''
|
||||
checkNameAndBio = False
|
||||
if 'name="previewAvatar"' in postBytesStr:
|
||||
redirectPath = '/welcome_profile'
|
||||
elif 'name="initialWelcomeScreen"' in postBytesStr:
|
||||
redirectPath = '/welcome'
|
||||
elif 'name="finalWelcomeScreen"' in postBytesStr:
|
||||
checkNameAndBio = True
|
||||
redirectPath = '/welcome_final'
|
||||
elif 'name="welcomeCompleteButton"' in postBytesStr:
|
||||
redirectPath = '/' + self.server.defaultTimeline
|
||||
welcomeScreenIsComplete(self.server.baseDir, nickname,
|
||||
self.server.domain)
|
||||
|
||||
# extract all of the text fields into a dict
|
||||
fields = \
|
||||
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||
|
|
@ -4172,7 +4192,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
actorJson['name'] = displayName
|
||||
else:
|
||||
actorJson['name'] = nickname
|
||||
if checkNameAndBio:
|
||||
redirectPath = 'previewAvatar'
|
||||
actorChanged = True
|
||||
else:
|
||||
if checkNameAndBio:
|
||||
redirectPath = 'previewAvatar'
|
||||
|
||||
# change media instance status
|
||||
if fields.get('mediaInstance'):
|
||||
|
|
@ -4535,10 +4560,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
for tagName, tag in actorTags.items():
|
||||
actorJson['tag'].append(tag)
|
||||
actorChanged = True
|
||||
else:
|
||||
if checkNameAndBio:
|
||||
redirectPath = 'previewAvatar'
|
||||
else:
|
||||
if actorJson['summary']:
|
||||
actorJson['summary'] = ''
|
||||
actorChanged = True
|
||||
if checkNameAndBio:
|
||||
redirectPath = 'previewAvatar'
|
||||
|
||||
adminNickname = \
|
||||
getConfigParam(baseDir, 'admin')
|
||||
|
|
@ -5022,7 +5049,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self._redirect_headers(actorStr + redirectPath,
|
||||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _progressiveWebAppManifest(self, callingDomain: str,
|
||||
|
|
@ -10345,6 +10373,23 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if '/users/' in self.path:
|
||||
usersInPath = True
|
||||
|
||||
# redirect to the welcome screen
|
||||
if htmlGET and authorized and usersInPath and \
|
||||
'/welcome' not in self.path:
|
||||
nickname = self.path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
if '?' in nickname:
|
||||
nickname = nickname.split('?')[0]
|
||||
if nickname == self.authorizedNickname and \
|
||||
self.path != '/users/' + nickname:
|
||||
if not isWelcomeScreenComplete(self.server.baseDir,
|
||||
nickname,
|
||||
self.server.domain):
|
||||
self._redirect_headers('/users/' + nickname + '/welcome',
|
||||
cookie, callingDomain)
|
||||
return
|
||||
|
||||
if not htmlGET and \
|
||||
usersInPath and self.path.endswith('/pinned'):
|
||||
nickname = self.path.split('/users/')[1]
|
||||
|
|
@ -10670,6 +10715,84 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'show about screen done',
|
||||
'robots txt')
|
||||
|
||||
# the initial welcome screen after first logging in
|
||||
if htmlGET and authorized and \
|
||||
'/users/' in self.path and self.path.endswith('/welcome'):
|
||||
nickname = self.path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
if not isWelcomeScreenComplete(self.server.baseDir,
|
||||
nickname,
|
||||
self.server.domain):
|
||||
msg = \
|
||||
htmlWelcomeScreen(self.server.baseDir, nickname,
|
||||
self.server.systemLanguage,
|
||||
self.server.translate)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._login_headers('text/html', msglen, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'following accounts done',
|
||||
'show welcome screen')
|
||||
return
|
||||
else:
|
||||
self.path = self.path.replace('/welcome', '')
|
||||
|
||||
# the welcome screen which allows you to set an avatar image
|
||||
if htmlGET and authorized and \
|
||||
'/users/' in self.path and self.path.endswith('/welcome_profile'):
|
||||
nickname = self.path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
if not isWelcomeScreenComplete(self.server.baseDir,
|
||||
nickname,
|
||||
self.server.domain):
|
||||
msg = \
|
||||
htmlWelcomeProfile(self.server.baseDir, nickname,
|
||||
self.server.domain,
|
||||
self.server.httpPrefix,
|
||||
self.server.domainFull,
|
||||
self.server.systemLanguage,
|
||||
self.server.translate)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._login_headers('text/html', msglen, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show welcome screen',
|
||||
'show welcome profile screen')
|
||||
return
|
||||
else:
|
||||
self.path = self.path.replace('/welcome_profile', '')
|
||||
|
||||
# the final welcome screen
|
||||
if htmlGET and authorized and \
|
||||
'/users/' in self.path and self.path.endswith('/welcome_final'):
|
||||
nickname = self.path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
if not isWelcomeScreenComplete(self.server.baseDir,
|
||||
nickname,
|
||||
self.server.domain):
|
||||
msg = \
|
||||
htmlWelcomeFinal(self.server.baseDir, nickname,
|
||||
self.server.domain,
|
||||
self.server.httpPrefix,
|
||||
self.server.domainFull,
|
||||
self.server.systemLanguage,
|
||||
self.server.translate)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._login_headers('text/html', msglen, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show welcome profile screen',
|
||||
'show welcome final screen')
|
||||
return
|
||||
else:
|
||||
self.path = self.path.replace('/welcome_final', '')
|
||||
|
||||
# if not authorized then show the login screen
|
||||
if htmlGET and self.path != '/login' and \
|
||||
not self._pathIsImage(self.path) and \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
### تهانينا!
|
||||
أنت الآن جاهز لبدء استخدام Epicyon. هذه مساحة اجتماعية خاضعة للإشراف ، لذا يرجى التأكد من الالتزام بـ [شروط الخدمة](/terms) الخاصة بنا ، واستمتع.
|
||||
|
||||
#### تلميحات
|
||||
استخدم رمز **المكبر** 🔍 للبحث عن مقابض الكون المشترك ومتابعة الأشخاص.
|
||||
|
||||
يؤدي تحديد **الشعار في الجزء العلوي** من الشاشة إلى التبديل بين عرض المخطط الزمني وملف التعريف الخاص بك.
|
||||
|
||||
لن يتم تحديث الشاشة تلقائيًا عند وصول المنشورات ، لذا استخدم **F5** أو زر البريد الوارد للتحديث.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Enhorabona!
|
||||
Ja esteu a punt per començar a utilitzar Epicyon. Aquest és un espai social moderat, així que assegureu-vos de complir les nostres [condicions del servei](/terms) i divertir-vos.
|
||||
|
||||
#### Consells
|
||||
Utilitzeu la icona de **lupa** 🔍 per cercar manetes fedivers i seguir les persones.
|
||||
|
||||
Si seleccioneu el **bàner a la part superior** de la pantalla es canvia entre la visualització de la línia de temps i el vostre perfil.
|
||||
|
||||
La pantalla no s'actualitzarà automàticament quan arribin les publicacions, així que utilitzeu **F5** o el botó **Safata d'entrada** per actualitzar.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Llongyfarchiadau!
|
||||
Rydych nawr yn barod i ddechrau defnyddio Epicyon. Mae hwn yn ofod cymdeithasol wedi'i gymedroli, felly gwnewch yn siŵr eich bod yn cadw at ein [telerau gwasanaeth](/terms), a chael hwyl.
|
||||
|
||||
#### Awgrymiadau
|
||||
Defnyddiwch yr eicon **chwyddwydr** 🔍 i chwilio am ddolenni bwydo a dilyn pobl.
|
||||
|
||||
Mae dewis y faner **ar frig** y sgrin yn newid rhwng yr olygfa llinell amser a'ch proffil.
|
||||
|
||||
Ni fydd y sgrin yn adnewyddu'n awtomatig pan fydd pyst yn cyrraedd, felly defnyddiwch **F5** neu'r botwm **Mewnflwch** i adnewyddu.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Congratulations!
|
||||
You are now ready to begin using Epicyon. This is a moderated social space, so please make sure to abide by our [terms of service](/terms), and have fun.
|
||||
|
||||
#### Hints
|
||||
Use the **magnifier** icon 🔍 to search for fediverse handles and follow people.
|
||||
|
||||
Selecting the **banner at the top** of the screen switches between timeline view and your profile.
|
||||
|
||||
The screen will not automatically refresh when posts arrive, so use **F5** or the **Inbox** button to refresh.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### ¡Felicidades!
|
||||
Ahora está listo para comenzar a usar Epicyon. Este es un espacio social moderado, así que asegúrese de cumplir con nuestros [términos de servicio](/terms) y diviértase.
|
||||
|
||||
#### Sugerencias
|
||||
Utilice el icono de **lupa** 🔍 para buscar identificadores de fediverse y seguir a las personas.
|
||||
|
||||
Al seleccionar el **banner en la parte superior** de la pantalla, se cambia entre la vista de línea de tiempo y su perfil.
|
||||
|
||||
La pantalla no se actualizará automáticamente cuando lleguen las publicaciones, así que use **F5** o el botón **Bandeja de entrada** para actualizar.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Toutes nos félicitations!
|
||||
Vous êtes maintenant prêt à commencer à utiliser Epicyon. Il s'agit d'un espace social modéré, alors assurez-vous de respecter nos [conditions d'utilisation](/terms) et amusez-vous.
|
||||
|
||||
#### Conseils
|
||||
Utilisez l'icône **loupe** 🔍 pour rechercher des poignées fediverse et suivre les gens.
|
||||
|
||||
La sélection de la **bannière en haut** de l'écran bascule entre la vue chronologique et votre profil.
|
||||
|
||||
L'écran ne s'actualisera pas automatiquement à l'arrivée des messages, utilisez donc **F5** ou le bouton **Boîte de réception** pour actualiser.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Comhghairdeas!
|
||||
Tá tú réidh anois chun Epicyon a úsáid. Is spás sóisialta measartha é seo, mar sin déan cinnte cloí lenár [dtéarmaí seirbhíse](/terms), agus spraoi a bheith agat.
|
||||
|
||||
#### Leideanna
|
||||
Úsáid an deilbhín **formhéadaitheoir** chun cuardach a dhéanamh ar láimhseálacha beathaithe agus lean daoine.
|
||||
|
||||
Ag roghnú an bhratach **ag barr** na lasca scáileáin idir amharc amlíne agus do phróifíl.
|
||||
|
||||
Ní dhéanfaidh an scáileán athnuachan go huathoibríoch nuair a thiocfaidh na poist, mar sin bain úsáid as **F5** nó an cnaipe **Bosca Isteach** chun athnuachan a dhéanamh.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### बधाई हो!
|
||||
अब आप एपिसकॉन का उपयोग शुरू करने के लिए तैयार हैं। यह एक मध्यम सामाजिक स्थान है, इसलिए कृपया हमारी [सेवा की शर्तों](/terms) का पालन करना सुनिश्चित करें, और मज़े करें।
|
||||
|
||||
#### संकेत
|
||||
फ़ेडरिवर्स हैंडल की खोज करने और लोगों का अनुसरण करने के लिए **आवर्धक आइकन** का उपयोग करें।
|
||||
|
||||
समय दृश्य और आपकी प्रोफ़ाइल के बीच स्क्रीन स्विच के शीर्ष **पर स्थित** बैनर का चयन करना।
|
||||
|
||||
पोस्ट आने पर स्क्रीन अपने आप रिफ्रेश नहीं होगी, इसलिए रीफ्रेश करने के लिए **F5** या **इनबॉक्स** बटन का उपयोग करें।
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Congratulazioni!
|
||||
Ora sei pronto per iniziare a utilizzare Epicyon. Questo è uno spazio social moderato, quindi assicurati di rispettare i nostri [termini di servizio](/terms) e divertiti.
|
||||
|
||||
#### Suggerimenti
|
||||
Usa l'icona **lente d'ingrandimento** 🔍 per cercare gli handle di fediverse e seguire le persone.
|
||||
|
||||
Selezionando il **banner nella parte superiore** dello schermo si passa dalla visualizzazione della sequenza temporale al tuo profilo.
|
||||
|
||||
La schermata non si aggiornerà automaticamente all'arrivo dei post, quindi utilizza **F5** o il pulsante **Posta in arrivo** per aggiornare.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### おめでとう!
|
||||
これで、Epicyonの使用を開始する準備が整いました。 適度な社交空間ですので、必ず [利用規約](/terms) を遵守して楽しんでください。
|
||||
|
||||
#### ヒント
|
||||
**拡大鏡** アイコン🔍を使用して、fediverseハンドルを検索し、人々をフォローします。
|
||||
|
||||
画面の上部にある **バナー** を選択すると、タイムラインビューとプロファイルが切り替わります。
|
||||
|
||||
投稿が到着しても画面は自動的に更新されないため、 **F5** または **受信トレイ** ボタンを使用して更新してください。
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Congratulations!
|
||||
You are now ready to begin using Epicyon. This is a moderated social space, so please make sure to abide by our [terms of service](/terms), and have fun.
|
||||
|
||||
### Hints
|
||||
Use the **magnifier** icon 🔍 to search for fediverse handles and follow people.
|
||||
|
||||
Selecting the **banner at the top** of the screen switches between timeline view and your profile.
|
||||
|
||||
The screen will not automatically refresh when posts arrive, so use **F5** or the **Inbox** button to refresh.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Parabéns!
|
||||
Agora você está pronto para começar a usar o Epicyon. Este é um espaço social moderado, portanto, certifique-se de seguir nossos [termos de serviço](/terms) e divirta-se.
|
||||
|
||||
### Dicas
|
||||
Use o ícone de **lupa** 🔍 para pesquisar as alças do fediverse e seguir pessoas.
|
||||
|
||||
Selecionar o **banner na parte superior** da tela alterna entre a visualização da linha do tempo e seu perfil.
|
||||
|
||||
A tela não será atualizada automaticamente quando as postagens chegarem, então use **F5** ou o botão **Caixa de entrada** para atualizar.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### Поздравляю!
|
||||
Теперь вы готовы начать использовать Epicyon. Это модерируемое социальное пространство, поэтому, пожалуйста, соблюдайте наши [условия обслуживания](/terms) и получайте удовольствие.
|
||||
|
||||
#### Подсказки
|
||||
Используйте значок **лупы** 🔍, чтобы искать нужные метки и следить за людьми.
|
||||
|
||||
При выборе **баннера вверху** экрана выполняется переключение между представлением временной шкалы и вашим профилем.
|
||||
|
||||
Экран не обновляется автоматически при поступлении сообщений, поэтому используйте **F5** или кнопку **Входящие** для обновления.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
### 恭喜你!
|
||||
您现在可以开始使用Epicyon。 这是一个温和的社交空间,因此请务必遵守我们的[服务条款](/terms),并从中获得乐趣。
|
||||
|
||||
####提示
|
||||
使用放大镜图标search搜索fed性的手柄并关注他人。
|
||||
|
||||
选择屏幕顶部的横幅广告可在时间轴视图和个人资料之间切换。
|
||||
|
||||
帖子到达时,屏幕不会自动刷新,因此请使用F5或“收件箱”按钮刷新。
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### اعدادات الحساب
|
||||
حدد صورتك الرمزية وأضف اسمك ووصفك. استخدم صورة رمزية صغيرة (على سبيل المثال ، 128 × 128 بكسل) بحيث يمكن تنزيلها بسرعة.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Configuració del compte
|
||||
Seleccioneu la vostra imatge d’avatar i afegiu el vostre nom i la vostra descripció. Utilitzeu una imatge d’avatar petita (per exemple, 128x128 píxels) per baixar-la ràpidament.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Gosod Cyfrif
|
||||
Dewiswch eich delwedd avatar ac ychwanegwch eich enw a'ch disgrifiad. Defnyddiwch ddelwedd avatar fach (ee 128x128 picsel) fel ei bod yn gyflym i'w lawrlwytho.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Kontoeinrichtung
|
||||
Wählen Sie Ihr Avatar-Bild aus und fügen Sie Ihren Namen und Ihre Beschreibung hinzu. Verwenden Sie ein kleines Avatar-Bild (z. B. 128 x 128 Pixel), damit es schnell heruntergeladen werden kann.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Account Setup
|
||||
Select your avatar image and add your name and description. Use a small avatar image (eg. 128x128 pixels) so that it's quick to download.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Configuracion de cuenta
|
||||
Seleccione su imagen de avatar y agregue su nombre y descripción. Utilice una imagen de avatar pequeña (por ejemplo, 128x128 píxeles) para que se descargue rápidamente.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Configuration du compte
|
||||
Sélectionnez l'image de votre avatar et ajoutez votre nom et votre description. Utilisez une petite image d'avatar (par exemple, 128x128 pixels) pour un téléchargement rapide.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Socrú Cuntas
|
||||
Roghnaigh d’íomhá avatar agus cuir d’ainm agus do thuairisc leis. Úsáid íomhá bheag avatar (m.sh. 128x128 picteilín) ionas go mbeidh sí gasta le híoslódáil.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### खाता स्थापित करना
|
||||
अपनी अवतार छवि का चयन करें और अपना नाम और विवरण जोड़ें। एक छोटी अवतार छवि (जैसे। 128x128 पिक्सेल) का उपयोग करें ताकि यह डाउनलोड करने में तेज हो।
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Configurazione dell'account
|
||||
Seleziona la tua immagine avatar e aggiungi il tuo nome e la descrizione. Usa una piccola immagine avatar (es. 128x128 pixel) in modo che sia veloce da scaricare.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### アカウントの設定
|
||||
アバター画像を選択し、名前と説明を追加します。 小さなアバター画像(128x128ピクセルなど)を使用して、すばやくダウンロードできるようにします。
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
## Account Setup
|
||||
Select your avatar image and add your name and description. Use a small avatar image (eg. 128x128 pixels) so that it's quick to download.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
## Configuração da conta
|
||||
Selecione sua imagem de avatar e adicione seu nome e descrição. Use uma pequena imagem de avatar (por exemplo, 128x128 pixels) para que o download seja rápido.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### Настройка учетной записи
|
||||
Выберите изображение своего аватара и добавьте свое имя и описание. Используйте небольшое изображение аватара (например, 128x128 пикселей), чтобы его можно было быстро загрузить.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
### 帐户设定
|
||||
选择您的头像图片并添加您的姓名和描述。 使用较小的头像图片(例如128x128像素),以便快速下载。
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### مرحبًا بكم في INSTANCE
|
||||
هذا خادم ActivityPub مصمم للاستضافة الذاتية السهلة لعدد قليل من الأشخاص على أنظمة منخفضة الطاقة ، مثل أجهزة الكمبيوتر ذات اللوحة الواحدة أو أجهزة الكمبيوتر المحمولة القديمة.
|
||||
|
||||
قم بتشغيل وجودك على الشبكة الاجتماعية بالطريقة التي تريدها ، وداعًا لشركة Big Tech.
|
||||
|
||||
الآن ، لنبدأ ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Benvingut a INSTANCE
|
||||
Es tracta d’un servidor ActivityPub dissenyat per allotjar fàcilment algunes persones en sistemes de poca potència, com ara ordinadors de placa única o portàtils antics.
|
||||
|
||||
Gestioneu la vostra pròpia presència a la xarxa social com vulgueu i acomiadeu-vos de Big Tech.
|
||||
|
||||
Ara, comencem ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Croeso i INSTANCE
|
||||
Gweinydd ActivityPub yw hwn sydd wedi'i gynllunio ar gyfer hunan-letya ychydig o bobl ar systemau pŵer isel yn hawdd, fel cyfrifiaduron bwrdd sengl neu hen gliniaduron.
|
||||
|
||||
Rhedeg eich presenoldeb rhwydwaith cymdeithasol eich hun yn y ffordd rydych chi eisiau, a ffarwelio â Big Tech.
|
||||
|
||||
Nawr, gadewch i ni fynd ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Willkommen bei INSTANCE
|
||||
Dies ist ein ActivityPub-Server, der für das einfache Selbsthosting einiger weniger Personen auf Systemen mit geringem Stromverbrauch wie Single-Board-Computern oder alten Laptops entwickelt wurde.
|
||||
|
||||
Führen Sie Ihre eigene soziale Netzwerkpräsenz so, wie Sie möchten, und verabschieden Sie sich von Big Tech.
|
||||
|
||||
Jetzt geht's los ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Welcome to INSTANCE
|
||||
This is an ActivityPub server designed for easy self-hosting of a few people on low power systems, such as single board computers or old laptops.
|
||||
|
||||
Run your own social network presence the way you want to, and say goodbye to Big Tech.
|
||||
|
||||
Now, lets get going...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Bienvenido a INSTANCE
|
||||
Este es un servidor ActivityPub diseñado para el autohospedaje sencillo de algunas personas en sistemas de bajo consumo de energía, como computadoras de placa única o laptops antiguas.
|
||||
|
||||
Ejecute su propia presencia en las redes sociales de la forma que desee y despídase de las grandes tecnologías.
|
||||
|
||||
Ahora, vayamos ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Bienvenue à INSTANCE
|
||||
Il s'agit d'un serveur ActivityPub conçu pour l'auto-hébergement facile de quelques personnes sur des systèmes à faible consommation d'énergie, tels que des ordinateurs monocarte ou d'anciens ordinateurs portables.
|
||||
|
||||
Gérez votre propre présence sur les réseaux sociaux comme vous le souhaitez et dites au revoir à Big Tech.
|
||||
|
||||
Maintenant, allons-y ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Fáilte go INSTANCE
|
||||
Is freastalaí ActivityPub é seo atá deartha chun féin-óstáil éasca a dhéanamh ar chúpla duine ar chórais ísealchumhachta, mar ríomhairí boird aonair nó sean ríomhairí glúine.
|
||||
|
||||
Rith do láithreacht líonra sóisialta féin ar an mbealach is mian leat, agus slán a fhágáil le Big Tech.
|
||||
|
||||
Anois, ligeann duit dul ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### INSTANCE पर आपका स्वागत है
|
||||
यह एक एक्टिविटीपब सर्वर है जो कम पावर सिस्टम पर सिंगल बोर्ड कंप्यूटर या पुराने लैपटॉप जैसे कुछ लोगों की आसान सेल्फ-होस्टिंग के लिए बनाया गया है।
|
||||
|
||||
जिस तरह से आप चाहते हैं, अपने खुद के सोशल नेटवर्क उपस्थिति को चलाएं और बिग टेक को अलविदा कहें।
|
||||
|
||||
अब, चल रहा है ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Benvenuto in INSTANCE
|
||||
Questo è un server ActivityPub progettato per un facile self-hosting di poche persone su sistemi a basso consumo, come computer a scheda singola o vecchi laptop.
|
||||
|
||||
Gestisci la tua presenza sui social network come preferisci e saluta Big Tech.
|
||||
|
||||
Ora andiamo ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### INSTANCEへようこそ
|
||||
これは、シングルボードコンピューターや古いラップトップなどの低電力システムで数人を簡単にセルフホスティングするために設計されたActivityPubサーバーです。
|
||||
|
||||
独自のソーシャルネットワークプレゼンスを希望どおりに実行し、BigTechに別れを告げます。
|
||||
|
||||
さあ、始めましょう...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Welcome
|
||||
Epicyon is an ActivityPub server designed for easy self-hosting of a few people on low power systems, such as single board computers or old laptops.
|
||||
|
||||
Run your own social network presence the way you want to, and say goodbye to Big Tech.
|
||||
|
||||
Now, lets get going...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Bem-vindo a INSTANCE
|
||||
Este é um servidor ActivityPub projetado para fácil auto-hospedagem de algumas pessoas em sistemas de baixo consumo de energia, como computadores de placa única ou laptops antigos.
|
||||
|
||||
Administre sua própria presença na rede social do jeito que você quiser e diga adeus à Big Tech.
|
||||
|
||||
Agora, vamos indo ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### Добро пожаловать в INSTANCE
|
||||
Это сервер ActivityPub, предназначенный для простого самостоятельного размещения нескольких человек в системах с низким энергопотреблением, таких как одноплатные компьютеры или старые ноутбуки.
|
||||
|
||||
Управляйте своим присутствием в социальных сетях так, как вы хотите, и попрощайтесь с Big Tech.
|
||||
|
||||
А теперь поехали ...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### 欢迎来到INSTANCE
|
||||
这是一个ActivityPub服务器,设计用于在低功耗系统(例如单板计算机或旧笔记本电脑)上轻松实现一些人的自我托管。
|
||||
|
||||
随心所欲地经营自己的社交网络,并与Big Tech道别。
|
||||
|
||||
现在,开始吧...
|
||||
|
|
@ -599,15 +599,15 @@ a:focus {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.darker {
|
||||
.container.darker {
|
||||
background-color: var(--main-bg-color-reply);
|
||||
}
|
||||
|
||||
.dm {
|
||||
.container.dm {
|
||||
background-color: var(--main-bg-color-dm);
|
||||
}
|
||||
|
||||
.report {
|
||||
.container.report {
|
||||
border-color: #255;
|
||||
background-color: var(--main-bg-color-report);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
@chaste "UTF-8";
|
||||
|
||||
:root {
|
||||
--welcome-bg-color: #282c37;
|
||||
--link-bg-color: #282c37;
|
||||
--welcome-fg-color: #dddddd;
|
||||
--main-link-color: #999;
|
||||
--main-visited-color: #888;
|
||||
--border-color: #505050;
|
||||
--border-width: 2px;
|
||||
--font-size-header: 18px;
|
||||
--font-color-header: #ccc;
|
||||
--welcome-font-size: 22px;
|
||||
--welcome-font-size-mobile: 40px;
|
||||
--text-entry-foreground: #ccc;
|
||||
--text-entry-background: #111;
|
||||
--time-color: #aaa;
|
||||
--welcome-button-width: 12ch;
|
||||
--button-text: #FFFFFF;
|
||||
--button-background: #999;
|
||||
--button-selected: #666;
|
||||
--form-border-radius: 30px;
|
||||
--focus-color: white;
|
||||
--line-spacing: 130%;
|
||||
--welcome-logo-width: 20%;
|
||||
--welcome-avatar-width: 40%;
|
||||
--main-link-color-hover: #bbb;
|
||||
--rendering: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Bedstead';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
font-display: block;
|
||||
src: url('./fonts/bedstead.otf') format('opentype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Bedstead';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-display: block;
|
||||
src: url('./fonts/bedstead.otf') format('opentype');
|
||||
}
|
||||
|
||||
body, html {
|
||||
background-color: var(--welcome-bg-color);
|
||||
color: var(--welcome-fg-color);
|
||||
|
||||
background-image: url("/welcome-background.jpg");
|
||||
background-size: cover;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
max-width: 60%;
|
||||
min-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-size: var(--welcome-font-size);
|
||||
line-height: var(--line-spacing);
|
||||
image-rendering: var(--rendering);
|
||||
}
|
||||
|
||||
a, u {
|
||||
color: var(--welcome-fg-color);
|
||||
}
|
||||
|
||||
a:visited{
|
||||
color: var(--main-visited-color);
|
||||
background: var(--link-bg-color);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: var(--main-link-color);
|
||||
background: var(--link-bg-color);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:link:hover {
|
||||
color: var(--main-link-color-hover);
|
||||
}
|
||||
|
||||
a:visited:hover {
|
||||
color: var(--main-link-color-hover);
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
form {
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--form-border-radius);
|
||||
}
|
||||
|
||||
.transparent {
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
font-size: 0px;
|
||||
line-height: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text);
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: var(--welcome-button-width);
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.imgcontainer {
|
||||
text-align: center;
|
||||
margin: 24px 0 12px 0;
|
||||
}
|
||||
|
||||
.imgcontainer img {
|
||||
width: var(--welcome-logo-width);
|
||||
}
|
||||
|
||||
img.avatar {
|
||||
width: 40%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.container img.welcomeavatar {
|
||||
width: var(--welcome-avatar-width);
|
||||
}
|
||||
|
||||
.container.next {
|
||||
float: right;
|
||||
}
|
||||
|
||||
span.psw {
|
||||
float: right;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 400px) {
|
||||
body, html {
|
||||
background-color: var(--welcome-bg-color);
|
||||
color: var(--welcome-fg-color);
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
max-width: 60%;
|
||||
min-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
.welcome-text {
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
input[type=text], input[type=password], textarea {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
button {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text);
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: var(--welcome-button-width);
|
||||
font-size: var(--welcome-font-size);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
body, html {
|
||||
background-color: var(--welcome-bg-color);
|
||||
color: var(--welcome-fg-color);
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
max-width: 95%;
|
||||
min-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-size: var(--welcome-font-size-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
.welcome-text {
|
||||
font-size: var(--welcome-font-size-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
input[type=text], input[type=password], textarea {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
font-size: var(--welcome-font-size-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
button {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text);
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: var(--welcome-button-width);
|
||||
font-size: var(--welcome-font-size-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
14
epicyon.py
14
epicyon.py
|
|
@ -1617,6 +1617,13 @@ if args.addaccount:
|
|||
if not args.domain or not getConfigParam(baseDir, 'domain'):
|
||||
print('Use the --domain option to set the domain name')
|
||||
sys.exit()
|
||||
|
||||
configuredDomain = getConfigParam(baseDir, 'domain')
|
||||
if configuredDomain:
|
||||
if domain != configuredDomain:
|
||||
print('The account domain is expected to be ' + configuredDomain)
|
||||
sys.exit()
|
||||
|
||||
if not validNickname(domain, nickname):
|
||||
print(nickname + ' is a reserved name. Use something different.')
|
||||
sys.exit()
|
||||
|
|
@ -1688,6 +1695,13 @@ if args.rmaccount:
|
|||
if not args.domain or not getConfigParam(baseDir, 'domain'):
|
||||
print('Use the --domain option to set the domain name')
|
||||
sys.exit()
|
||||
|
||||
configuredDomain = getConfigParam(baseDir, 'domain')
|
||||
if configuredDomain:
|
||||
if domain != configuredDomain:
|
||||
print('The account domain is expected to be ' + configuredDomain)
|
||||
sys.exit()
|
||||
|
||||
if args.deactivate:
|
||||
if deactivateAccount(baseDir, nickname, domain):
|
||||
print('Account for ' + nickname + '@' + domain +
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ def _removeFromFollowRejects(baseDir: str,
|
|||
def isFollowingActor(baseDir: str,
|
||||
nickname: str, domain: str, actor: str) -> bool:
|
||||
"""Is the given nickname following the given actor?
|
||||
The actor can also be a handle: nickname@domain
|
||||
"""
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')[0]
|
||||
|
|
|
|||
155
inbox.py
155
inbox.py
|
|
@ -40,6 +40,7 @@ from categories import setHashtagCategory
|
|||
from httpsig import verifyPostHeaders
|
||||
from session import createSession
|
||||
from session import getJson
|
||||
from follow import isFollowingActor
|
||||
from follow import receiveFollowRequest
|
||||
from follow import getFollowersOfActor
|
||||
from follow import unfollowerOfAccount
|
||||
|
|
@ -57,6 +58,7 @@ from utils import updateAnnounceCollection
|
|||
from utils import undoAnnounceCollectionEntry
|
||||
from utils import dangerousMarkup
|
||||
from httpsig import messageContentDigest
|
||||
from posts import createDirectMessagePost
|
||||
from posts import validContentWarning
|
||||
from posts import downloadAnnounce
|
||||
from posts import isDM
|
||||
|
|
@ -73,7 +75,6 @@ from git import receiveGitPatch
|
|||
from followingCalendar import receivingCalendarEvents
|
||||
from happening import saveEventPost
|
||||
from delete import removeOldHashtags
|
||||
from follow import isFollowingActor
|
||||
from categories import guessHashtagCategory
|
||||
from context import hasValidContext
|
||||
|
||||
|
|
@ -2056,6 +2057,80 @@ def _updateLastSeen(baseDir: str, handle: str, actor: str) -> None:
|
|||
lastSeenFile.write(str(daysSinceEpoch))
|
||||
|
||||
|
||||
def _bounceDM(senderPostId: str, session, httpPrefix: str,
|
||||
baseDir: str, nickname: str, domain: str, port: int,
|
||||
sendingHandle: str, federationList: [],
|
||||
sendThreads: [], postLog: [],
|
||||
cachedWebfingers: {}, personCache: {},
|
||||
translate: {}, debug: bool,
|
||||
lastBounceMessage: []) -> bool:
|
||||
"""Sends a bounce message back to the sending handle
|
||||
if a DM has been rejected
|
||||
"""
|
||||
print(nickname + '@' + domain +
|
||||
' cannot receive DM from ' + sendingHandle +
|
||||
' because they do not follow them')
|
||||
|
||||
# Don't send out bounce messages too frequently.
|
||||
# Otherwise an adversary could try to DoS your instance
|
||||
# by continuously sending DMs to you
|
||||
currTime = int(time.time())
|
||||
if currTime - lastBounceMessage[0] < 60:
|
||||
return False
|
||||
|
||||
# record the last time that a bounce was generated
|
||||
lastBounceMessage[0] = currTime
|
||||
|
||||
senderNickname = sendingHandle.split('@')[0]
|
||||
senderDomain = sendingHandle.split('@')[1]
|
||||
senderPort = port
|
||||
if ':' in senderDomain:
|
||||
senderPortStr = senderDomain.split(':')[1]
|
||||
if senderPortStr.isdigit():
|
||||
senderPort = int(senderPortStr)
|
||||
senderDomain = senderDomain.split(':')[0]
|
||||
cc = []
|
||||
|
||||
# create the bounce DM
|
||||
subject = None
|
||||
content = translate['DM bounce']
|
||||
followersOnly = False
|
||||
saveToFile = False
|
||||
clientToServer = False
|
||||
commentsEnabled = False
|
||||
attachImageFilename = None
|
||||
mediaType = None
|
||||
imageDescription = ''
|
||||
inReplyTo = removeIdEnding(senderPostId)
|
||||
inReplyToAtomUri = None
|
||||
schedulePost = False
|
||||
eventDate = None
|
||||
eventTime = None
|
||||
location = None
|
||||
postJsonObject = \
|
||||
createDirectMessagePost(baseDir, nickname, domain, port,
|
||||
httpPrefix, content, followersOnly,
|
||||
saveToFile, clientToServer,
|
||||
commentsEnabled,
|
||||
attachImageFilename, mediaType,
|
||||
imageDescription,
|
||||
inReplyTo, inReplyToAtomUri,
|
||||
subject, debug, schedulePost,
|
||||
eventDate, eventTime, location)
|
||||
if not postJsonObject:
|
||||
print('WARN: unable to create bounce message to ' + sendingHandle)
|
||||
return False
|
||||
# bounce DM goes back to the sender
|
||||
print('Sending bounce DM to ' + sendingHandle)
|
||||
sendSignedJson(postJsonObject, session, baseDir,
|
||||
nickname, domain, port,
|
||||
senderNickname, senderDomain, senderPort, cc,
|
||||
httpPrefix, False, False, federationList,
|
||||
sendThreads, postLog, cachedWebfingers,
|
||||
personCache, debug, __version__)
|
||||
return True
|
||||
|
||||
|
||||
def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||
session, keyId: str, handle: str, messageJson: {},
|
||||
baseDir: str, httpPrefix: str, sendThreads: [],
|
||||
|
|
@ -2070,7 +2145,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
unitTest: bool, YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool,
|
||||
allowLocalNetworkAccess: bool,
|
||||
peertubeInstances: []) -> bool:
|
||||
peertubeInstances: [],
|
||||
lastBounceMessage: []) -> bool:
|
||||
""" Anything which needs to be done after initial checks have passed
|
||||
"""
|
||||
actor = keyId
|
||||
|
|
@ -2263,41 +2339,68 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
postIsDM = isDM(postJsonObject)
|
||||
if postIsDM:
|
||||
if nickname != 'inbox':
|
||||
# check for the flag file which indicates to
|
||||
# only receive DMs from people you are following
|
||||
followDMsFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/.followDMs'
|
||||
if os.path.isfile(followDMsFilename):
|
||||
# get the file containing following handles
|
||||
followingFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/following.txt'
|
||||
# who is sending a DM?
|
||||
if not postJsonObject.get('actor'):
|
||||
return False
|
||||
sendingActor = postJsonObject['actor']
|
||||
sendingActorNickname = \
|
||||
getNicknameFromActor(sendingActor)
|
||||
if not sendingActorNickname:
|
||||
return False
|
||||
sendingActorDomain, sendingActorPort = \
|
||||
getDomainFromActor(sendingActor)
|
||||
if sendingActorNickname and sendingActorDomain:
|
||||
if not os.path.isfile(followingFilename):
|
||||
print('No following.txt file exists for ' +
|
||||
nickname + '@' + domain +
|
||||
' so not accepting DM from ' +
|
||||
sendingActorNickname + '@' +
|
||||
sendingActorDomain)
|
||||
return False
|
||||
sendH = \
|
||||
sendingActorNickname + '@' + sendingActorDomain
|
||||
if sendH != nickname + '@' + domain:
|
||||
if sendH not in \
|
||||
open(followingFilename).read():
|
||||
print(nickname + '@' + domain +
|
||||
' cannot receive DM from ' +
|
||||
sendH +
|
||||
' because they do not ' +
|
||||
'follow them')
|
||||
return False
|
||||
else:
|
||||
if not sendingActorDomain:
|
||||
return False
|
||||
# check that the following file exists
|
||||
if not os.path.isfile(followingFilename):
|
||||
print('No following.txt file exists for ' +
|
||||
nickname + '@' + domain +
|
||||
' so not accepting DM from ' +
|
||||
sendingActorNickname + '@' +
|
||||
sendingActorDomain)
|
||||
return False
|
||||
# get the handle of the DM sender
|
||||
sendH = \
|
||||
sendingActorNickname + '@' + sendingActorDomain
|
||||
# Not sending to yourself
|
||||
if sendH != nickname + '@' + domain:
|
||||
# check the follow
|
||||
if not isFollowingActor(baseDir,
|
||||
nickname, domain,
|
||||
sendH):
|
||||
# send back a bounce DM
|
||||
if postJsonObject.get('id') and \
|
||||
postJsonObject.get('object'):
|
||||
# don't send bounces back to
|
||||
# replies to bounce messages
|
||||
obj = postJsonObject['object']
|
||||
if isinstance(obj, dict):
|
||||
if not obj.get('inReplyTo'):
|
||||
senderPostId = \
|
||||
postJsonObject['id']
|
||||
_bounceDM(senderPostId,
|
||||
session, httpPrefix,
|
||||
baseDir,
|
||||
nickname, domain,
|
||||
port, sendH,
|
||||
federationList,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache,
|
||||
translate, debug,
|
||||
lastBounceMessage)
|
||||
return False
|
||||
|
||||
# dm index will be updated
|
||||
updateIndexList.append('dm')
|
||||
_dmNotify(baseDir, handle,
|
||||
|
|
@ -2513,6 +2616,11 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
heartBeatCtr = 0
|
||||
queueRestoreCtr = 0
|
||||
|
||||
# time when the last DM bounce message was sent
|
||||
# This is in a list so that it can be changed by reference
|
||||
# within _bounceDM
|
||||
lastBounceMessage = [int(time.time())]
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
|
|
@ -2969,7 +3077,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
allowLocalNetworkAccess,
|
||||
peertubeInstances)
|
||||
peertubeInstances,
|
||||
lastBounceMessage)
|
||||
if debug:
|
||||
pprint(queueJson['post'])
|
||||
|
||||
|
|
|
|||
35
tests.py
35
tests.py
|
|
@ -98,6 +98,7 @@ from newswire import parseFeedDate
|
|||
from mastoapiv1 import getMastoApiV1IdFromNickname
|
||||
from mastoapiv1 import getNicknameFromMastoApiV1Id
|
||||
from webapp_post import prepareHtmlPostNickname
|
||||
from webapp_utils import markdownToHtml
|
||||
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
|
|
@ -3282,9 +3283,43 @@ def testValidHashTag():
|
|||
assert not validHashTag('This=IsAlsoNotValid"')
|
||||
|
||||
|
||||
def testMarkdownToHtml():
|
||||
print('testMarkdownToHtml')
|
||||
markdown = 'This is just plain text'
|
||||
assert markdownToHtml(markdown) == markdown
|
||||
|
||||
markdown = 'This is **bold**'
|
||||
assert markdownToHtml(markdown) == 'This is <b>bold</b>'
|
||||
|
||||
markdown = 'This is *italic*'
|
||||
assert markdownToHtml(markdown) == 'This is <i>italic</i>'
|
||||
|
||||
markdown = 'This is _underlined_'
|
||||
assert markdownToHtml(markdown) == 'This is <ul>underlined</ul>'
|
||||
|
||||
markdown = 'This is **just** plain text'
|
||||
assert markdownToHtml(markdown) == 'This is <b>just</b> plain text'
|
||||
|
||||
markdown = '# Title1\n### Title3\n## Title2\n'
|
||||
assert markdownToHtml(markdown) == \
|
||||
'<h1>Title1</h1><h3>Title3</h3><h2>Title2</h2>'
|
||||
|
||||
markdown = \
|
||||
'This is [a link](https://something.somewhere) to something.\n' + \
|
||||
'And [something else](https://cat.pic).'
|
||||
assert markdownToHtml(markdown) == \
|
||||
'This is <a href="https://something.somewhere" ' + \
|
||||
'target="_blank" rel="nofollow noopener noreferrer">' + \
|
||||
'a link</a> to something.<br>' + \
|
||||
'And <a href="https://cat.pic" ' + \
|
||||
'target="_blank" rel="nofollow noopener noreferrer">' + \
|
||||
'something else</a>.'
|
||||
|
||||
|
||||
def runAllTests():
|
||||
print('Running tests...')
|
||||
testFunctions()
|
||||
testMarkdownToHtml()
|
||||
testValidHashTag()
|
||||
testPrepareHtmlPostNickname()
|
||||
testDomainHandling()
|
||||
|
|
|
|||
8
theme.py
8
theme.py
|
|
@ -19,7 +19,8 @@ def _getThemeFiles() -> []:
|
|||
"""
|
||||
return ('epicyon.css', 'login.css', 'follow.css',
|
||||
'suspended.css', 'calendar.css', 'blog.css',
|
||||
'options.css', 'search.css', 'links.css')
|
||||
'options.css', 'search.css', 'links.css',
|
||||
'welcome.css')
|
||||
|
||||
|
||||
def getThemesList(baseDir: str) -> []:
|
||||
|
|
@ -264,6 +265,8 @@ def _setThemeFromDict(baseDir: str, name: str,
|
|||
_setBackgroundFormat(baseDir, name, 'options', bgParams['options'])
|
||||
if bgParams.get('search'):
|
||||
_setBackgroundFormat(baseDir, name, 'search', bgParams['search'])
|
||||
if bgParams.get('welcome'):
|
||||
_setBackgroundFormat(baseDir, name, 'welcome', bgParams['welcome'])
|
||||
|
||||
|
||||
def _setBackgroundFormat(baseDir: str, name: str,
|
||||
|
|
@ -507,7 +510,8 @@ def _setThemeImages(baseDir: str, name: str) -> None:
|
|||
_setTextModeTheme(baseDir, themeNameLower)
|
||||
|
||||
backgroundNames = ('login', 'shares', 'delete', 'follow',
|
||||
'options', 'block', 'search', 'calendar')
|
||||
'options', 'block', 'search', 'calendar',
|
||||
'welcome')
|
||||
extensions = getImageExtensions()
|
||||
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"gallery-font-size-mobile": "55px",
|
||||
"main-bg-color": "#002365",
|
||||
"login-bg-color": "#002365",
|
||||
"welcome-bg-color": "#002365",
|
||||
"options-bg-color": "#002365",
|
||||
"post-bg-color": "#002365",
|
||||
"timeline-posts-background-color": "#002365",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"button-selected-highlighted": "#2b5c6d",
|
||||
"button-approve": "#2b5c6d",
|
||||
"login-button-color": "#2b5c6d",
|
||||
"welcome-button-color": "#2b5c6d",
|
||||
"button-event-background-color": "#2b5c6d",
|
||||
"post-separator-margin-top": "10px",
|
||||
"post-separator-margin-bottom": "10px",
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
"column-left-color": "#e6ebf0",
|
||||
"main-bg-color": "#e6ebf0",
|
||||
"login-bg-color": "#010026",
|
||||
"welcome-bg-color": "#010026",
|
||||
"options-bg-color": "#010026",
|
||||
"post-bg-color": "#e6ebf0",
|
||||
"timeline-posts-background-color": "#e6ebf0",
|
||||
|
|
@ -54,6 +56,7 @@
|
|||
"cw-color": "#2d2c37",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "white",
|
||||
"welcome-fg-color": "white",
|
||||
"options-fg-color": "lightgrey",
|
||||
"column-left-fg-color": "#2d2c37",
|
||||
"border-color": "#c0cdd9",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"focus-color": "green",
|
||||
"main-bg-color": "black",
|
||||
"login-bg-color": "black",
|
||||
"welcome-bg-color": "black",
|
||||
"options-bg-color": "black",
|
||||
"post-bg-color": "black",
|
||||
"timeline-posts-background-color": "black",
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
"cw-color": "#00ff00",
|
||||
"main-fg-color": "#00ff00",
|
||||
"login-fg-color": "#00ff00",
|
||||
"welcome-fg-color": "#00ff00",
|
||||
"options-fg-color": "#00ff00",
|
||||
"column-left-fg-color": "#00ff00",
|
||||
"border-color": "#035103",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
"time-color": "grey",
|
||||
"event-color": "white",
|
||||
"login-bg-color": "#567726",
|
||||
"welcome-bg-color": "#567726",
|
||||
"login-fg-color": "black",
|
||||
"welcome-fg-color": "black",
|
||||
"options-bg-color": "black",
|
||||
"newswire-publish-icon": "True",
|
||||
"full-width-timeline-buttons": "False",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
"font-size5": "22px",
|
||||
"main-bg-color": "black",
|
||||
"login-bg-color": "black",
|
||||
"welcome-bg-color": "black",
|
||||
"options-bg-color": "black",
|
||||
"post-bg-color": "black",
|
||||
"timeline-posts-background-color": "black",
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
"cw-color": "white",
|
||||
"main-fg-color": "white",
|
||||
"login-fg-color": "white",
|
||||
"welcome-fg-color": "white",
|
||||
"options-fg-color": "white",
|
||||
"column-left-fg-color": "white",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
|
|
@ -80,5 +82,7 @@
|
|||
"column-right-width": "20vw",
|
||||
"column-right-icon-size": "11%",
|
||||
"login-button-color": "red",
|
||||
"login-button-fg-color": "white"
|
||||
"welcome-button-color": "red",
|
||||
"login-button-fg-color": "white",
|
||||
"welcome-button-fg-color": "white"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@
|
|||
"tab-border-color": "transparent",
|
||||
"button-corner-radius": "0px",
|
||||
"login-button-color": "#25408f",
|
||||
"welcome-button-color": "#25408f",
|
||||
"login-button-fg-color": "white",
|
||||
"welcome-button-fg-color": "white",
|
||||
"column-left-width": "10vw",
|
||||
"column-center-width": "75vw",
|
||||
"column-right-width": "15vw",
|
||||
|
|
@ -91,6 +93,7 @@
|
|||
"column-left-color": "#efefef",
|
||||
"main-bg-color": "#efefef",
|
||||
"login-bg-color": "#efefef",
|
||||
"welcome-bg-color": "#efefef",
|
||||
"options-bg-color": "#efefef",
|
||||
"post-bg-color": "white",
|
||||
"timeline-posts-background-color": "white",
|
||||
|
|
@ -102,6 +105,7 @@
|
|||
"cw-color": "black",
|
||||
"main-fg-color": "black",
|
||||
"login-fg-color": "black",
|
||||
"welcome-fg-color": "black",
|
||||
"options-fg-color": "black",
|
||||
"column-left-fg-color": "#25408f",
|
||||
"border-color": "#c0cdd9",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"column-left-header-color": "#33390d",
|
||||
"main-bg-color": "#9fb42b",
|
||||
"login-bg-color": "#9fb42b",
|
||||
"welcome-bg-color": "#9fb42b",
|
||||
"options-bg-color": "#9fb42b",
|
||||
"post-bg-color": "#9fb42b",
|
||||
"timeline-posts-background-color": "#9fb42b",
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
"cw-color": "#33390d",
|
||||
"main-fg-color": "#33390d",
|
||||
"login-fg-color": "#33390d",
|
||||
"welcome-fg-color": "#33390d",
|
||||
"options-fg-color": "#33390d",
|
||||
"border-color": "#33390d",
|
||||
"border-width": "5px",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"column-left-color": "#e6ebf0",
|
||||
"main-bg-color": "#e6ebf0",
|
||||
"login-bg-color": "#e6ebf0",
|
||||
"welcome-bg-color": "#e6ebf0",
|
||||
"options-bg-color": "#e6ebf0",
|
||||
"post-bg-color": "#e6ebf0",
|
||||
"timeline-posts-background-color": "#e6ebf0",
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
"cw-color": "#777",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "#2d2c37",
|
||||
"welcome-fg-color": "#2d2c37",
|
||||
"options-fg-color": "#2d2c37",
|
||||
"column-left-fg-color": "#2d2c37",
|
||||
"border-color": "#c0cdd9",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"login-bg-color": "#0f0d10",
|
||||
"welcome-bg-color": "#0f0d10",
|
||||
"options-bg-color": "#0f0d10",
|
||||
"post-bg-color": "#0f0d10",
|
||||
"timeline-posts-background-color": "#0f0d10",
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
"cw-color": "#0481f5",
|
||||
"main-fg-color": "#0481f5",
|
||||
"login-fg-color": "#0481f5",
|
||||
"welcome-fg-color": "#0481f5",
|
||||
"options-fg-color": "#0481f5",
|
||||
"column-left-fg-color": "#0481f5",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"button-approve": "#12435f",
|
||||
"border-color": "#7152a3",
|
||||
"login-fg-color": "black",
|
||||
"welcome-fg-color": "black",
|
||||
"cw-color": "black",
|
||||
"main-link-color": "#333",
|
||||
"options-main-link-color": "#333",
|
||||
|
|
@ -44,6 +45,7 @@
|
|||
"dropdown-bg-color": "#8ba0d4",
|
||||
"dropdown-bg-color-hover": "#7ba0d4",
|
||||
"login-bg-color": "#9ba0d4",
|
||||
"welcome-bg-color": "#9ba0d4",
|
||||
"text-entry-background": "#8ba0d4",
|
||||
"timeline-posts-background-color": "#9ba0d4",
|
||||
"header-bg-color": "#9ba0d4",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"font-size5": "22px",
|
||||
"main-bg-color": "#1f152d",
|
||||
"login-bg-color": "#1f152d",
|
||||
"welcome-bg-color": "#1f152d",
|
||||
"options-bg-color": "#1f152d",
|
||||
"post-bg-color": "#1f152d",
|
||||
"timeline-posts-background-color": "#1f152d",
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
"cw-color": "#f98bb0",
|
||||
"main-fg-color": "#f98bb0",
|
||||
"login-fg-color": "#f98bb0",
|
||||
"welcome-fg-color": "#f98bb0",
|
||||
"options-fg-color": "#f98bb0",
|
||||
"column-left-fg-color": "#f98bb0",
|
||||
"border-color": "#3f2145",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
"button-selected-highlighted": "#0481f5",
|
||||
"button-fg-highlighted": "white",
|
||||
"login-button-color": "#6800e7",
|
||||
"welcome-button-color": "#6800e7",
|
||||
"login-button-fg-color": "white",
|
||||
"welcome-button-fg-color": "white",
|
||||
"verticals-width": "16px",
|
||||
"tab-border-color": "#6800e7",
|
||||
"timeline-border-radius": "0",
|
||||
|
|
@ -43,6 +45,7 @@
|
|||
"font-size-likes": "10px",
|
||||
"main-bg-color": "#100e23",
|
||||
"login-bg-color": "#100e23",
|
||||
"welcome-bg-color": "#100e23",
|
||||
"options-bg-color": "#100e23",
|
||||
"post-bg-color": "#100e23",
|
||||
"timeline-posts-background-color": "#100e23",
|
||||
|
|
@ -57,6 +60,7 @@
|
|||
"cw-color": "white",
|
||||
"main-fg-color": "white",
|
||||
"login-fg-color": "white",
|
||||
"welcome-fg-color": "white",
|
||||
"options-fg-color": "white",
|
||||
"title-color": "white",
|
||||
"column-left-fg-color": "#05b9ec",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"main-bg-color": "#eeeeee",
|
||||
"login-bg-color": "#eeeeee",
|
||||
"welcome-bg-color": "#eeeeee",
|
||||
"options-bg-color": "#eeeeee",
|
||||
"post-bg-color": "#eeeeee",
|
||||
"timeline-posts-background-color": "#eeeeee",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"cw-color": "#2d2c37",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "#2d2c37",
|
||||
"welcome-fg-color": "#2d2c37",
|
||||
"options-fg-color": "#2d2c37",
|
||||
"column-left-fg-color": "#2d2c37",
|
||||
"border-color": "#c0cdd9",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"login-bg-color": "#0f0d10",
|
||||
"welcome-bg-color": "#0f0d10",
|
||||
"options-bg-color": "#0f0d10",
|
||||
"post-bg-color": "#0f0d10",
|
||||
"timeline-posts-background-color": "#0f0d10",
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
"cw-color": "#ffc4bc",
|
||||
"main-fg-color": "#ffc4bc",
|
||||
"login-fg-color": "#ffc4bc",
|
||||
"welcome-fg-color": "#ffc4bc",
|
||||
"options-fg-color": "#ffc4bc",
|
||||
"column-left-fg-color": "#ffc4bc",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"button-text": "#d5c7b7",
|
||||
"button-selected-text": "#d5c7b7",
|
||||
"login-bg-color": "#212e3f",
|
||||
"welcome-bg-color": "#212e3f",
|
||||
"lines-color": "#b6a188",
|
||||
"day-number": "black",
|
||||
"day-number2": "#bbb",
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "إنشاء DM جديد",
|
||||
"Switch to profile view": "التبديل إلى عرض الملف الشخصي",
|
||||
"Inbox": "صندوق الوارد",
|
||||
"Outbox": "صندوق الحفظ",
|
||||
"Sent": "أرسلت",
|
||||
"Search and follow": "بحث ومتابعة",
|
||||
"Refresh": "تحديث",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "الاسم المستعار أو عنوان URL. حظر استخدام *@domain أو اسم النطاق@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "نشر مقال بلوق",
|
||||
"Featured writer": "كاتب متميز",
|
||||
"Broch mode": "وضع الكتيب",
|
||||
"Pixel": "بكسل"
|
||||
"Pixel": "بكسل",
|
||||
"DM bounce": "يتم قبول الرسائل فقط من الحسابات المتبعة",
|
||||
"Next": "التالي",
|
||||
"Preview": "معاينة"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Crea un nou missatge directe",
|
||||
"Switch to profile view": "Canvia a la vista del perfil",
|
||||
"Inbox": "entrada",
|
||||
"Outbox": "sortida",
|
||||
"Sent": "Enviat",
|
||||
"Search and follow": "Cerca i segueix",
|
||||
"Refresh": "Actualització",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Nickname o URL. Bloquegeu amb el domini *@ o el sobrenom@",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Publicar un article del bloc",
|
||||
"Featured writer": "Escriptor destacat",
|
||||
"Broch mode": "Mode Broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Els missatges només s’accepten des dels comptes seguits",
|
||||
"Next": "Pròxim",
|
||||
"Preview": "Vista prèvia"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Creu Neges Uniongyrchol newydd",
|
||||
"Switch to profile view": "Newid i olwg proffil",
|
||||
"Inbox": "Mewnflwch",
|
||||
"Outbox": "Allan",
|
||||
"Sent": "Anfonwyd",
|
||||
"Search and follow": "Chwilio a dilyn",
|
||||
"Refresh": "Adnewyddu",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Llysenw neu URL. Blociwch gan ddefnyddio *@domain neu lysenw@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Cyhoeddi erthygl blog",
|
||||
"Featured writer": "Awdur dan sylw",
|
||||
"Broch mode": "Modd Broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Dim ond o gyfrifon a ddilynir y derbynnir negeseuon",
|
||||
"Next": "Nesaf",
|
||||
"Preview": "Rhagolwg"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Neue Direktnachricht",
|
||||
"Switch to profile view": "Zur Profilansicht wechseln",
|
||||
"Inbox": "Eingang",
|
||||
"Outbox": "Ausgang",
|
||||
"Sent": "Geschickt",
|
||||
"Search and follow": "Suchen und folgen",
|
||||
"Refresh": "Aktualisieren",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Benutzername oder URL. *@Domäne oder Benutzer@Domäne sperren",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Veröffentlichen Sie einen Blog-Artikel",
|
||||
"Featured writer": "Ausgewählter Schriftsteller",
|
||||
"Broch mode": "Broch-Modus",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Nachrichten werden nur von folgenden Konten akzeptiert",
|
||||
"Next": "Nächster",
|
||||
"Preview": "Vorschau"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Create a new DM",
|
||||
"Switch to profile view": "Profile view",
|
||||
"Inbox": "Inbox",
|
||||
"Outbox": "Outbox",
|
||||
"Sent": "Sent",
|
||||
"Search and follow": "Search/follow",
|
||||
"Refresh": "Refresh",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Nickname or URL. Block using *@domain or nickname@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Publish a blog article",
|
||||
"Featured writer": "Featured writer",
|
||||
"Broch mode": "Broch mode",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Messages are only accepted from followed accounts",
|
||||
"Next": "Next",
|
||||
"Preview": "Preview"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Crear un nuevo mensaje directo",
|
||||
"Switch to profile view": "Cambiar a la vista de perfil",
|
||||
"Inbox": "Entrada",
|
||||
"Outbox": "Salida",
|
||||
"Sent": "Enviada",
|
||||
"Search and follow": "Busca y sigue",
|
||||
"Refresh": "Refrescar",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Apodo o URL. Bloquear usando *@dominio o apodo@dominio",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Publica un artículo de blog",
|
||||
"Featured writer": "Escritora destacada",
|
||||
"Broch mode": "Modo broche",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Solo se aceptan mensajes de cuentas seguidas",
|
||||
"Next": "Próxima",
|
||||
"Preview": "Avance"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Créer un nouveau message direct",
|
||||
"Switch to profile view": "Passer en vue de profil",
|
||||
"Inbox": "Réception",
|
||||
"Outbox": "Envoi",
|
||||
"Sent": "Expédié",
|
||||
"Search and follow": "Rechercher et suivre",
|
||||
"Refresh": "Rafraîchir",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Surnom ou URL. Bloquer en utilisant *@domain ou pseudo@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Publier un article de blog",
|
||||
"Featured writer": "Écrivain en vedette",
|
||||
"Broch mode": "Mode Broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Les messages ne sont acceptés que des comptes suivis",
|
||||
"Next": "Suivante",
|
||||
"Preview": "Aperçu"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Cruthaigh Teachtaireacht Dhíreach nua",
|
||||
"Switch to profile view": "Athraigh an amharcphróifíl",
|
||||
"Inbox": "Isteach",
|
||||
"Outbox": "Outbox",
|
||||
"Sent": "Seolta",
|
||||
"Search and follow": "Cuardaigh agus leanúint",
|
||||
"Refresh": "Athnuachan",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Leasainm nó URL. Bloc ag baint úsáide as *@fearainn nó leasainm@fearainn",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Foilsigh alt blagála",
|
||||
"Featured writer": "Scríbhneoir mór le rá",
|
||||
"Broch mode": "Modh broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Ní ghlactar le teachtaireachtaí ach ó chuntais a leanann",
|
||||
"Next": "Ar Aghaidh",
|
||||
"Preview": "Réamhamharc"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "नया डीएम बनाएं",
|
||||
"Switch to profile view": "प्रोफ़ाइल दृश्य पर स्विच करें",
|
||||
"Inbox": "इनबॉक्स",
|
||||
"Outbox": "आउटबॉक्स",
|
||||
"Sent": "भेज दिया",
|
||||
"Search and follow": "खोज और अनुसरण करें",
|
||||
"Refresh": "ताज़ा करना",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "उपनाम या URL। *@डोमेन या उपनाम@डोमेन का उपयोग करके ब्लॉक करें",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "एक ब्लॉग लेख प्रकाशित करें",
|
||||
"Featured writer": "फीचर्ड लेखक",
|
||||
"Broch mode": "ब्रोच मोड",
|
||||
"Pixel": "पिक्सेल"
|
||||
"Pixel": "पिक्सेल",
|
||||
"DM bounce": "संदेश केवल अनुसरण किए गए खातों से स्वीकार किए जाते हैं",
|
||||
"Next": "अगला",
|
||||
"Preview": "पूर्वावलोकन"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Crea un nuovo messaggio diretto",
|
||||
"Switch to profile view": "Passa alla vista profilo",
|
||||
"Inbox": "Arrivo",
|
||||
"Outbox": "In uscita",
|
||||
"Sent": "Inviata",
|
||||
"Search and follow": "Cerca e segui",
|
||||
"Refresh": "Ricaricare",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Soprannome o URL. Blocca usando *@domain o nickname@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Pubblica un articolo sul blog",
|
||||
"Featured writer": "Scrittore in primo piano",
|
||||
"Broch mode": "Modalità Broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "I messaggi sono accettati solo dagli account seguiti",
|
||||
"Next": "Il prossimo",
|
||||
"Preview": "Anteprima"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "新しいDMを作成する",
|
||||
"Switch to profile view": "縦断ビューに切り替え",
|
||||
"Inbox": "受信トレイ",
|
||||
"Outbox": "送信トレイ",
|
||||
"Sent": "送信済み",
|
||||
"Search and follow": "検索してフォローする",
|
||||
"Refresh": "リフレッシュ",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "ニックネームまたはURL。 * @ domainまたはnickname @ domainを使用してブロックする",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "ブログ記事を公開する",
|
||||
"Featured writer": "注目の作家",
|
||||
"Broch mode": "ブロッホモード",
|
||||
"Pixel": "ピクセル"
|
||||
"Pixel": "ピクセル",
|
||||
"DM bounce": "メッセージはフォローされているアカウントからのみ受け付けられます",
|
||||
"Next": "次",
|
||||
"Preview": "プレビュー"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@
|
|||
"Remove": "Suprimir",
|
||||
"Refresh": "Actualizar",
|
||||
"Search and follow": "Cercar e seguir",
|
||||
"Outbox": "Enviats",
|
||||
"Sent": "Enviats",
|
||||
"Inbox": "Recepcion",
|
||||
"Switch to profile view": "Passar a la vista perfil",
|
||||
"Create a new DM": "Crear un messatge dirèct nòu",
|
||||
|
|
@ -366,5 +366,8 @@
|
|||
"Publish a blog article": "Publish a blog article",
|
||||
"Featured writer": "Featured writer",
|
||||
"Broch mode": "Broch mode",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Messages are only accepted from followed accounts",
|
||||
"Next": "Next",
|
||||
"Preview": "Preview"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@
|
|||
"Create a new post": "Crie uma nova postagem",
|
||||
"Create a new DM": "Crie uma nova mensagem direta",
|
||||
"Switch to profile view": "Mudar para a vista de perfil",
|
||||
"Inbox": "Caixa de entrada",
|
||||
"Outbox": "Caixa de fora",
|
||||
"Inbox": "Entrada",
|
||||
"Sent": "Enviado",
|
||||
"Search and follow": "Pesquise e siga",
|
||||
"Refresh": "Atualizar",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Apelido ou URL. Bloquear usando *@domain ou apelido@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Publique um artigo de blog",
|
||||
"Featured writer": "Escritor em destaque",
|
||||
"Broch mode": "Modo broch",
|
||||
"Pixel": "Pixel"
|
||||
"Pixel": "Pixel",
|
||||
"DM bounce": "Mensagens são aceitas apenas de contas seguidas",
|
||||
"Next": "Próxima",
|
||||
"Preview": "Antevisão"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "Создать новое прямое сообщение",
|
||||
"Switch to profile view": "Переключиться на вид профиля",
|
||||
"Inbox": "входящие",
|
||||
"Outbox": "Исходящие",
|
||||
"Sent": "Отправлено",
|
||||
"Search and follow": "Искать и следовать",
|
||||
"Refresh": "обновление",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "Псевдоним или URL. Блокировка с использованием *@domain или псевдоним@domain",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "Опубликовать статью в блоге",
|
||||
"Featured writer": "Избранный писатель",
|
||||
"Broch mode": "Брош режим",
|
||||
"Pixel": "Пиксель"
|
||||
"Pixel": "Пиксель",
|
||||
"DM bounce": "Сообщения принимаются только от следующих аккаунтов",
|
||||
"Next": "Следующий",
|
||||
"Preview": "Предварительный просмотр"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"Create a new DM": "建立新的直接讯息",
|
||||
"Switch to profile view": "切换到个人资料视图",
|
||||
"Inbox": "收件箱",
|
||||
"Outbox": "发件箱",
|
||||
"Sent": "发送",
|
||||
"Search and follow": "搜索并关注",
|
||||
"Refresh": "刷新",
|
||||
"Nickname or URL. Block using *@domain or nickname@domain": "昵称或网址。 使用*@domain或昵称@domain阻止",
|
||||
|
|
@ -370,5 +370,8 @@
|
|||
"Publish a blog article": "发布博客文章",
|
||||
"Featured writer": "特色作家",
|
||||
"Broch mode": "断点模式",
|
||||
"Pixel": "像素点"
|
||||
"Pixel": "像素点",
|
||||
"DM bounce": "仅接受来自后续帐户的邮件",
|
||||
"Next": "下一个",
|
||||
"Preview": "预览"
|
||||
}
|
||||
|
|
|
|||
3
utils.py
3
utils.py
|
|
@ -1273,7 +1273,8 @@ def _isReservedName(nickname: str) -> bool:
|
|||
'likes', 'users', 'statuses', 'tags',
|
||||
'accounts', 'channels', 'profile', 'u',
|
||||
'updates', 'repeat', 'announce',
|
||||
'shares', 'fonts', 'icons', 'avatars')
|
||||
'shares', 'fonts', 'icons', 'avatars',
|
||||
'welcome')
|
||||
if nickname in reservedNames:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -176,19 +176,42 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
if ' ' not in lineStr:
|
||||
if '#' not in lineStr:
|
||||
if '*' not in lineStr:
|
||||
continue
|
||||
if not lineStr.startswith('['):
|
||||
if not lineStr.startswith('=> '):
|
||||
continue
|
||||
lineStr = lineStr.strip()
|
||||
words = lineStr.split(' ')
|
||||
# get the link
|
||||
linkStr = None
|
||||
for word in words:
|
||||
if word == '#':
|
||||
if not lineStr.startswith('['):
|
||||
words = lineStr.split(' ')
|
||||
# get the link
|
||||
for word in words:
|
||||
if word == '#':
|
||||
continue
|
||||
if word == '*':
|
||||
continue
|
||||
if word == '=>':
|
||||
continue
|
||||
if '://' in word:
|
||||
linkStr = word
|
||||
break
|
||||
else:
|
||||
# markdown link
|
||||
if ']' not in lineStr:
|
||||
continue
|
||||
if word == '*':
|
||||
if '(' not in lineStr:
|
||||
continue
|
||||
if '://' in word:
|
||||
linkStr = word
|
||||
break
|
||||
if ')' not in lineStr:
|
||||
continue
|
||||
linkStr = lineStr.split('(')[1]
|
||||
if ')' not in linkStr:
|
||||
continue
|
||||
linkStr = linkStr.split(')')[0]
|
||||
if '://' not in linkStr:
|
||||
continue
|
||||
lineStr = lineStr.split('[')[1]
|
||||
if ']' not in lineStr:
|
||||
continue
|
||||
lineStr = lineStr.split(']')[0]
|
||||
if linkStr:
|
||||
lineStr = lineStr.replace(linkStr, '').strip()
|
||||
# avoid any dubious scripts being added
|
||||
|
|
@ -203,6 +226,17 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
'rel="nofollow noopener noreferrer">' + \
|
||||
lineStr + '</a></p>\n'
|
||||
linksFileContainsEntries = True
|
||||
elif lineStr.startswith('=> '):
|
||||
# gemini style link
|
||||
lineStr = lineStr.replace('=> ', '')
|
||||
lineStr = lineStr.replace(linkStr, '')
|
||||
# add link to the returned html
|
||||
htmlStr += \
|
||||
' <p><a href="' + linkStr + \
|
||||
'" target="_blank" ' + \
|
||||
'rel="nofollow noopener noreferrer">' + \
|
||||
lineStr.strip() + '</a></p>\n'
|
||||
linksFileContainsEntries = True
|
||||
else:
|
||||
if lineStr.startswith('#') or lineStr.startswith('*'):
|
||||
lineStr = lineStr[1:].strip()
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ def headerButtonsTimeline(defaultTimeline: str,
|
|||
'<a href="' + usersPath + \
|
||||
'/outbox"><button class="' + \
|
||||
sentButton + '" tabindex="-1">' + \
|
||||
'<span>' + translate['Outbox'] + \
|
||||
'<span>' + translate['Sent'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
# add other buttons
|
||||
|
|
|
|||
|
|
@ -1269,10 +1269,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
# If this is the inbox timeline then don't show the repeat icon on any DMs
|
||||
showRepeatIcon = showRepeats
|
||||
isPublicRepeat = False
|
||||
showDMicon = False
|
||||
postIsDM = isDM(postJsonObject)
|
||||
if showRepeats:
|
||||
if isDM(postJsonObject):
|
||||
showDMicon = True
|
||||
if postIsDM:
|
||||
showRepeatIcon = False
|
||||
else:
|
||||
if not isPublicPost(postJsonObject):
|
||||
|
|
@ -1347,7 +1346,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
_logPostTiming(enableTimingLog, postStartTime, '9')
|
||||
|
||||
# Show a DM icon for DMs in the inbox timeline
|
||||
if showDMicon:
|
||||
if postIsDM:
|
||||
titleStr = \
|
||||
titleStr + ' <img loading="lazy" src="/' + \
|
||||
'icons/dm.png" class="DMicon"/>\n'
|
||||
|
|
@ -1501,7 +1500,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'" class="' + timeClass + '">' + publishedStr + '</a>\n'
|
||||
|
||||
# change the background color for DMs in inbox timeline
|
||||
if showDMicon:
|
||||
if postIsDM:
|
||||
containerClassIcons = 'containericons dm'
|
||||
containerClass = 'container dm'
|
||||
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
menuInbox = \
|
||||
htmlHideFromScreenReader('📥') + ' ' + translate['Inbox']
|
||||
menuOutbox = \
|
||||
htmlHideFromScreenReader('📤') + ' ' + translate['Outbox']
|
||||
htmlHideFromScreenReader('📤') + ' ' + translate['Sent']
|
||||
menuSearch = \
|
||||
htmlHideFromScreenReader('🔍') + ' ' + \
|
||||
translate['Search and follow']
|
||||
|
|
|
|||
103
webapp_utils.py
103
webapp_utils.py
|
|
@ -21,6 +21,109 @@ from content import addHtmlTags
|
|||
from content import replaceEmojiFromTags
|
||||
|
||||
|
||||
def _markdownEmphasisHtml(markdown: str) -> str:
|
||||
"""Add italics and bold html markup to the given markdown
|
||||
"""
|
||||
replacements = {
|
||||
' **': ' <b>',
|
||||
'** ': '</b> ',
|
||||
'**.': '</b>.',
|
||||
'**:': '</b>:',
|
||||
'**;': '</b>;',
|
||||
'**,': '</b>,',
|
||||
'**\n': '</b>\n',
|
||||
' *': ' <i>',
|
||||
'* ': '</i> ',
|
||||
'*.': '</i>.',
|
||||
'*:': '</i>:',
|
||||
'*;': '</i>;',
|
||||
'*,': '</i>,',
|
||||
'*\n': '</i>\n',
|
||||
' _': ' <ul>',
|
||||
'_ ': '</ul> ',
|
||||
'_.': '</ul>.',
|
||||
'_:': '</ul>:',
|
||||
'_;': '</ul>;',
|
||||
'_,': '</ul>,',
|
||||
'_\n': '</ul>\n'
|
||||
}
|
||||
for md, html in replacements.items():
|
||||
markdown = markdown.replace(md, html)
|
||||
|
||||
if markdown.startswith('**'):
|
||||
markdown = markdown[2:] + '<b>'
|
||||
elif markdown.startswith('*'):
|
||||
markdown = markdown[1:] + '<i>'
|
||||
elif markdown.startswith('_'):
|
||||
markdown = markdown[1:] + '<ul>'
|
||||
|
||||
if markdown.endswith('**'):
|
||||
markdown = markdown[:len(markdown) - 2] + '</b>'
|
||||
elif markdown.endswith('*'):
|
||||
markdown = markdown[:len(markdown) - 1] + '</i>'
|
||||
elif markdown.endswith('_'):
|
||||
markdown = markdown[:len(markdown) - 1] + '</ul>'
|
||||
return markdown
|
||||
|
||||
|
||||
def markdownToHtml(markdown: str) -> str:
|
||||
"""Converts markdown formatted text to html
|
||||
"""
|
||||
markdown = _markdownEmphasisHtml(markdown)
|
||||
# replace markdown style links with html links
|
||||
replaceLinks = {}
|
||||
text = markdown
|
||||
while '[' in text:
|
||||
if ')' not in text:
|
||||
break
|
||||
text = text.split('[', 1)[1]
|
||||
markdownLink = '[' + text.split(')')[0] + ')'
|
||||
if ']' not in markdownLink or \
|
||||
'(' not in markdownLink:
|
||||
text = text.split(')', 1)[1]
|
||||
continue
|
||||
replaceLinks[markdownLink] = \
|
||||
'<a href="' + \
|
||||
markdownLink.split('(')[1].split(')')[0] + \
|
||||
'" target="_blank" rel="nofollow noopener noreferrer">' + \
|
||||
markdownLink.split('[')[1].split(']')[0] + \
|
||||
'</a>'
|
||||
text = text.split(')', 1)[1]
|
||||
for mdLink, htmlLink in replaceLinks.items():
|
||||
markdown = markdown.replace(mdLink, htmlLink)
|
||||
|
||||
# replace headers
|
||||
linesList = markdown.split('\n')
|
||||
htmlStr = ''
|
||||
ctr = 0
|
||||
for line in linesList:
|
||||
if ctr > 0:
|
||||
htmlStr += '<br>'
|
||||
if line.startswith('#####'):
|
||||
line = line.replace('#####', '').strip()
|
||||
line = '<h5>' + line + '</h5>'
|
||||
ctr = -1
|
||||
elif line.startswith('####'):
|
||||
line = line.replace('####', '').strip()
|
||||
line = '<h4>' + line + '</h4>'
|
||||
ctr = -1
|
||||
elif line.startswith('###'):
|
||||
line = line.replace('###', '').strip()
|
||||
line = '<h3>' + line + '</h3>'
|
||||
ctr = -1
|
||||
elif line.startswith('##'):
|
||||
line = line.replace('##', '').strip()
|
||||
line = '<h2>' + line + '</h2>'
|
||||
ctr = -1
|
||||
elif line.startswith('#'):
|
||||
line = line.replace('#', '').strip()
|
||||
line = '<h1>' + line + '</h1>'
|
||||
ctr = -1
|
||||
htmlStr += line
|
||||
ctr += 1
|
||||
return htmlStr
|
||||
|
||||
|
||||
def getBrokenLinkSubstitute() -> str:
|
||||
"""Returns html used to show a default image if the link to
|
||||
an image is broken
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
__filename__ = "webapp_welcome.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.2.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from utils import getConfigParam
|
||||
from utils import removeHtml
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import markdownToHtml
|
||||
|
||||
|
||||
def isWelcomeScreenComplete(baseDir: str, nickname: str, domain: str) -> bool:
|
||||
"""Returns true if the welcome screen is complete for the given account
|
||||
"""
|
||||
accountPath = baseDir + '/accounts/' + nickname + '@' + domain
|
||||
if not os.path.isdir(accountPath):
|
||||
return
|
||||
completeFilename = accountPath + '/.welcome_complete'
|
||||
return os.path.isfile(completeFilename)
|
||||
|
||||
|
||||
def welcomeScreenIsComplete(baseDir: str,
|
||||
nickname: str, domain: str) -> None:
|
||||
"""Indicates that the welcome screen has been shown for a given account
|
||||
"""
|
||||
accountPath = baseDir + '/accounts/' + nickname + '@' + domain
|
||||
if not os.path.isdir(accountPath):
|
||||
return
|
||||
completeFilename = accountPath + '/.welcome_complete'
|
||||
completeFile = open(completeFilename, 'w+')
|
||||
if completeFile:
|
||||
completeFile.write('\n')
|
||||
completeFile.close()
|
||||
|
||||
|
||||
def htmlWelcomeScreen(baseDir: str, nickname: str,
|
||||
language: str, translate: {},
|
||||
currScreen='welcome') -> str:
|
||||
"""Returns the welcome screen
|
||||
"""
|
||||
# set a custom background for the welcome screen
|
||||
if os.path.isfile(baseDir + '/accounts/welcome-background-custom.jpg'):
|
||||
if not os.path.isfile(baseDir + '/accounts/welcome-background.jpg'):
|
||||
copyfile(baseDir + '/accounts/welcome-background-custom.jpg',
|
||||
baseDir + '/accounts/welcome-background.jpg')
|
||||
|
||||
welcomeText = 'Welcome to Epicyon'
|
||||
welcomeFilename = baseDir + '/accounts/' + currScreen + '.md'
|
||||
if not os.path.isfile(welcomeFilename):
|
||||
defaultFilename = \
|
||||
baseDir + '/defaultwelcome/' + currScreen + '_' + language + '.md'
|
||||
if not os.path.isfile(defaultFilename):
|
||||
defaultFilename = \
|
||||
baseDir + '/defaultwelcome/' + currScreen + '_en.md'
|
||||
copyfile(defaultFilename, welcomeFilename)
|
||||
|
||||
instanceTitle = \
|
||||
getConfigParam(baseDir, 'instanceTitle')
|
||||
if not instanceTitle:
|
||||
instanceTitle = 'Epicyon'
|
||||
|
||||
if os.path.isfile(welcomeFilename):
|
||||
with open(welcomeFilename, 'r') as welcomeFile:
|
||||
welcomeText = welcomeFile.read()
|
||||
welcomeText = welcomeText.replace('INSTANCE', instanceTitle)
|
||||
welcomeText = markdownToHtml(removeHtml(welcomeText))
|
||||
|
||||
welcomeForm = ''
|
||||
cssFilename = baseDir + '/epicyon-welcome.css'
|
||||
if os.path.isfile(baseDir + '/welcome.css'):
|
||||
cssFilename = baseDir + '/welcome.css'
|
||||
|
||||
welcomeForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
|
||||
welcomeForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" ' + \
|
||||
'action="/users/' + nickname + '/profiledata">\n'
|
||||
welcomeForm += '<div class="container">' + welcomeText + '</div>\n'
|
||||
welcomeForm += ' <div class="container next">\n'
|
||||
welcomeForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="previewAvatar">' + translate['Next'] + '</button>\n'
|
||||
welcomeForm += ' </div>\n'
|
||||
welcomeForm += '</div>\n'
|
||||
welcomeForm += '</form>\n'
|
||||
welcomeForm += htmlFooter()
|
||||
return welcomeForm
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
__filename__ = "webapp_welcome_final.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.2.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from utils import removeHtml
|
||||
from utils import getConfigParam
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import markdownToHtml
|
||||
|
||||
|
||||
def htmlWelcomeFinal(baseDir: str, nickname: str, domain: str,
|
||||
httpPrefix: str, domainFull: str,
|
||||
language: str, translate: {}) -> str:
|
||||
"""Returns the final welcome screen after first login
|
||||
"""
|
||||
# set a custom background for the welcome screen
|
||||
if os.path.isfile(baseDir + '/accounts/welcome-background-custom.jpg'):
|
||||
if not os.path.isfile(baseDir + '/accounts/welcome-background.jpg'):
|
||||
copyfile(baseDir + '/accounts/welcome-background-custom.jpg',
|
||||
baseDir + '/accounts/welcome-background.jpg')
|
||||
|
||||
finalText = 'Welcome to Epicyon'
|
||||
finalFilename = baseDir + '/accounts/welcome_final.md'
|
||||
if not os.path.isfile(finalFilename):
|
||||
defaultFilename = \
|
||||
baseDir + '/defaultwelcome/final_' + language + '.md'
|
||||
if not os.path.isfile(defaultFilename):
|
||||
defaultFilename = baseDir + '/defaultwelcome/final_en.md'
|
||||
copyfile(defaultFilename, finalFilename)
|
||||
|
||||
instanceTitle = \
|
||||
getConfigParam(baseDir, 'instanceTitle')
|
||||
if not instanceTitle:
|
||||
instanceTitle = 'Epicyon'
|
||||
|
||||
if os.path.isfile(finalFilename):
|
||||
with open(finalFilename, 'r') as finalFile:
|
||||
finalText = finalFile.read()
|
||||
finalText = finalText.replace('INSTANCE', instanceTitle)
|
||||
finalText = markdownToHtml(removeHtml(finalText))
|
||||
|
||||
finalForm = ''
|
||||
cssFilename = baseDir + '/epicyon-welcome.css'
|
||||
if os.path.isfile(baseDir + '/welcome.css'):
|
||||
cssFilename = baseDir + '/welcome.css'
|
||||
|
||||
finalForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
|
||||
|
||||
finalForm += '<div class="container">' + finalText + '</div>\n'
|
||||
finalForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" ' + \
|
||||
'action="/users/' + nickname + '/profiledata">\n'
|
||||
finalForm += '<div class="container next">\n'
|
||||
finalForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="previewAvatar">' + translate['Go Back'] + '</button>\n'
|
||||
finalForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="welcomeCompleteButton">' + translate['Next'] + '</button>\n'
|
||||
finalForm += '</div>\n'
|
||||
|
||||
finalForm += '</form>\n'
|
||||
finalForm += htmlFooter()
|
||||
return finalForm
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
__filename__ = "webapp_welcome_profile.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.2.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from utils import removeHtml
|
||||
from utils import loadJson
|
||||
from utils import getConfigParam
|
||||
from utils import getImageExtensions
|
||||
from utils import getImageFormats
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import markdownToHtml
|
||||
|
||||
|
||||
def htmlWelcomeProfile(baseDir: str, nickname: str, domain: str,
|
||||
httpPrefix: str, domainFull: str,
|
||||
language: str, translate: {}) -> str:
|
||||
"""Returns the welcome profile screen to set avatar and bio
|
||||
"""
|
||||
# set a custom background for the welcome screen
|
||||
if os.path.isfile(baseDir + '/accounts/welcome-background-custom.jpg'):
|
||||
if not os.path.isfile(baseDir + '/accounts/welcome-background.jpg'):
|
||||
copyfile(baseDir + '/accounts/welcome-background-custom.jpg',
|
||||
baseDir + '/accounts/welcome-background.jpg')
|
||||
|
||||
profileText = 'Welcome to Epicyon'
|
||||
profileFilename = baseDir + '/accounts/welcome_profile.md'
|
||||
if not os.path.isfile(profileFilename):
|
||||
defaultFilename = \
|
||||
baseDir + '/defaultwelcome/profile_' + language + '.md'
|
||||
if not os.path.isfile(defaultFilename):
|
||||
defaultFilename = baseDir + '/defaultwelcome/profile_en.md'
|
||||
copyfile(defaultFilename, profileFilename)
|
||||
|
||||
instanceTitle = \
|
||||
getConfigParam(baseDir, 'instanceTitle')
|
||||
if not instanceTitle:
|
||||
instanceTitle = 'Epicyon'
|
||||
|
||||
if os.path.isfile(profileFilename):
|
||||
with open(profileFilename, 'r') as profileFile:
|
||||
profileText = profileFile.read()
|
||||
profileText = profileText.replace('INSTANCE', instanceTitle)
|
||||
profileText = markdownToHtml(removeHtml(profileText))
|
||||
|
||||
profileForm = ''
|
||||
cssFilename = baseDir + '/epicyon-welcome.css'
|
||||
if os.path.isfile(baseDir + '/welcome.css'):
|
||||
cssFilename = baseDir + '/welcome.css'
|
||||
|
||||
profileForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
|
||||
|
||||
# get the url of the avatar
|
||||
for ext in getImageExtensions():
|
||||
avatarFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '/avatar.' + ext
|
||||
if os.path.isfile(avatarFilename):
|
||||
break
|
||||
avatarUrl = \
|
||||
httpPrefix + '://' + domainFull + \
|
||||
'/users/' + nickname + '/avatar.' + ext
|
||||
|
||||
imageFormats = getImageFormats()
|
||||
profileForm += '<div class="container">' + profileText + '</div>\n'
|
||||
profileForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" ' + \
|
||||
'action="/users/' + nickname + '/profiledata">\n'
|
||||
profileForm += '<div class="container">\n'
|
||||
profileForm += ' <center>\n'
|
||||
profileForm += ' <img class="welcomeavatar" src="'
|
||||
profileForm += avatarUrl + '"><br>\n'
|
||||
profileForm += ' <input type="file" id="avatar" name="avatar" '
|
||||
profileForm += 'accept="' + imageFormats + '">\n'
|
||||
profileForm += ' </center>\n'
|
||||
profileForm += '</div>\n'
|
||||
|
||||
profileForm += '<center>\n'
|
||||
profileForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="previewAvatar">' + translate['Preview'] + '</button> '
|
||||
profileForm += '</center>\n'
|
||||
|
||||
actorFilename = baseDir + '/accounts/' + nickname + '@' + domain + '.json'
|
||||
actorJson = loadJson(actorFilename)
|
||||
displayNickname = actorJson['name']
|
||||
profileForm += '<div class="container">\n'
|
||||
profileForm += ' <label class="labels">' + \
|
||||
translate['Nickname'] + '</label><br>\n'
|
||||
profileForm += ' <input type="text" name="displayNickname" value="' + \
|
||||
displayNickname + '"><br>\n'
|
||||
|
||||
bioStr = \
|
||||
actorJson['summary'].replace('<p>', '').replace('</p>', '')
|
||||
profileForm += ' <label class="labels">' + \
|
||||
translate['Your bio'] + '</label><br>\n'
|
||||
profileForm += ' <textarea id="message" name="bio" ' + \
|
||||
'style="height:130px">' + bioStr + '</textarea>\n'
|
||||
profileForm += '</div>\n'
|
||||
|
||||
profileForm += '<div class="container next">\n'
|
||||
profileForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="initialWelcomeScreen">' + translate['Go Back'] + '</button> '
|
||||
profileForm += \
|
||||
' <button type="submit" class="button" ' + \
|
||||
'name="finalWelcomeScreen">' + translate['Next'] + '</button>\n'
|
||||
profileForm += '</div>\n'
|
||||
|
||||
profileForm += '</form>\n'
|
||||
profileForm += htmlFooter()
|
||||
return profileForm
|
||||
Loading…
Reference in New Issue