diff --git a/daemon.py b/daemon.py index b147c100d..3008469f4 100644 --- a/daemon.py +++ b/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 \ diff --git a/defaultwelcome/final_ar.md b/defaultwelcome/final_ar.md new file mode 100644 index 000000000..0d663d636 --- /dev/null +++ b/defaultwelcome/final_ar.md @@ -0,0 +1,9 @@ +### تهانينا! +أنت الآن جاهز لبدء استخدام Epicyon. هذه مساحة اجتماعية خاضعة للإشراف ، لذا يرجى التأكد من الالتزام بـ [شروط الخدمة](/terms) الخاصة بنا ، واستمتع. + +#### تلميحات +استخدم رمز **المكبر** 🔍 للبحث عن مقابض الكون المشترك ومتابعة الأشخاص. + +يؤدي تحديد **الشعار في الجزء العلوي** من الشاشة إلى التبديل بين عرض المخطط الزمني وملف التعريف الخاص بك. + +لن يتم تحديث الشاشة تلقائيًا عند وصول المنشورات ، لذا استخدم **F5** أو زر البريد الوارد للتحديث. diff --git a/defaultwelcome/final_ca.md b/defaultwelcome/final_ca.md new file mode 100644 index 000000000..deff371ba --- /dev/null +++ b/defaultwelcome/final_ca.md @@ -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. diff --git a/defaultwelcome/final_cy.md b/defaultwelcome/final_cy.md new file mode 100644 index 000000000..12211aa7a --- /dev/null +++ b/defaultwelcome/final_cy.md @@ -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. diff --git a/defaultwelcome/final_en.md b/defaultwelcome/final_en.md new file mode 100644 index 000000000..81aad26cd --- /dev/null +++ b/defaultwelcome/final_en.md @@ -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. diff --git a/defaultwelcome/final_es.md b/defaultwelcome/final_es.md new file mode 100644 index 000000000..9050750a9 --- /dev/null +++ b/defaultwelcome/final_es.md @@ -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. diff --git a/defaultwelcome/final_fr.md b/defaultwelcome/final_fr.md new file mode 100644 index 000000000..272133245 --- /dev/null +++ b/defaultwelcome/final_fr.md @@ -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. diff --git a/defaultwelcome/final_ga.md b/defaultwelcome/final_ga.md new file mode 100644 index 000000000..b8c1e576b --- /dev/null +++ b/defaultwelcome/final_ga.md @@ -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. diff --git a/defaultwelcome/final_hi.md b/defaultwelcome/final_hi.md new file mode 100644 index 000000000..971af274c --- /dev/null +++ b/defaultwelcome/final_hi.md @@ -0,0 +1,9 @@ +### बधाई हो! +अब आप एपिसकॉन का उपयोग शुरू करने के लिए तैयार हैं। यह एक मध्यम सामाजिक स्थान है, इसलिए कृपया हमारी [सेवा की शर्तों](/terms) का पालन करना सुनिश्चित करें, और मज़े करें। + +#### संकेत +फ़ेडरिवर्स हैंडल की खोज करने और लोगों का अनुसरण करने के लिए **आवर्धक आइकन** का उपयोग करें। + +समय दृश्य और आपकी प्रोफ़ाइल के बीच स्क्रीन स्विच के शीर्ष **पर स्थित** बैनर का चयन करना। + +पोस्ट आने पर स्क्रीन अपने आप रिफ्रेश नहीं होगी, इसलिए रीफ्रेश करने के लिए **F5** या **इनबॉक्स** बटन का उपयोग करें। diff --git a/defaultwelcome/final_it.md b/defaultwelcome/final_it.md new file mode 100644 index 000000000..818b231f9 --- /dev/null +++ b/defaultwelcome/final_it.md @@ -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. diff --git a/defaultwelcome/final_ja.md b/defaultwelcome/final_ja.md new file mode 100644 index 000000000..40b4e2575 --- /dev/null +++ b/defaultwelcome/final_ja.md @@ -0,0 +1,9 @@ +### おめでとう! +これで、Epicyonの使用を開始する準備が整いました。 適度な社交空間ですので、必ず [利用規約](/terms) を遵守して楽しんでください。 + +#### ヒント +**拡大鏡** アイコン🔍を使用して、fediverseハンドルを検索し、人々をフォローします。 + +画面の上部にある **バナー** を選択すると、タイムラインビューとプロファイルが切り替わります。 + +投稿が到着しても画面は自動的に更新されないため、 **F5** または **受信トレイ** ボタンを使用して更新してください。 diff --git a/defaultwelcome/final_oc.md b/defaultwelcome/final_oc.md new file mode 100644 index 000000000..ef9eb2601 --- /dev/null +++ b/defaultwelcome/final_oc.md @@ -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. diff --git a/defaultwelcome/final_pt.md b/defaultwelcome/final_pt.md new file mode 100644 index 000000000..3f8cdf87b --- /dev/null +++ b/defaultwelcome/final_pt.md @@ -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. diff --git a/defaultwelcome/final_ru.md b/defaultwelcome/final_ru.md new file mode 100644 index 000000000..31e11700f --- /dev/null +++ b/defaultwelcome/final_ru.md @@ -0,0 +1,9 @@ +### Поздравляю! +Теперь вы готовы начать использовать Epicyon. Это модерируемое социальное пространство, поэтому, пожалуйста, соблюдайте наши [условия обслуживания](/terms) и получайте удовольствие. + +#### Подсказки +Используйте значок **лупы** 🔍, чтобы искать нужные метки и следить за людьми. + +При выборе **баннера вверху** экрана выполняется переключение между представлением временной шкалы и вашим профилем. + +Экран не обновляется автоматически при поступлении сообщений, поэтому используйте **F5** или кнопку **Входящие** для обновления. diff --git a/defaultwelcome/final_zh.md b/defaultwelcome/final_zh.md new file mode 100644 index 000000000..1cd50c41b --- /dev/null +++ b/defaultwelcome/final_zh.md @@ -0,0 +1,9 @@ +### 恭喜你! +您现在可以开始使用Epicyon。 这是一个温和的社交空间,因此请务必遵守我们的[服务条款](/terms),并从中获得乐趣。 + +####提示 +使用放大镜图标search搜索fed性的手柄并关注他人。 + +选择屏幕顶部的横幅广告可在时间轴视图和个人资料之间切换。 + +帖子到达时,屏幕不会自动刷新,因此请使用F5或“收件箱”按钮刷新。 diff --git a/defaultwelcome/profile_ar.md b/defaultwelcome/profile_ar.md new file mode 100644 index 000000000..0d0ed4ab9 --- /dev/null +++ b/defaultwelcome/profile_ar.md @@ -0,0 +1,2 @@ +### اعدادات الحساب +حدد صورتك الرمزية وأضف اسمك ووصفك. استخدم صورة رمزية صغيرة (على سبيل المثال ، 128 × 128 بكسل) بحيث يمكن تنزيلها بسرعة. diff --git a/defaultwelcome/profile_ca.md b/defaultwelcome/profile_ca.md new file mode 100644 index 000000000..90c5d6c85 --- /dev/null +++ b/defaultwelcome/profile_ca.md @@ -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. diff --git a/defaultwelcome/profile_cy.md b/defaultwelcome/profile_cy.md new file mode 100644 index 000000000..92ced863e --- /dev/null +++ b/defaultwelcome/profile_cy.md @@ -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. diff --git a/defaultwelcome/profile_de.md b/defaultwelcome/profile_de.md new file mode 100644 index 000000000..668da69ce --- /dev/null +++ b/defaultwelcome/profile_de.md @@ -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. diff --git a/defaultwelcome/profile_en.md b/defaultwelcome/profile_en.md new file mode 100644 index 000000000..2a1768332 --- /dev/null +++ b/defaultwelcome/profile_en.md @@ -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. diff --git a/defaultwelcome/profile_es.md b/defaultwelcome/profile_es.md new file mode 100644 index 000000000..63bac75b9 --- /dev/null +++ b/defaultwelcome/profile_es.md @@ -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. diff --git a/defaultwelcome/profile_fr.md b/defaultwelcome/profile_fr.md new file mode 100644 index 000000000..56f93d648 --- /dev/null +++ b/defaultwelcome/profile_fr.md @@ -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. diff --git a/defaultwelcome/profile_ga.md b/defaultwelcome/profile_ga.md new file mode 100644 index 000000000..b0019e644 --- /dev/null +++ b/defaultwelcome/profile_ga.md @@ -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. diff --git a/defaultwelcome/profile_hi.md b/defaultwelcome/profile_hi.md new file mode 100644 index 000000000..5190b999d --- /dev/null +++ b/defaultwelcome/profile_hi.md @@ -0,0 +1,2 @@ +### खाता स्थापित करना +अपनी अवतार छवि का चयन करें और अपना नाम और विवरण जोड़ें। एक छोटी अवतार छवि (जैसे। 128x128 पिक्सेल) का उपयोग करें ताकि यह डाउनलोड करने में तेज हो। diff --git a/defaultwelcome/profile_it.md b/defaultwelcome/profile_it.md new file mode 100644 index 000000000..97487bc62 --- /dev/null +++ b/defaultwelcome/profile_it.md @@ -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. diff --git a/defaultwelcome/profile_ja.md b/defaultwelcome/profile_ja.md new file mode 100644 index 000000000..d63a80d55 --- /dev/null +++ b/defaultwelcome/profile_ja.md @@ -0,0 +1,2 @@ +### アカウントの設定 +アバター画像を選択し、名前と説明を追加します。 小さなアバター画像(128x128ピクセルなど)を使用して、すばやくダウンロードできるようにします。 diff --git a/defaultwelcome/profile_oc.md b/defaultwelcome/profile_oc.md new file mode 100644 index 000000000..10d1e09e3 --- /dev/null +++ b/defaultwelcome/profile_oc.md @@ -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. diff --git a/defaultwelcome/profile_pt.md b/defaultwelcome/profile_pt.md new file mode 100644 index 000000000..9371c6ad3 --- /dev/null +++ b/defaultwelcome/profile_pt.md @@ -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. diff --git a/defaultwelcome/profile_ru.md b/defaultwelcome/profile_ru.md new file mode 100644 index 000000000..b0d53a568 --- /dev/null +++ b/defaultwelcome/profile_ru.md @@ -0,0 +1,2 @@ +### Настройка учетной записи +Выберите изображение своего аватара и добавьте свое имя и описание. Используйте небольшое изображение аватара (например, 128x128 пикселей), чтобы его можно было быстро загрузить. diff --git a/defaultwelcome/profile_zh.md b/defaultwelcome/profile_zh.md new file mode 100644 index 000000000..33a1b0a6d --- /dev/null +++ b/defaultwelcome/profile_zh.md @@ -0,0 +1,2 @@ +### 帐户设定 +选择您的头像图片并添加您的姓名和描述。 使用较小的头像图片(例如128x128像素),以便快速下载。 diff --git a/defaultwelcome/welcome_ar.md b/defaultwelcome/welcome_ar.md new file mode 100644 index 000000000..8e88deb0a --- /dev/null +++ b/defaultwelcome/welcome_ar.md @@ -0,0 +1,6 @@ +### مرحبًا بكم في INSTANCE +هذا خادم ActivityPub مصمم للاستضافة الذاتية السهلة لعدد قليل من الأشخاص على أنظمة منخفضة الطاقة ، مثل أجهزة الكمبيوتر ذات اللوحة الواحدة أو أجهزة الكمبيوتر المحمولة القديمة. + +قم بتشغيل وجودك على الشبكة الاجتماعية بالطريقة التي تريدها ، وداعًا لشركة Big Tech. + +الآن ، لنبدأ ... diff --git a/defaultwelcome/welcome_ca.md b/defaultwelcome/welcome_ca.md new file mode 100644 index 000000000..0721e7e14 --- /dev/null +++ b/defaultwelcome/welcome_ca.md @@ -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 ... diff --git a/defaultwelcome/welcome_cy.md b/defaultwelcome/welcome_cy.md new file mode 100644 index 000000000..7596a7927 --- /dev/null +++ b/defaultwelcome/welcome_cy.md @@ -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 ... diff --git a/defaultwelcome/welcome_de.md b/defaultwelcome/welcome_de.md new file mode 100644 index 000000000..f1dedb95b --- /dev/null +++ b/defaultwelcome/welcome_de.md @@ -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 ... diff --git a/defaultwelcome/welcome_en.md b/defaultwelcome/welcome_en.md new file mode 100644 index 000000000..5d942a9a5 --- /dev/null +++ b/defaultwelcome/welcome_en.md @@ -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... diff --git a/defaultwelcome/welcome_es.md b/defaultwelcome/welcome_es.md new file mode 100644 index 000000000..f9ba98454 --- /dev/null +++ b/defaultwelcome/welcome_es.md @@ -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 ... diff --git a/defaultwelcome/welcome_fr.md b/defaultwelcome/welcome_fr.md new file mode 100644 index 000000000..9fd72437e --- /dev/null +++ b/defaultwelcome/welcome_fr.md @@ -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 ... diff --git a/defaultwelcome/welcome_ga.md b/defaultwelcome/welcome_ga.md new file mode 100644 index 000000000..c9a11c680 --- /dev/null +++ b/defaultwelcome/welcome_ga.md @@ -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 ... diff --git a/defaultwelcome/welcome_hi.md b/defaultwelcome/welcome_hi.md new file mode 100644 index 000000000..13923d095 --- /dev/null +++ b/defaultwelcome/welcome_hi.md @@ -0,0 +1,6 @@ +### INSTANCE पर आपका स्वागत है +यह एक एक्टिविटीपब सर्वर है जो कम पावर सिस्टम पर सिंगल बोर्ड कंप्यूटर या पुराने लैपटॉप जैसे कुछ लोगों की आसान सेल्फ-होस्टिंग के लिए बनाया गया है। + +जिस तरह से आप चाहते हैं, अपने खुद के सोशल नेटवर्क उपस्थिति को चलाएं और बिग टेक को अलविदा कहें। + +अब, चल रहा है ... diff --git a/defaultwelcome/welcome_it.md b/defaultwelcome/welcome_it.md new file mode 100644 index 000000000..498caf115 --- /dev/null +++ b/defaultwelcome/welcome_it.md @@ -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 ... diff --git a/defaultwelcome/welcome_ja.md b/defaultwelcome/welcome_ja.md new file mode 100644 index 000000000..7e21a1821 --- /dev/null +++ b/defaultwelcome/welcome_ja.md @@ -0,0 +1,6 @@ +### INSTANCEへようこそ +これは、シングルボードコンピューターや古いラップトップなどの低電力システムで数人を簡単にセルフホスティングするために設計されたActivityPubサーバーです。 + +独自のソーシャルネットワークプレゼンスを希望どおりに実行し、BigTechに別れを告げます。 + +さあ、始めましょう... diff --git a/defaultwelcome/welcome_oc.md b/defaultwelcome/welcome_oc.md new file mode 100644 index 000000000..2a90b3a52 --- /dev/null +++ b/defaultwelcome/welcome_oc.md @@ -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... diff --git a/defaultwelcome/welcome_pt.md b/defaultwelcome/welcome_pt.md new file mode 100644 index 000000000..f302f5aec --- /dev/null +++ b/defaultwelcome/welcome_pt.md @@ -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 ... diff --git a/defaultwelcome/welcome_ru.md b/defaultwelcome/welcome_ru.md new file mode 100644 index 000000000..65c82400d --- /dev/null +++ b/defaultwelcome/welcome_ru.md @@ -0,0 +1,6 @@ +### Добро пожаловать в INSTANCE +Это сервер ActivityPub, предназначенный для простого самостоятельного размещения нескольких человек в системах с низким энергопотреблением, таких как одноплатные компьютеры или старые ноутбуки. + +Управляйте своим присутствием в социальных сетях так, как вы хотите, и попрощайтесь с Big Tech. + +А теперь поехали ... diff --git a/defaultwelcome/welcome_zh.md b/defaultwelcome/welcome_zh.md new file mode 100644 index 000000000..978734e94 --- /dev/null +++ b/defaultwelcome/welcome_zh.md @@ -0,0 +1,6 @@ +### 欢迎来到INSTANCE +这是一个ActivityPub服务器,设计用于在低功耗系统(例如单板计算机或旧笔记本电脑)上轻松实现一些人的自我托管。 + +随心所欲地经营自己的社交网络,并与Big Tech道别。 + +现在,开始吧... diff --git a/epicyon-profile.css b/epicyon-profile.css index 2afefdc66..384c60893 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -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); } diff --git a/epicyon-welcome.css b/epicyon-welcome.css new file mode 100644 index 000000000..21a2ec8a1 --- /dev/null +++ b/epicyon-welcome.css @@ -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; + } +} diff --git a/epicyon.py b/epicyon.py index cf0289c65..5b5d01b8b 100644 --- a/epicyon.py +++ b/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 + diff --git a/follow.py b/follow.py index cce18f1b2..c3a383664 100644 --- a/follow.py +++ b/follow.py @@ -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] diff --git a/inbox.py b/inbox.py index 86507b87d..0efca270d 100644 --- a/inbox.py +++ b/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']) diff --git a/tests.py b/tests.py index 02fe2b604..ddab940d4 100644 --- a/tests.py +++ b/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 bold' + + markdown = 'This is *italic*' + assert markdownToHtml(markdown) == 'This is italic' + + markdown = 'This is _underlined_' + assert markdownToHtml(markdown) == 'This is ' + + markdown = 'This is **just** plain text' + assert markdownToHtml(markdown) == 'This is just plain text' + + markdown = '# Title1\n### Title3\n## Title2\n' + assert markdownToHtml(markdown) == \ + '

