diff --git a/daemon.py b/daemon.py index d4d009d6c..5ea50f7ff 100644 --- a/daemon.py +++ b/daemon.py @@ -120,6 +120,7 @@ from blocking import getDomainBlocklist from roles import setRole from roles import clearModeratorStatus from roles import clearEditorStatus +from roles import clearCounselorStatus from blog import htmlBlogPageRSS2 from blog import htmlBlogPageRSS3 from blog import htmlBlogView @@ -4728,6 +4729,63 @@ class PubServer(BaseHTTPRequestHandler): 'instance', 'editor') + # change site counselors list + if fields.get('counselors'): + if path.startswith('/users/' + + adminNickname + '/'): + counselorsFile = \ + baseDir + \ + '/accounts/counselors.txt' + clearCounselorStatus(baseDir) + if ',' in fields['counselors']: + # if the list was given as comma separated + edFile = open(counselorsFile, "w+") + eds = fields['counselors'].split(',') + for edNick in eds: + edNick = edNick.strip() + edDir = baseDir + \ + '/accounts/' + edNick + \ + '@' + domain + if os.path.isdir(edDir): + edFile.write(edNick + '\n') + edFile.close() + eds = fields['counselors'].split(',') + for edNick in eds: + edNick = edNick.strip() + edDir = baseDir + \ + '/accounts/' + edNick + \ + '@' + domain + if os.path.isdir(edDir): + setRole(baseDir, + edNick, domain, + 'instance', 'counselor') + else: + # nicknames on separate lines + edFile = open(counselorsFile, "w+") + eds = fields['counselors'].split('\n') + for edNick in eds: + edNick = edNick.strip() + edDir = \ + baseDir + \ + '/accounts/' + edNick + \ + '@' + domain + if os.path.isdir(edDir): + edFile.write(edNick + '\n') + edFile.close() + eds = fields['counselors'].split('\n') + for edNick in eds: + edNick = edNick.strip() + edDir = \ + baseDir + \ + '/accounts/' + \ + edNick + '@' + \ + domain + if os.path.isdir(edDir): + setRole(baseDir, + edNick, domain, + 'instance', + 'counselor') + # remove scheduled posts if fields.get('removeScheduledPosts'): if fields['removeScheduledPosts'] == 'on': diff --git a/roles.py b/roles.py index 15d381648..a0d055d9d 100644 --- a/roles.py +++ b/roles.py @@ -51,6 +51,14 @@ def clearEditorStatus(baseDir: str) -> None: _clearRoleStatus(baseDir, 'editor') +def clearCounselorStatus(baseDir: str) -> None: + """Removes counselor status from all accounts + This could be slow if there are many users, but only happens + rarely when counselors are appointed or removed + """ + _clearRoleStatus(baseDir, 'editor') + + def clearModeratorStatus(baseDir: str) -> None: """Removes moderator status from all accounts This could be slow if there are many users, but only happens @@ -119,7 +127,8 @@ def setRole(baseDir: str, nickname: str, domain: str, roleFiles = { "moderator": "moderators.txt", - "editor": "editors.txt" + "editor": "editors.txt", + "counselor": "counselors.txt" } actorJson = loadJson(actorFilename) diff --git a/epicyon-notification b/scripts/epicyon-notification similarity index 81% rename from epicyon-notification rename to scripts/epicyon-notification index 03043f978..cdeb716e6 100755 --- a/epicyon-notification +++ b/scripts/epicyon-notification @@ -6,7 +6,7 @@ # # Something like: # -# */1 * * * * root /usr/local/bin/epicyon-notification --epicyon yes +# */1 * * * * root /usr/local/bin/epicyon-notification # # License # ======= @@ -29,6 +29,8 @@ PROJECT_NAME=epicyon epicyonInstallDir=/opt/${PROJECT_NAME} +MY_EMAIL_ADDRESS="username@domain" + local_domain=$HOSTNAME if [ -f /var/lib/tor/hidden_service_epicyon/hostname ]; then local_domain=$(cat /var/lib/tor/hidden_service_epicyon/hostname) @@ -38,18 +40,18 @@ fi function notification_translate_text { text="$1" if ! grep -q '"language":' "${epicyonInstallDir}/config.json"; then - echo "$text" - return + echo "$text" + return fi language=$(cat "${epicyonInstallDir}/config.json" | awk -F '"language":' '{print $2}' | awk -F '"' '{print $2}') translationsFilename="${epicyonInstallDir}/translations/${language}.json" if [ ! -f "$translationsFilename" ]; then - echo "$text" - return + echo "$text" + return fi if ! grep -q "\"$text\":" "$translationsFilename"; then - echo "$text" - return + echo "$text" + return fi grep "\"$text\":" "$translationsFilename" | awk -F '"' '{print $4}' } @@ -71,7 +73,7 @@ function matrix_server_message { MATRIX_DATA_DIR='/var/lib/matrix' homeserver_config="${MATRIX_DATA_DIR}/homeserver.yaml" - + # shellcheck disable=SC2002 MATRIX_DOMAIN_NAME=$(cat "$homeserver_config" | grep "server_name:" | head -n 1 | awk -F '"' '{print $2}') if [ ! "$MATRIX_DOMAIN_NAME" ]; then @@ -110,26 +112,42 @@ function sendNotification { USERNAME="$1" SUBJECT="$2" MESSAGE="$3" - + + hasSent= + if [ -d /etc/prosody ]; then if [ -f /usr/bin/sendxmpp ]; then + # generate a random password for a temporary user account notification_user_password=$(openssl rand -base64 32 | tr -dc A-Za-z0-9 | head -c 30 ; echo -n '') + # register a temporary xmpp user account to send the message if prosodyctl register "notification" "$local_domain" "$notification_user_password"; then if [[ "$SUBJECT" == *' Tor '* ]]; then MESSAGE="$SUBJECT" fi if [ -f /usr/bin/sendxmpp ]; then + # kill any existing message which hasn't sent kill_sendxmpp_process + # send the xmpp notification using the temporary account echo "${MESSAGE}" | /usr/bin/sendxmpp -u notification -p "${notification_user_password}" -j localhost -o ${local_domain} --message-type=headline -n -t -s ${PROJECT_NAME} ${USERNAME}@${local_domain} + hasSent=1 fi fi + # remove the temporary xmpp account prosodyctl deluser "notification@$local_domain" fi fi if [ -d /etc/matrix ]; then matrix_server_message "${USERNAME}" "${USERNAME}" "$MESSAGE" + hasSent=1 + fi + + if [ ! "$hasSent" ]; then + if [[ "$MY_EMAIL_ADDRESS" != "username@domain" ]]; then + # send to a fixed email address for a single user instance + echo "$MESSAGE" | /usr/bin/mail -s "$SUBJECT" "$MY_EMAIL_ADDRESS" + fi fi } @@ -140,28 +158,28 @@ function notifications { fi if [ ! -f "${epicyonInstallDir}/config.json" ]; then - return + return fi # shellcheck disable=SC2002 EPICYON_DOMAIN_NAME=$(cat "${epicyonInstallDir}/config.json" | awk -F '"domain":' '{print $2}' | awk -F '"' '{print $2}') for d in ${epicyonInstallDir}/accounts/*/ ; do - if [[ "$d" != *'@'* ]]; then - continue - fi - epicyonDir="${d::-1}" - USERNAME=$(echo "$epicyonDir" | awk -F '/' '{print $5}' | awk -F '@' '{print $1}') + if [[ "$d" != *'@'* ]]; then + continue + fi + epicyonDir="${d::-1}" + USERNAME=$(echo "$epicyonDir" | awk -F '/' '{print $5}' | awk -F '@' '{print $1}') # send notifications for calendar events to XMPP/email users epicyonCalendarfile="$epicyonDir/.newCalendar" if [ -f "$epicyonCalendarfile" ]; then if ! grep -q "##sent##" "$epicyonCalendarfile"; then - epicyonCalendarmessage=$(notification_translate_text 'New calendar event') + epicyonCalendarmessage=$(notification_translate_text 'Calendar') epicyonCalendarfileContent=$(echo "$epicyonCalendarmessage")" "$(cat "$epicyonCalendarfile") if [[ "$epicyonCalendarfileContent" == '/calendar'* ]]; then epicyonCalendarmessage="Epicyon: ${EPICYON_DOMAIN_NAME}/users/${USERNAME}${epicyonCalendarfileContent}" fi - sendNotification "$USERNAME" "Epicyon" "$epicyonCalendarmessage" + sendNotification "$USERNAME" "Epicyon" "$epicyonCalendarmessage" echo "##sent##" >> "$epicyonCalendarfile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonCalendarfile" fi @@ -171,12 +189,12 @@ function notifications { epicyonDMfile="$epicyonDir/.newDM" if [ -f "$epicyonDMfile" ]; then if ! grep -q "##sent##" "$epicyonDMfile"; then - epicyonDMmessage=$(notification_translate_text 'New direct message') + epicyonDMmessage=$(notification_translate_text 'DM') epicyonDMfileContent=$(echo "$epicyonDMmessage")" "$(cat "$epicyonDMfile") if [[ "$epicyonDMfileContent" == *':'* ]]; then epicyonDMmessage="Epicyon: $epicyonDMfileContent" fi - sendNotification "$USERNAME" "Epicyon" "$epicyonDMmessage" + sendNotification "$USERNAME" "Epicyon" "$epicyonDMmessage" echo "##sent##" > "$epicyonDMfile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonDMfile" fi @@ -186,7 +204,7 @@ function notifications { epicyonLikeFile="$epicyonDir/.newLike" if [ -f "$epicyonLikeFile" ]; then if ! grep -q "##sent##" "$epicyonLikeFile"; then - epicyonLikeMessage=$(notification_translate_text 'liked your post') + epicyonLikeMessage=$(notification_translate_text 'Liked by') epicyonLikeFileContent=$(cat "$epicyonLikeFile" | awk -F ' ' '{print $1}')" "$(echo "$epicyonLikeMessage")" "$(cat "$epicyonLikeFile" | awk -F ' ' '{print $2}') if [[ "$epicyonLikeFileContent" == *':'* ]]; then epicyonLikeMessage="Epicyon: $epicyonLikeFileContent" @@ -201,12 +219,12 @@ function notifications { epicyonReplyFile="$epicyonDir/.newReply" if [ -f "$epicyonReplyFile" ]; then if ! grep -q "##sent##" "$epicyonReplyFile"; then - epicyonReplyMessage=$(notification_translate_text 'New reply') + epicyonReplyMessage=$(notification_translate_text 'Replies') epicyonReplyFileContent=$(echo "$epicyonReplyMessage")" "$(cat "$epicyonReplyFile") if [[ "$epicyonReplyFileContent" == *':'* ]]; then epicyonReplyMessage="Epicyon: $epicyonReplyFileContent" fi - sendNotification "$USERNAME" "Epicyon" "$epicyonReplyMessage" + sendNotification "$USERNAME" "Epicyon" "$epicyonReplyMessage" echo "##sent##" > "$epicyonReplyFile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonReplyFile" fi @@ -216,18 +234,18 @@ function notifications { epicyonPatchFile="$epicyonDir/.newPatch" if [ -f "$epicyonPatchFile" ]; then if [ -f "${epicyonPatchFile}Content" ]; then - if ! grep -q "##sent##" "$epicyonPatchFile"; then - epicyonPatchMessage=$(cat "$epicyonPatchFile") - if [ "$epicyonPatchMessage" ]; then - # notify the member - sendNotification "$USERNAME" "Epicyon" "$epicyonPatchMessage" - echo "##sent##" > "$epicyonPatchFile" - chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonPatchFile" - # send the patch to them by email - cat "${epicyonPatchFile}Content" | mail -s "[Epicyon] $epicyonPatchMessage" "${USERNAME}@${HOSTNAME}" - rm "${epicyonPatchFile}Content" - fi - fi + if ! grep -q "##sent##" "$epicyonPatchFile"; then + epicyonPatchMessage=$(cat "$epicyonPatchFile") + if [ "$epicyonPatchMessage" ]; then + # notify the member + sendNotification "$USERNAME" "Epicyon" "$epicyonPatchMessage" + echo "##sent##" > "$epicyonPatchFile" + chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonPatchFile" + # send the patch to them by email + cat "${epicyonPatchFile}Content" | mail -s "[Epicyon] $epicyonPatchMessage" "${USERNAME}@${HOSTNAME}" + rm "${epicyonPatchFile}Content" + fi + fi fi fi @@ -235,12 +253,12 @@ function notifications { epicyonShareFile="$epicyonDir/.newShare" if [ -f "$epicyonShareFile" ]; then if ! grep -q "##sent##" "$epicyonShareFile"; then - epicyonShareMessage=$(notification_translate_text 'New shared item') + epicyonShareMessage=$(notification_translate_text 'Shares') epicyonShareFileContent=$(echo "$epicyonShareMessage")" "$(cat "$epicyonShareFile") if [[ "$epicyonShareFileContent" == *':'* ]]; then epicyonShareMessage="Epicyon: $epicyonShareFileContent" fi - sendNotification "$USERNAME" "Epicyon" "$epicyonShareMessage" + sendNotification "$USERNAME" "Epicyon" "$epicyonShareMessage" echo "##sent##" > "$epicyonShareFile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonShareFile" fi @@ -264,13 +282,13 @@ function notifications { if [ $epicyonNotify ]; then cp "$epicyonFollowFile" "$epicyonFollowNotificationsFile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonFollowNotificationsFile" - - epicyonFollowMessage=$(notification_translate_text "New follow request")" ${EPICYON_DOMAIN_NAME}/users/${USERNAME}/followers" - sendNotification "$USERNAME" "Epicyon" "$epicyonFollowMessage" + + epicyonFollowMessage=$(notification_translate_text 'Approve follower requests')" ${EPICYON_DOMAIN_NAME}/users/${USERNAME}/followers" + sendNotification "$USERNAME" "Epicyon" "$epicyonFollowMessage" fi fi fi - done + done } notifications diff --git a/speaker.py b/speaker.py index 06360e5ce..2b6ab90cc 100644 --- a/speaker.py +++ b/speaker.py @@ -380,6 +380,9 @@ def _postToSpeakerJson(baseDir: str, nickname: str, domain: str, content = urllib.parse.unquote_plus(postJsonObject['object']['content']) content = html.unescape(content) content = content.replace('

