main
Bob Mottram 2021-02-25 23:32:03 +00:00
commit 2fb1cdbac2
91 changed files with 1352 additions and 81 deletions

131
daemon.py
View File

@ -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 \

View File

@ -0,0 +1,9 @@
### تهانينا!
أنت الآن جاهز لبدء استخدام Epicyon. هذه مساحة اجتماعية خاضعة للإشراف ، لذا يرجى التأكد من الالتزام بـ [شروط الخدمة](/terms) الخاصة بنا ، واستمتع.
#### تلميحات
استخدم رمز **المكبر** 🔍 للبحث عن مقابض الكون المشترك ومتابعة الأشخاص.
يؤدي تحديد **الشعار في الجزء العلوي** من الشاشة إلى التبديل بين عرض المخطط الزمني وملف التعريف الخاص بك.
لن يتم تحديث الشاشة تلقائيًا عند وصول المنشورات ، لذا استخدم **F5** أو زر البريد الوارد للتحديث.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,9 @@
### बधाई हो!
अब आप एपिसकॉन का उपयोग शुरू करने के लिए तैयार हैं। यह एक मध्यम सामाजिक स्थान है, इसलिए कृपया हमारी [सेवा की शर्तों](/terms) का पालन करना सुनिश्चित करें, और मज़े करें।
#### संकेत
फ़ेडरिवर्स हैंडल की खोज करने और लोगों का अनुसरण करने के लिए **आवर्धक आइकन** का उपयोग करें।
समय दृश्य और आपकी प्रोफ़ाइल के बीच स्क्रीन स्विच के शीर्ष **पर स्थित** बैनर का चयन करना।
पोस्ट आने पर स्क्रीन अपने आप रिफ्रेश नहीं होगी, इसलिए रीफ्रेश करने के लिए **F5** या **इनबॉक्स** बटन का उपयोग करें।

View File

@ -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.

View File

@ -0,0 +1,9 @@
### おめでとう!
これで、Epicyonの使用を開始する準備が整いました。 適度な社交空間ですので、必ず [利用規約](/terms) を遵守して楽しんでください。
#### ヒント
**拡大鏡** アイコン🔍を使用して、fediverseハンドルを検索し、人々をフォローします。
画面の上部にある **バナー** を選択すると、タイムラインビューとプロファイルが切り替わります。
投稿が到着しても画面は自動的に更新されないため、 **F5** または **受信トレイ** ボタンを使用して更新してください。

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,9 @@
### Поздравляю!
Теперь вы готовы начать использовать Epicyon. Это модерируемое социальное пространство, поэтому, пожалуйста, соблюдайте наши [условия обслуживания](/terms) и получайте удовольствие.
#### Подсказки
Используйте значок **лупы** 🔍, чтобы искать нужные метки и следить за людьми.
При выборе **баннера вверху** экрана выполняется переключение между представлением временной шкалы и вашим профилем.
Экран не обновляется автоматически при поступлении сообщений, поэтому используйте **F5** или кнопку **Входящие** для обновления.

View File

@ -0,0 +1,9 @@
### 恭喜你!
您现在可以开始使用Epicyon。 这是一个温和的社交空间,因此请务必遵守我们的[服务条款](/terms),并从中获得乐趣。
####提示
使用放大镜图标search搜索fed性的手柄并关注他人。
选择屏幕顶部的横幅广告可在时间轴视图和个人资料之间切换。
帖子到达时屏幕不会自动刷新因此请使用F5或“收件箱”按钮刷新。

View File

@ -0,0 +1,2 @@
### اعدادات الحساب
حدد صورتك الرمزية وأضف اسمك ووصفك. استخدم صورة رمزية صغيرة (على سبيل المثال ، 128 × 128 بكسل) بحيث يمكن تنزيلها بسرعة.

View File

@ -0,0 +1,2 @@
### Configuració del compte
Seleccioneu la vostra imatge davatar i afegiu el vostre nom i la vostra descripció. Utilitzeu una imatge davatar petita (per exemple, 128x128 píxels) per baixar-la ràpidament.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,2 @@
### Socrú Cuntas
Roghnaigh díomhá avatar agus cuir dainm agus do thuairisc leis. Úsáid íomhá bheag avatar (m.sh. 128x128 picteilín) ionas go mbeidh sí gasta le híoslódáil.