Title1

Title3

Title2

' + + markdown = \ + 'This is [a link](https://something.somewhere) to something.\n' + \ + 'And [something else](https://cat.pic).' + assert markdownToHtml(markdown) == \ + 'This is ' + \ + 'a link to something.
' + \ + 'And ' + \ + 'something else.' + + def runAllTests(): print('Running tests...') testFunctions() + testMarkdownToHtml() testValidHashTag() testPrepareHtmlPostNickname() testDomainHandling() diff --git a/theme.py b/theme.py index dbf78cf27..008d0a7f3 100644 --- a/theme.py +++ b/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'): diff --git a/theme/blue/theme.json b/theme/blue/theme.json index 2abaf6666..39bb7effd 100644 --- a/theme/blue/theme.json +++ b/theme/blue/theme.json @@ -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", diff --git a/theme/debian/theme.json b/theme/debian/theme.json index 225bf948c..a494551b9 100644 --- a/theme/debian/theme.json +++ b/theme/debian/theme.json @@ -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", diff --git a/theme/hacker/theme.json b/theme/hacker/theme.json index a33b48b7b..b4ceed266 100644 --- a/theme/hacker/theme.json +++ b/theme/hacker/theme.json @@ -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", diff --git a/theme/henge/theme.json b/theme/henge/theme.json index 0048d7b0d..4f6fce492 100644 --- a/theme/henge/theme.json +++ b/theme/henge/theme.json @@ -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", diff --git a/theme/indymediaclassic/theme.json b/theme/indymediaclassic/theme.json index 80d9c6eff..1605d885b 100644 --- a/theme/indymediaclassic/theme.json +++ b/theme/indymediaclassic/theme.json @@ -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" } diff --git a/theme/indymediamodern/theme.json b/theme/indymediamodern/theme.json index 8f9d41143..88884f3bf 100644 --- a/theme/indymediamodern/theme.json +++ b/theme/indymediamodern/theme.json @@ -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", diff --git a/theme/lcd/theme.json b/theme/lcd/theme.json index 8f80f567e..457d0bf61 100644 --- a/theme/lcd/theme.json +++ b/theme/lcd/theme.json @@ -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", diff --git a/theme/light/theme.json b/theme/light/theme.json index 586a04bcd..836b0c2be 100644 --- a/theme/light/theme.json +++ b/theme/light/theme.json @@ -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", diff --git a/theme/night/theme.json b/theme/night/theme.json index f29c4cd12..d7ebe9bb0 100644 --- a/theme/night/theme.json +++ b/theme/night/theme.json @@ -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", diff --git a/theme/pixel/theme.json b/theme/pixel/theme.json index d51e87316..0aec8c372 100644 --- a/theme/pixel/theme.json +++ b/theme/pixel/theme.json @@ -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", diff --git a/theme/purple/theme.json b/theme/purple/theme.json index 3a1e8582d..66b17a17c 100644 --- a/theme/purple/theme.json +++ b/theme/purple/theme.json @@ -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", diff --git a/theme/rc3/theme.json b/theme/rc3/theme.json index 95b54d546..e41930aec 100644 --- a/theme/rc3/theme.json +++ b/theme/rc3/theme.json @@ -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", diff --git a/theme/solidaric/theme.json b/theme/solidaric/theme.json index e3e46b6ff..cdb9c08c3 100644 --- a/theme/solidaric/theme.json +++ b/theme/solidaric/theme.json @@ -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", diff --git a/theme/starlight/theme.json b/theme/starlight/theme.json index 1be4c4002..f91141886 100644 --- a/theme/starlight/theme.json +++ b/theme/starlight/theme.json @@ -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", diff --git a/theme/zen/theme.json b/theme/zen/theme.json index 03b743247..7eb3b5d86 100644 --- a/theme/zen/theme.json +++ b/theme/zen/theme.json @@ -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", diff --git a/translations/ar.json b/translations/ar.json index ecd8cced3..5ced3e7f3 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -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": "معاينة" } diff --git a/translations/ca.json b/translations/ca.json index fd87456c5..289209daa 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -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" } diff --git a/translations/cy.json b/translations/cy.json index c14bab40a..e113a37ae 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -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" } diff --git a/translations/de.json b/translations/de.json index b1336dc61..6b5cfd4a8 100644 --- a/translations/de.json +++ b/translations/de.json @@ -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" } diff --git a/translations/en.json b/translations/en.json index f98ac234d..a80f3da5f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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" } diff --git a/translations/es.json b/translations/es.json index 83869d7f2..a18c27d52 100644 --- a/translations/es.json +++ b/translations/es.json @@ -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" } diff --git a/translations/fr.json b/translations/fr.json index c579130b3..e0a42b4c2 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -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" } diff --git a/translations/ga.json b/translations/ga.json index a225f7303..f459bc1b0 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -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" } diff --git a/translations/hi.json b/translations/hi.json index e500e6f66..13e65d5f3 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -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": "पूर्वावलोकन" } diff --git a/translations/it.json b/translations/it.json index b626f6d8a..8446396c8 100644 --- a/translations/it.json +++ b/translations/it.json @@ -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" } diff --git a/translations/ja.json b/translations/ja.json index 03a83f83b..f8ee10565 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -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": "プレビュー" } diff --git a/translations/oc.json b/translations/oc.json index 1221d8e4d..e597567f2 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -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" } diff --git a/translations/pt.json b/translations/pt.json index 83d726b1b..10e0bcd8b 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -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" } diff --git a/translations/ru.json b/translations/ru.json index 93e5e2d6b..bf1f5129b 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -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": "Предварительный просмотр" } diff --git a/translations/zh.json b/translations/zh.json index eb981683b..9b09b3300 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -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": "预览" } diff --git a/utils.py b/utils.py index 8f2348062..25499145e 100644 --- a/utils.py +++ b/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 diff --git a/webapp_column_left.py b/webapp_column_left.py index 14651564f..598b53821 100644 --- a/webapp_column_left.py +++ b/webapp_column_left.py @@ -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 + '