', '').replace('

', ' ') + # replace some emoji before removing html + if ' <3' in content: + content = content.replace(' <3', ' ' + translate['heart']) content = removeHtml(htmlReplaceQuoteMarks(content)) content = speakerReplaceLinks(content, translate, detectedLinks) content = _speakerPronounce(baseDir, content, translate) diff --git a/translations/ar.json b/translations/ar.json index 872bb6f69..f030124dc 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "التصفية والحظر", "Role Assignment": "تعيين الدور", "Contact Details": "بيانات المتصل", - "Background Images": "صور الخلفية" + "Background Images": "صور الخلفية", + "heart": "قلب", + "counselor": "مستشار", + "Counselors": "المستشارين" } diff --git a/translations/ca.json b/translations/ca.json index 05fd736f3..a0007c55d 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtratge i bloqueig", "Role Assignment": "Assignació de funcions", "Contact Details": "Detalls de contacte", - "Background Images": "Imatges de fons" + "Background Images": "Imatges de fons", + "heart": "cor", + "counselor": "conseller", + "Counselors": "Consellers" } diff --git a/translations/cy.json b/translations/cy.json index 43bc66b67..7ed539a03 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Hidlo a Blocio", "Role Assignment": "Aseiniad Rôl", "Background Images": "Delweddau Cefndir", - "Contact Details": "Manylion cyswllt" + "Contact Details": "Manylion cyswllt", + "heart": "galon", + "counselor": "cynghorydd", + "Counselors": "Cynghorwyr" } diff --git a/translations/de.json b/translations/de.json index a7e2e6d87..435ca103a 100644 --- a/translations/de.json +++ b/translations/de.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtern und Blockieren", "Role Assignment": "Rollenzuweisung", "Background Images": "Hintergrundbilder", - "Contact Details": "Kontaktdetails" + "Contact Details": "Kontaktdetails", + "heart": "herz", + "counselor": "Beraterin", + "Counselors": "Berater" } diff --git a/translations/en.json b/translations/en.json index 1f5f1a1de..ac5baf042 100644 --- a/translations/en.json +++ b/translations/en.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtering and Blocking", "Role Assignment": "Role Assignment", "Contact Details": "Contact Details", - "Background Images": "Background Images" + "Background Images": "Background Images", + "heart": "heart", + "counselor": "counselor", + "Counselors": "Counselors" } diff --git a/translations/es.json b/translations/es.json index 9414179e0..2660bf339 100644 --- a/translations/es.json +++ b/translations/es.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtrado y bloqueo", "Role Assignment": "Asignación de roles", "Background Images": "Imágenes de fondo", - "Contact Details": "Detalles de contacto" + "Contact Details": "Detalles de contacto", + "heart": "corazón", + "counselor": "Consejera", + "Counselors": "Consejeras" } diff --git a/translations/fr.json b/translations/fr.json index 3aba51267..784e6fe2c 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtrage et blocage", "Role Assignment": "Attribution de rôle", "Background Images": "Images d'arrière-plan", - "Contact Details": "Détails du contact" + "Contact Details": "Détails du contact", + "heart": "cœur", + "counselor": "Conseillère", + "Counselors": "Conseillères" } diff --git a/translations/ga.json b/translations/ga.json index cb98b3e88..3a792e9d9 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Scagadh agus Blocáil", "Role Assignment": "Sannadh Róil", "Background Images": "Íomhánna Cúlra", - "Contact Details": "Sonraí Teagmhála" + "Contact Details": "Sonraí Teagmhála", + "heart": "chroí", + "counselor": "Comhairleoir", + "Counselors": "Comhairleoirí" } diff --git a/translations/hi.json b/translations/hi.json index d890c16ba..70b701f8a 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "छानना और अवरुद्ध करना", "Role Assignment": "भूमिका असाइनमेंट", "Background Images": "पृष्ठभूमि छवियों", - "Contact Details": "सम्पर्क करने का विवरण" + "Contact Details": "सम्पर्क करने का विवरण", + "heart": "दिल", + "counselor": "काउंसलर", + "Counselors": "सलाहकार" } diff --git a/translations/it.json b/translations/it.json index be3e43443..1634f537d 100644 --- a/translations/it.json +++ b/translations/it.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtraggio e blocco", "Role Assignment": "Assegnazione del ruolo", "Background Images": "Immagini di sfondo", - "Contact Details": "Dettagli del contatto" + "Contact Details": "Dettagli del contatto", + "heart": "cuore", + "counselor": "Consulente", + "Counselors": "Consiglieri" } diff --git a/translations/ja.json b/translations/ja.json index d5ef7d006..0771a6559 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "フィルタリングとブロッキング", "Role Assignment": "役割の割り当て", "Background Images": "背景画像", - "Contact Details": "連絡先の詳細" + "Contact Details": "連絡先の詳細", + "heart": "ハート", + "counselor": "カウンセラー", + "Counselors": "カウンセラー" } diff --git a/translations/ku.json b/translations/ku.json index 64633d5d2..a5a937809 100644 --- a/translations/ku.json +++ b/translations/ku.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Fîlterkirin û Astengkirin", "Role Assignment": "Erk Rol", "Contact Details": "Agahdariyên Têkiliyê", - "Background Images": "Wêneyên Paşê" + "Background Images": "Wêneyên Paşê", + "heart": "dil", + "counselor": "Pêşnîyarvan", + "Counselors": "Selêwirmendan" } diff --git a/translations/oc.json b/translations/oc.json index f8495c139..9921ce049 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -392,5 +392,8 @@ "Filtering and Blocking": "Filtering and Blocking", "Role Assignment": "Role Assignment", "Background Images": "Background Images", - "Contact Details": "Contact Details" + "Contact Details": "Contact Details", + "heart": "heart", + "counselor": "Counselors", + "Counselors": "Counselors" } diff --git a/translations/pt.json b/translations/pt.json index 54ffea0d8..ab453fba9 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Filtragem e Bloqueio", "Role Assignment": "Atribuição de Função", "Background Images": "Imagens de fundo", - "Contact Details": "Detalhes do contato" + "Contact Details": "Detalhes do contato", + "heart": "coração", + "counselor": "Conselheira", + "Counselors": "Conselheiras" } diff --git a/translations/ru.json b/translations/ru.json index f171d543c..8678ff20a 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "Фильтрация и блокировка", "Role Assignment": "Назначение ролей", "Background Images": "Фоновые изображения", - "Contact Details": "Контактная информация" + "Contact Details": "Контактная информация", + "heart": "сердце", + "counselor": "Советник", + "Counselors": "Советники" } diff --git a/translations/zh.json b/translations/zh.json index 493e6e184..61a5de055 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -396,5 +396,8 @@ "Filtering and Blocking": "过滤和阻止", "Role Assignment": "角色分配", "Background Images": "背景图片", - "Contact Details": "联系方式" + "Contact Details": "联系方式", + "heart": "心", + "counselor": "顾问", + "Counselors": "辅导员" } diff --git a/webapp_profile.py b/webapp_profile.py index 272719eb5..0b5bb8db1 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -1387,6 +1387,20 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, ' ' + + # counselors + counselors = '' + counselorsFile = baseDir + '/accounts/counselors.txt' + if os.path.isfile(counselorsFile): + with open(counselorsFile, "r") as f: + counselors = f.read() + roleAssignStr += '
\n' + roleAssignStr += \ + ' ' roleAssignStr += ' \n' # Video section