View File

@ -0,0 +1,2 @@
### खाता स्थापित करना
अपनी अवतार छवि का चयन करें और अपना नाम और विवरण जोड़ें। एक छोटी अवतार छवि (जैसे। 128x128 पिक्सेल) का उपयोग करें ताकि यह डाउनलोड करने में तेज हो।

View File

@ -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.

View File

@ -0,0 +1,2 @@
### アカウントの設定
アバター画像を選択し、名前と説明を追加します。 小さなアバター画像128x128ピクセルなどを使用して、すばやくダウンロードできるようにします。

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,2 @@
### Настройка учетной записи
Выберите изображение своего аватара и добавьте свое имя и описание. Используйте небольшое изображение аватара (например, 128x128 пикселей), чтобы его можно было быстро загрузить.

View File

@ -0,0 +1,2 @@
### 帐户设定
选择您的头像图片并添加您的姓名和描述。 使用较小的头像图片例如128x128像素以便快速下载。

View File

@ -0,0 +1,6 @@
### مرحبًا بكم في INSTANCE
هذا خادم ActivityPub مصمم للاستضافة الذاتية السهلة لعدد قليل من الأشخاص على أنظمة منخفضة الطاقة ، مثل أجهزة الكمبيوتر ذات اللوحة الواحدة أو أجهزة الكمبيوتر المحمولة القديمة.
قم بتشغيل وجودك على الشبكة الاجتماعية بالطريقة التي تريدها ، وداعًا لشركة Big Tech.
الآن ، لنبدأ ...

View File

@ -0,0 +1,6 @@
### Benvingut a INSTANCE
Es tracta dun 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 ...

View File

@ -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 ...

View File

@ -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 ...

View File

@ -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...

View File

@ -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 ...

View File

@ -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 ...

View File

@ -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 ...

View File

@ -0,0 +1,6 @@
### INSTANCE पर आपका स्वागत है
यह एक एक्टिविटीपब सर्वर है जो कम पावर सिस्टम पर सिंगल बोर्ड कंप्यूटर या पुराने लैपटॉप जैसे कुछ लोगों की आसान सेल्फ-होस्टिंग के लिए बनाया गया है।
जिस तरह से आप चाहते हैं, अपने खुद के सोशल नेटवर्क उपस्थिति को चलाएं और बिग टेक को अलविदा कहें।
अब, चल रहा है ...

View File

@ -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 ...

View File

@ -0,0 +1,6 @@
### INSTANCEへようこそ
これは、シングルボードコンピューターや古いラップトップなどの低電力システムで数人を簡単にセルフホスティングするために設計されたActivityPubサーバーです。
独自のソーシャルネットワークプレゼンスを希望どおりに実行し、BigTechに別れを告げます。
さあ、始めましょう...

View File

@ -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...

View File

@ -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 ...

View File

@ -0,0 +1,6 @@
### Добро пожаловать в INSTANCE
Это сервер ActivityPub, предназначенный для простого самостоятельного размещения нескольких человек в системах с низким энергопотреблением, таких как одноплатные компьютеры или старые ноутбуки.
Управляйте своим присутствием в социальных сетях так, как вы хотите, и попрощайтесь с Big Tech.
А теперь поехали ...

View File

@ -0,0 +1,6 @@
### 欢迎来到INSTANCE
这是一个ActivityPub服务器设计用于在低功耗系统例如单板计算机或旧笔记本电脑上轻松实现一些人的自我托管。
随心所欲地经营自己的社交网络并与Big Tech道别。
现在,开始吧...

View File

@ -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);
}

239
epicyon-welcome.css 100644
View File

@ -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;
}
}

View File

@ -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 +

View File

@ -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
View File

@ -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'])

View File

@ -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()

View File

@ -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'):

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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"
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "معاينة"
}

View File

@ -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 saccepten des dels comptes seguits",
"Next": "Pròxim",
"Preview": "Vista prèvia"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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": "पूर्वावलोकन"
}

View File

@ -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"
}

View File

@ -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": "プレビュー"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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": "Предварительный просмотр"
}

View File

@ -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": "预览"
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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'

View File

@ -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']

View File

@ -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

93
webapp_welcome.py 100644
View File

@ -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

View File

@ -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

View File

@ -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