\n' linksFileContainsEntries = True + elif lineStr.startswith('=> '): + # gemini style link + lineStr = lineStr.replace('=> ', '') + lineStr = lineStr.replace(linkStr, '') + # add link to the returned html + htmlStr += \ + '

' + \ + lineStr.strip() + '

\n' + linksFileContainsEntries = True else: if lineStr.startswith('#') or lineStr.startswith('*'): lineStr = lineStr[1:].strip() diff --git a/webapp_headerbuttons.py b/webapp_headerbuttons.py index bf922d588..85c2d9fb3 100644 --- a/webapp_headerbuttons.py +++ b/webapp_headerbuttons.py @@ -202,7 +202,7 @@ def headerButtonsTimeline(defaultTimeline: str, '' # add other buttons diff --git a/webapp_post.py b/webapp_post.py index b5646c416..f66d53a87 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -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 + ' \n' @@ -1501,7 +1500,7 @@ def individualPostAsHtml(allowDownloads: bool, '" class="' + timeClass + '">' + publishedStr + '\n' # change the background color for DMs in inbox timeline - if showDMicon: + if postIsDM: containerClassIcons = 'containericons dm' containerClass = 'container dm' diff --git a/webapp_timeline.py b/webapp_timeline.py index 8f2e4c399..1c3cf7f63 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -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'] diff --git a/webapp_utils.py b/webapp_utils.py index 64b1cae89..5c3983bc2 100644 --- a/webapp_utils.py +++ b/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 = { + ' **': ' ', + '** ': ' ', + '**.': '.', + '**:': ':', + '**;': ';', + '**,': ',', + '**\n': '\n', + ' *': ' ', + '* ': ' ', + '*.': '.', + '*:': ':', + '*;': ';', + '*,': ',', + '*\n': '\n', + ' _': ' ', + '_.': '.', + '_:': ':', + '_;': ';', + '_,': ',', + '_\n': '\n' + } + for md, html in replacements.items(): + markdown = markdown.replace(md, html) + + if markdown.startswith('**'): + markdown = markdown[2:] + '' + elif markdown.startswith('*'): + markdown = markdown[1:] + '' + elif markdown.startswith('_'): + markdown = markdown[1:] + '' + 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] = \ + '' + \ + markdownLink.split('[')[1].split(']')[0] + \ + '' + 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 += '
' + if line.startswith('#####'): + line = line.replace('#####', '').strip() + line = '
' + line + '
' + ctr = -1 + elif line.startswith('####'): + line = line.replace('####', '').strip() + line = '

' + line + '

' + ctr = -1 + elif line.startswith('###'): + line = line.replace('###', '').strip() + line = '

' + line + '

' + ctr = -1 + elif line.startswith('##'): + line = line.replace('##', '').strip() + line = '

' + line + '

' + ctr = -1 + elif line.startswith('#'): + line = line.replace('#', '').strip() + line = '

' + line + '

' + 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 diff --git a/webapp_welcome.py b/webapp_welcome.py new file mode 100644 index 000000000..77c868707 --- /dev/null +++ b/webapp_welcome.py @@ -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 += \ + '
\n' + welcomeForm += '
' + welcomeText + '
\n' + welcomeForm += ' \n' + welcomeForm += '\n' + welcomeForm += '
\n' + welcomeForm += htmlFooter() + return welcomeForm diff --git a/webapp_welcome_final.py b/webapp_welcome_final.py new file mode 100644 index 000000000..2ae2c8996 --- /dev/null +++ b/webapp_welcome_final.py @@ -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 += '
' + finalText + '
\n' + finalForm += \ + '
\n' + finalForm += '\n' + + finalForm += '
\n' + finalForm += htmlFooter() + return finalForm diff --git a/webapp_welcome_profile.py b/webapp_welcome_profile.py new file mode 100644 index 000000000..341ad5b55 --- /dev/null +++ b/webapp_welcome_profile.py @@ -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 += '
' + profileText + '
\n' + profileForm += \ + '
\n' + profileForm += '
\n' + profileForm += '
\n' + profileForm += '
\n' + profileForm += ' \n' + profileForm += '
\n' + profileForm += '
\n' + + profileForm += '
\n' + profileForm += \ + ' ' + profileForm += '
\n' + + actorFilename = baseDir + '/accounts/' + nickname + '@' + domain + '.json' + actorJson = loadJson(actorFilename) + displayNickname = actorJson['name'] + profileForm += '
\n' + profileForm += '
\n' + profileForm += '
\n' + + bioStr = \ + actorJson['summary'].replace('

', '').replace('

', '') + profileForm += '
\n' + profileForm += ' \n' + profileForm += '
\n' + + profileForm += '\n' + + profileForm += '
\n' + profileForm += htmlFooter() + return profileForm