Date: Mon, 19 Oct 2020 21:43:27 +0100
Subject: [PATCH 050/351] Modify mirrored news links
---
newsdaemon.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index 8caf27613..bcb3e4e32 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -488,14 +488,19 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
rssDescription = rssDescription.replace(']]>', '')
rssDescription = '' + rssDescription + '
'
+ mirrored = item[7]
+ postUrl = url
+ if mirrored and '://' in url:
+ postUrl = '/newsmirror/' + url.split('://')[1]
+
# add the off-site link to the description
if rssDescription and not dangerousMarkup(rssDescription):
rssDescription += \
- '
' + \
+ '
' + \
translate['Read more...'] + ''
else:
rssDescription = \
- '' + \
+ '' + \
translate['Read more...'] + ''
# remove image dimensions
@@ -517,7 +522,6 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
if not blog:
continue
- mirrored = item[7]
if mirrored:
if not createNewsMirror(baseDir, statusNumber,
url, maxMirroredArticles):
From 3e08f63cdb70fbf14eaa40234ab8ec464572eb61 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 22:19:22 +0100
Subject: [PATCH 051/351] News mirror path
---
newsdaemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index bcb3e4e32..31fc34aeb 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -491,7 +491,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
mirrored = item[7]
postUrl = url
if mirrored and '://' in url:
- postUrl = '/newsmirror/' + url.split('://')[1]
+ postUrl = '/newsmirror/' + statusNumber + '/' + url.split('://')[1]
# add the off-site link to the description
if rssDescription and not dangerousMarkup(rssDescription):
From 3d629a128739ba4c8c7b7101352e1160bafb1bc8 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 22:23:18 +0100
Subject: [PATCH 052/351] Clear mirrored news
---
scripts/clearnewswire | 3 +++
1 file changed, 3 insertions(+)
diff --git a/scripts/clearnewswire b/scripts/clearnewswire
index 39999724f..f2faf45e2 100755
--- a/scripts/clearnewswire
+++ b/scripts/clearnewswire
@@ -2,6 +2,9 @@
rm accounts/news@*/outbox/*
rm accounts/news@*/postcache/*
rm accounts/news@*/outbox.index
+if [ -d accounts/newsmirror ]; then
+ rm -rf accounts/newsmirror
+fi
if [ -f accounts/.newswirestate.json ]; then
rm accounts/.newswirestate.json
fi
From 23fcc996dc2f09f566cd8de6e58fcee48742c54f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 22:56:55 +0100
Subject: [PATCH 053/351] Document creation of news mirror
---
gemini/EN/install.gmi | 11 +++++++++++
website/EN/index.html | 16 +++++++++++++++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/gemini/EN/install.gmi b/gemini/EN/install.gmi
index 7f8f7f68b..e5832e3dc 100644
--- a/gemini/EN/install.gmi
+++ b/gemini/EN/install.gmi
@@ -19,6 +19,12 @@ Create a user for the server to run as:
adduser --system --home=/opt/epicyon --group epicyon
chown -R epicyon:epicyon /opt/epicyon
+Link news mirrors:
+
+ mkdir /var/www/YOUR_DOMAIN
+ mkdir -p /opt/epicyon/accounts/newsmirror
+ ln -s /opt/epicyon/accounts/newsmirror /var/www/YOUR_DOMAIN/newsmirror
+
Create a daemon:
nano /etc/systemd/system/epicyon.service
@@ -104,6 +110,11 @@ And paste the following:
index index.html;
+ location /newsmirror {
+ root /var/www/YOUR_DOMAIN;
+ try_files $uri =404;
+ }
+
location / {
proxy_http_version 1.1;
client_max_body_size 31M;
diff --git a/website/EN/index.html b/website/EN/index.html
index c3b0705ba..3d9c73553 100644
--- a/website/EN/index.html
+++ b/website/EN/index.html
@@ -1263,7 +1263,7 @@
Want to read the news from an RSS feed, or run your own Independent Media Center producing news and federating it through the network? Both of those are possible. The newswire gives you the capability of collectively moderating news as it arrives, and publishing your own articles. The administrator can assign moderator and editor roles to other users on an instance. Organize your community to overcome the limitations of corporate media.
-
+
You will need python version 3.7 or later.
@@ -1293,6 +1293,15 @@
chown -R epicyon:epicyon /opt/epicyon
+
+ Link news mirrors:
+
+
+
mkdir /var/www/YOUR_DOMAIN
+
mkdir -p /opt/epicyon/accounts/newsmirror
+
ln -s /opt/epicyon/accounts/newsmirror /var/www/YOUR_DOMAIN/newsmirror
+
+
Create a daemon:
@@ -1389,6 +1398,11 @@
index index.html;
+ location /newsmirror {
+ root /var/www/YOUR_DOMAIN;
+ try_files $uri =404;
+ }
+
location / {
proxy_http_version 1.1;
client_max_body_size 31M;
From bddb7495d5c6ae8693a0e541f5c9f1a9ce802460 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 23:01:56 +0100
Subject: [PATCH 054/351] Extra information
---
translations/en.json | 2 +-
translations/oc.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/translations/en.json b/translations/en.json
index ca15cb68b..ea90d27f3 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -295,7 +295,7 @@
"Right column image": "Right column image",
"RSS feed for this site": "RSS feed for this site",
"Edit newswire": "Edit newswire",
- "Add RSS feed links below.": "RSS feed links below. Add a * at the beginning or end to indicate that a feed should be moderated.",
+ "Add RSS feed links below.": "RSS feed links below. Add a * at the beginning or end to indicate that a feed should be moderated. Add a ! at the beginning or end to indicate that the feed content should be mirrored.",
"Newswire RSS Feed": "Newswire RSS Feed",
"Nicknames whose blog entries appear on the newswire.": "Nicknames whose blog entries appear on the newswire.",
"Posts to be approved": "Posts to be approved",
diff --git a/translations/oc.json b/translations/oc.json
index a8fb2e273..8f8c08b90 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -291,7 +291,7 @@
"Right column image": "Right column image",
"RSS feed for this site": "RSS feed for this site",
"Edit newswire": "Edit newswire",
- "Add RSS feed links below.": "RSS feed links below. Add a * at the beginning or end to indicate that a feed should be moderated.",
+ "Add RSS feed links below.": "RSS feed links below. Add a * at the beginning or end to indicate that a feed should be moderated. Add a ! at the beginning or end to indicate that the feed content should be mirrored.",
"Newswire RSS Feed": "Newswire RSS Feed",
"Nicknames whose blog entries appear on the newswire.": "Nicknames whose blog entries appear on the newswire.",
"Posts to be approved": "Posts to be approved",
From 5297eb1b644b4eaf3369e493050be3085a39f13e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 23:17:06 +0100
Subject: [PATCH 055/351] Add index
---
newsdaemon.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index 31fc34aeb..2504c0864 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -491,7 +491,8 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
mirrored = item[7]
postUrl = url
if mirrored and '://' in url:
- postUrl = '/newsmirror/' + statusNumber + '/' + url.split('://')[1]
+ postUrl = '/newsmirror/' + statusNumber + '/' + \
+ url.split('://')[1] + '/index.html'
# add the off-site link to the description
if rssDescription and not dangerousMarkup(rssDescription):
From 302e1ed03664ef07a14560164aa04d3a22f7b73d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 23:21:30 +0100
Subject: [PATCH 056/351] Handle trailing slashes
---
newsdaemon.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index 2504c0864..d4f461fcb 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -492,7 +492,11 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
postUrl = url
if mirrored and '://' in url:
postUrl = '/newsmirror/' + statusNumber + '/' + \
- url.split('://')[1] + '/index.html'
+ url.split('://')[1]
+ if postUrl.endswith('/'):
+ postUrl += 'index.html'
+ else:
+ postUrl += '/index.html'
# add the off-site link to the description
if rssDescription and not dangerousMarkup(rssDescription):
From 475c23f1fb02e49ea0d668fc94df197a603cf9bb Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 23:39:55 +0100
Subject: [PATCH 057/351] Update readme
---
README.md | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 969b37de6..1b02f500d 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ sudo apt install -y \
python3-django-timezone-field \
libimage-exiftool-perl python3-flake8 \
python3-pyqrcode python3-png python3-bandit \
- certbot nginx
+ certbot nginx wget
```
## Installation
@@ -57,6 +57,14 @@ Add a dedicated user so that we don't have to run as root.
adduser --system --home=/opt/epicyon --group epicyon
```
+Link news mirrors:
+
+``` bash
+mkdir /var/www/YOUR_DOMAIN
+mkdir -p /opt/epicyon/accounts/newsmirror
+ln -s /opt/epicyon/accounts/newsmirror /var/www/YOUR_DOMAIN/newsmirror
+```
+
Edit */etc/systemd/system/epicyon.service* and add the following:
``` systemd
@@ -147,7 +155,12 @@ server {
error_log /dev/null;
index index.html;
-
+
+ location /newsmirror {
+ root /var/www/YOUR_DOMAIN;
+ try_files $uri =404;
+ }
+
location / {
proxy_http_version 1.1;
client_max_body_size 31M;
@@ -255,4 +268,3 @@ To run the network tests. These simulate instances exchanging messages.
``` bash
python3 epicyon.py --testsnetwork
```
-
From 24c1b2401c80e0ed723f8cdc71442ab5bbbf9c20 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 19 Oct 2020 23:47:42 +0100
Subject: [PATCH 058/351] Update onion deployment
---
deploy/onion | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/deploy/onion b/deploy/onion
index c60df40b1..cf277b4e6 100755
--- a/deploy/onion
+++ b/deploy/onion
@@ -229,6 +229,9 @@ fi
if [ ! -d ${web_dir}/cache ]; then
mkdir ${web_dir}/cache
fi
+if [ ! -d /var/www/${ONION_DOMAIN}/htdocs ]; then
+ mkdir -p /var/www/${ONION_DOMAIN}/htdocs
+fi
echo "Creating nginx virtual host for ${ONION_DOMAIN}"
{ echo "proxy_cache_path ${web_dir}/cache levels=1:2 keys_zone=my_cache:10m max_size=10g";
@@ -252,6 +255,12 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}"
echo ' error_log /dev/null;';
echo '';
echo ' index index.html;';
+ echo '';
+ echo ' location /newsmirror {';
+ echo ' root /var/www/${ONION_DOMAIN}/htdocs;';
+ echo ' try_files $uri =404;';
+ echo ' }';
+ echo '';
echo ' location / {';
echo ' proxy_http_version 1.1;';
echo ' client_max_body_size 31M;';
@@ -299,6 +308,13 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}"
echo ' }';
echo '}'; } > "/etc/nginx/sites-available/${username}"
+chown -R www-data:www-data /var/www/${ONION_DOMAIN}/htdocs
+if [ ! -d ${install_destination}/accounts/newsmirror ]; then
+ mkdir -p ${install_destination}/accounts/newsmirror
+ chown -R ${username}:${username} ${install_destination}
+fi
+ln -s ${install_destination}/newsmirror /var/www/${ONION_DOMAIN}/htdocs/newsmirror
+
ln -s "/etc/nginx/sites-available/${username}" /etc/nginx/sites-enabled/
systemctl restart nginx
From 06ab99a96c703a73547dc47662cfb3dfaa20c839 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 09:48:58 +0100
Subject: [PATCH 059/351] Add wget to dependencies
---
gemini/EN/install.gmi | 2 +-
website/EN/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/gemini/EN/install.gmi b/gemini/EN/install.gmi
index e5832e3dc..06000e78b 100644
--- a/gemini/EN/install.gmi
+++ b/gemini/EN/install.gmi
@@ -4,7 +4,7 @@ You will need python version 3.7 or later.
On a Debian based system:
- sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-pycryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-django-timezone-field python3-pyqrcode python3-png python3-bandit libimage-exiftool-perl certbot nginx
+ sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-pycryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-django-timezone-field python3-pyqrcode python3-png python3-bandit libimage-exiftool-perl certbot nginx wget
The following instructions install Epicyon to the /opt directory. It's not essential that it be installed there, and it could be in any other preferred directory.
diff --git a/website/EN/index.html b/website/EN/index.html
index 3d9c73553..3a8023cfb 100644
--- a/website/EN/index.html
+++ b/website/EN/index.html
@@ -1269,7 +1269,7 @@
You will need python version 3.7 or later.
On a Debian based system:
-
sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-pycryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-django-timezone-field python3-pyqrcode python3-png python3-bandit libimage-exiftool-perl certbot nginx
+
sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-pycryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-django-timezone-field python3-pyqrcode python3-png python3-bandit libimage-exiftool-perl certbot nginx wget
From ddadcf1f8baff8dc490ebcd63cda643ecceb045c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 10:27:58 +0100
Subject: [PATCH 060/351] Onion instances mirror via tor
---
newsdaemon.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index d4f461fcb..3b3297590 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -350,7 +350,8 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
return True
-def createNewsMirror(baseDir: str, postIdNumber: str, url: str,
+def createNewsMirror(baseDir: str, domain: str,
+ postIdNumber: str, url: str,
maxMirroredArticles: int) -> bool:
"""Creates a local mirror of a news article
"""
@@ -408,14 +409,20 @@ def createNewsMirror(baseDir: str, postIdNumber: str, url: str,
# already mirrored
return True
+ # for onion instances mirror via tor
+ prefixStr = ''
+ if domain.endswith('.onion'):
+ prefixStr = '/usr/bin/torsocks '
+
# download the files
commandStr = \
- '/usr/bin/wget -mkEpnp -e robots=off ' + url + \
+ prefixStr + '/usr/bin/wget -mkEpnp -e robots=off ' + url + \
' -P ' + mirrorArticleDir
p = Popen(commandStr, shell=True)
os.waitpid(p.pid, 0)
if not os.path.isdir(mirrorArticleDir):
+ print('WARN: failed to mirror ' + url)
return True
# append the post Id number to the index file
@@ -528,7 +535,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
continue
if mirrored:
- if not createNewsMirror(baseDir, statusNumber,
+ if not createNewsMirror(baseDir, domain, statusNumber,
url, maxMirroredArticles):
continue
From c9f3d21b4115ee01b7fb31d942e265b224c76060 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 10:43:30 +0100
Subject: [PATCH 061/351] Support xor in hashtag rules
---
newsdaemon.py | 17 ++++++++++++++++-
tests.py | 2 +-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index 3b3297590..91229b2fe 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -136,6 +136,21 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
if argValue:
return True
return False
+ elif tree[0] == 'xor':
+ if len(tree) >= 3:
+ trueCtr = 0
+ for argIndex in range(1, len(tree)):
+ argValue = False
+ if isinstance(tree[argIndex], str):
+ argValue = (tree[argIndex] in hashtags)
+ elif isinstance(tree[argIndex], list):
+ argValue = hashtagRuleResolve(tree[argIndex],
+ hashtags, moderated,
+ content)
+ if argValue:
+ trueCtr += 1
+ if trueCtr == 1:
+ return True
elif tree[0].startswith('#') and len(tree) == 1:
return tree[0] in hashtags
elif tree[0].startswith('moderated'):
@@ -239,7 +254,7 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
content = content.lower()
# actionOccurred = False
- operators = ('not', 'and', 'or', 'contains')
+ operators = ('not', 'and', 'or', 'xor', 'contains')
for ruleStr in rules:
if not ruleStr:
continue
diff --git a/tests.py b/tests.py
index 6e06fd9ab..0bc9ea0dc 100644
--- a/tests.py
+++ b/tests.py
@@ -2177,7 +2177,7 @@ def testRemoveHtmlTag():
def testHashtagRuleTree():
print('testHashtagRuleTree')
- operators = ('not', 'and', 'or', 'contains')
+ operators = ('not', 'and', 'or', 'xor', 'contains')
moderated = True
conditionsStr = \
From 7a08940f0dfabadf90cbfa2902b5ada4654cde2d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 10:52:35 +0100
Subject: [PATCH 062/351] Hashtag rules documentation
---
hashtagrules.txt | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/hashtagrules.txt b/hashtagrules.txt
index 211b25452..8e80a15cf 100644
--- a/hashtagrules.txt
+++ b/hashtagrules.txt
@@ -5,6 +5,21 @@ As news arrives via RSS or Atom feeds it can be processed to add or remove hasht
On the newswire edit screen, available to moderators, you can define the news processing rules. There is one rule per line.
+Syntax
+------
+
+if [conditions] then [action]
+
+Logical Operators
+-----------------
+
+The following operators are available:
+
+ not, and, or, xor, contains
+
+Examples
+--------
+
A simple example is:
if moderated and not #oxfordimc then block
@@ -13,7 +28,7 @@ For moderated feeds this will only allow items through if they have the #oxfordi
If you want to add hashtags an example is:
- if contains "garden" then add #gardening
+ if contains "garden" or contains "lawn" then add #gardening
So if incoming news contains the word "garden" either in its title or description then it will automatically be assigned the hashtag #gardening. You can also add hashtags based upon other hashtags.
From 719ec924130408bce04fbbcc8240e0bc2441eb2b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 12:55:49 +0100
Subject: [PATCH 063/351] Check date format
---
webinterface.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/webinterface.py b/webinterface.py
index 90202604e..4e3d1a5e4 100644
--- a/webinterface.py
+++ b/webinterface.py
@@ -5631,6 +5631,8 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
"""
htmlStr = ''
for dateStr, item in newswire.items():
+ if '+00:00' not in dateStr:
+ continue
publishedDate = \
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S+00:00")
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
From 8fd41920f923845bc29ad41612a725225320f684 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 13:04:42 +0100
Subject: [PATCH 064/351] Convert time
---
webinterface.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/webinterface.py b/webinterface.py
index 4e3d1a5e4..935c30fdc 100644
--- a/webinterface.py
+++ b/webinterface.py
@@ -5631,10 +5631,8 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
"""
htmlStr = ''
for dateStr, item in newswire.items():
- if '+00:00' not in dateStr:
- continue
publishedDate = \
- datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S+00:00")
+ datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
dateStrLink = dateStr.replace('T', ' ')
From 4fdecc4f30d2cfe9599c929137461668d1376ba2 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 13:22:52 +0100
Subject: [PATCH 065/351] Handle dates with offset
---
newswire.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/newswire.py b/newswire.py
index d43dacbb8..303a9865a 100644
--- a/newswire.py
+++ b/newswire.py
@@ -339,8 +339,13 @@ def getRSSfromDict(baseDir: str, newswire: {},
None, domainFull,
'Newswire', translate)
for published, fields in newswire.items():
- published = published.replace('+00:00', 'Z').strip()
- published = published.replace(' ', 'T')
+ if '+00:00' in published:
+ published = published.replace('+00:00', 'Z').strip()
+ published = published.replace(' ', 'T')
+ else:
+ publishedWithOffset = \
+ datetime.datetime.strptime(published, "%Y-%m-%d %H:%M:%S%z")
+ published = publishedWithOffset.strftime("%Y-%m-%dT%H:%M:%SZ")
try:
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
except BaseException:
From fda4e294f8ac2bde69455e8b5600feb71419ab3c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 13:28:15 +0100
Subject: [PATCH 066/351] Exception warn when converting date
---
newswire.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/newswire.py b/newswire.py
index 303a9865a..8edb475cb 100644
--- a/newswire.py
+++ b/newswire.py
@@ -348,7 +348,8 @@ def getRSSfromDict(baseDir: str, newswire: {},
published = publishedWithOffset.strftime("%Y-%m-%dT%H:%M:%SZ")
try:
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
- except BaseException:
+ except Exception as e:
+ print('WARN: Unable to convert date ' + published + ' ' + str(e))
continue
rssStr += '- \n'
rssStr += ' ' + fields[0] + '\n'
From 456b4202b129e76684f1d4a0ec8255a8c853de80 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 13:37:32 +0100
Subject: [PATCH 067/351] Convert dates with offset
---
newsdaemon.py | 9 +++++++--
newswire.py | 2 +-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index 91229b2fe..4cb546761 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -478,8 +478,13 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
for dateStr, item in newswireReverse.items():
originalDateStr = dateStr
# convert the date to the format used by ActivityPub
- dateStr = dateStr.replace(' ', 'T')
- dateStr = dateStr.replace('+00:00', 'Z')
+ if '+00:00' in dateStr:
+ dateStr = dateStr.replace(' ', 'T')
+ dateStr = dateStr.replace('+00:00', 'Z')
+ else:
+ dateStrWithOffset = \
+ datetime.datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
+ dateStr = dateStrWithOffset.strftime("%Y-%m-%dT%H:%M:%SZ")
statusNumber, published = getStatusNumber(dateStr)
newPostId = \
diff --git a/newswire.py b/newswire.py
index 8edb475cb..9ed0ab888 100644
--- a/newswire.py
+++ b/newswire.py
@@ -344,7 +344,7 @@ def getRSSfromDict(baseDir: str, newswire: {},
published = published.replace(' ', 'T')
else:
publishedWithOffset = \
- datetime.datetime.strptime(published, "%Y-%m-%d %H:%M:%S%z")
+ datetime.strptime(published, "%Y-%m-%d %H:%M:%S%z")
published = publishedWithOffset.strftime("%Y-%m-%dT%H:%M:%SZ")
try:
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
From ee767503058d93ab9dcaca1511593ffa33b4a29c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 13:49:12 +0100
Subject: [PATCH 068/351] Set content without parsing
---
newsdaemon.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/newsdaemon.py b/newsdaemon.py
index 4cb546761..f1b69e787 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -578,6 +578,8 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
blog['object']['url'] = \
httpPrefix + '://' + domain + '/@news/' + statusNumber
blog['object']['published'] = dateStr
+ blog['object']['content'] = rssDescription
+ blog['object']['contentMap']['en'] = rssDescription
domainFull = domain
if port:
From 77fd759adfd6d7f3d5b4f43dddeeaeea728342fc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 14:07:02 +0100
Subject: [PATCH 069/351] Remove escaped html from feeds
---
newsdaemon.py | 20 +++++++-------------
1 file changed, 7 insertions(+), 13 deletions(-)
diff --git a/newsdaemon.py b/newsdaemon.py
index f1b69e787..d682b55b3 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -15,6 +15,7 @@ __status__ = "Production"
import os
import time
import datetime
+import html
from shutil import rmtree
from subprocess import Popen
from collections import OrderedDict
@@ -65,20 +66,10 @@ def saveArrivedTime(baseDir: str, postFilename: str, arrived: str) -> None:
def removeControlCharacters(content: str) -> str:
- """TODO this is hacky and a better solution is needed
- the unicode is messing up somehow
+ """Remove escaped html
"""
- lookups = {
- "8211": "-",
- "8230": "...",
- "8216": "'",
- "8217": "'",
- "8220": '"',
- "8221": '"'
- }
- for code, ch in lookups.items():
- content = content.replace('&' + code + ';', ch)
- content = content.replace('' + code + ';', ch)
+ if '&' in content:
+ return html.unescape(content)
return content
@@ -513,6 +504,8 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
if rssDescription.startswith('', '')
+ if '&' in rssDescription:
+ rssDescription = html.unescape(rssDescription)
rssDescription = '
' + rssDescription + '
'
mirrored = item[7]
@@ -578,6 +571,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
blog['object']['url'] = \
httpPrefix + '://' + domain + '/@news/' + statusNumber
blog['object']['published'] = dateStr
+
blog['object']['content'] = rssDescription
blog['object']['contentMap']['en'] = rssDescription
From 89a9b8d7b36dd8e4c3377724f22984aceb4575ba Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 20 Oct 2020 18:37:15 +0100
Subject: [PATCH 070/351] From operator in hashtag rules
---
hashtagrules.txt | 6 +++++-
newsdaemon.py | 33 +++++++++++++++++++++---------
tests.py | 52 +++++++++++++++++++++++++++++++-----------------
3 files changed, 63 insertions(+), 28 deletions(-)
diff --git a/hashtagrules.txt b/hashtagrules.txt
index 8e80a15cf..4349a2d02 100644
--- a/hashtagrules.txt
+++ b/hashtagrules.txt
@@ -15,7 +15,7 @@ Logical Operators
The following operators are available:
- not, and, or, xor, contains
+ not, and, or, xor, from, contains
Examples
--------
@@ -39,3 +39,7 @@ You can also remove hashtags.
if #garden or #lawn then remove #gardening
Which will remove #gardening if it exists as a hashtag within the news post.
+
+You can add tags based upon the RSS link, such as:
+
+ if from "mycatsite.com" then add #cats
diff --git a/newsdaemon.py b/newsdaemon.py
index d682b55b3..aed9ff20b 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -74,7 +74,7 @@ def removeControlCharacters(content: str) -> str:
def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
- content: str) -> bool:
+ content: str, url: str) -> bool:
"""Returns whether the tree for a hashtag rule evaluates to true or false
"""
if not tree:
@@ -86,7 +86,7 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
return tree[1] not in hashtags
elif isinstance(tree[1], list):
return not hashtagRuleResolve(tree[1], hashtags, moderated,
- content)
+ content, url)
elif tree[0] == 'contains':
if len(tree) == 2:
if isinstance(tree[1], str):
@@ -101,6 +101,20 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
matchStr = matchStr[1:]
matchStr = matchStr[:len(matchStr) - 1]
return matchStr.lower() in content
+ elif tree[0] == 'from':
+ if len(tree) == 2:
+ if isinstance(tree[1], str):
+ matchStr = tree[1]
+ if matchStr.startswith('"') and matchStr.endswith('"'):
+ matchStr = matchStr[1:]
+ matchStr = matchStr[:len(matchStr) - 1]
+ return matchStr.lower() in url
+ elif isinstance(tree[1], list):
+ matchStr = tree[1][0]
+ if matchStr.startswith('"') and matchStr.endswith('"'):
+ matchStr = matchStr[1:]
+ matchStr = matchStr[:len(matchStr) - 1]
+ return matchStr.lower() in url
elif tree[0] == 'and':
if len(tree) >= 3:
for argIndex in range(1, len(tree)):
@@ -110,7 +124,7 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
elif isinstance(tree[argIndex], list):
argValue = hashtagRuleResolve(tree[argIndex],
hashtags, moderated,
- content)
+ content, url)
if not argValue:
return False
return True
@@ -123,7 +137,7 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
elif isinstance(tree[argIndex], list):
argValue = hashtagRuleResolve(tree[argIndex],
hashtags, moderated,
- content)
+ content, url)
if argValue:
return True
return False
@@ -137,7 +151,7 @@ def hashtagRuleResolve(tree: [], hashtags: [], moderated: bool,
elif isinstance(tree[argIndex], list):
argValue = hashtagRuleResolve(tree[argIndex],
hashtags, moderated,
- content)
+ content, url)
if argValue:
trueCtr += 1
if trueCtr == 1:
@@ -219,7 +233,7 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
cachedWebfingers: {},
federationList: [],
sendThreads: [], postLog: [],
- moderated: bool) -> bool:
+ moderated: bool, url: str) -> bool:
"""Applies hashtag rules to a news post.
Returns true if the post should be saved to the news timeline
of this instance
@@ -245,7 +259,7 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
content = content.lower()
# actionOccurred = False
- operators = ('not', 'and', 'or', 'xor', 'contains')
+ operators = ('not', 'and', 'or', 'xor', 'from', 'contains')
for ruleStr in rules:
if not ruleStr:
continue
@@ -258,7 +272,7 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
tagsInConditions = []
tree = hashtagRuleTree(operators, conditionsStr,
tagsInConditions, moderated)
- if not hashtagRuleResolve(tree, hashtags, moderated, content):
+ if not hashtagRuleResolve(tree, hashtags, moderated, content, url):
continue
# the condition matches, so do something
actionStr = ruleStr.split(' then ')[1].strip()
@@ -607,7 +621,8 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
httpPrefix, domain, port,
personCache, cachedWebfingers,
federationList,
- sendThreads, postLog, moderated)
+ sendThreads, postLog,
+ moderated, url)
# save the post and update the index
if savePost:
diff --git a/tests.py b/tests.py
index 0bc9ea0dc..2e309c84b 100644
--- a/tests.py
+++ b/tests.py
@@ -2177,8 +2177,9 @@ def testRemoveHtmlTag():
def testHashtagRuleTree():
print('testHashtagRuleTree')
- operators = ('not', 'and', 'or', 'xor', 'contains')
+ operators = ('not', 'and', 'or', 'xor', 'from', 'contains')
+ url = 'testsite.com'
moderated = True
conditionsStr = \
'contains "Cat" or contains "Corvid" or ' + \
@@ -2200,9 +2201,24 @@ def testHashtagRuleTree():
assert str(tree) == str(['or', ['#foo'], ['#bar']])
assert str(tagsInConditions) == str(['#foo', '#bar'])
hashtags = ['#foo']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#carrot', '#stick']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
+
+ content = 'This is a test'
+ url = 'https://testsite.com/something'
+ moderated = True
+ conditionsStr = '#foo and from "testsite.com"'
+ tagsInConditions = []
+ tree = hashtagRuleTree(operators, conditionsStr,
+ tagsInConditions, moderated)
+ assert str(tree) == str(['and', ['#foo'], ['from', ['"testsite.com"']]])
+ assert str(tagsInConditions) == str(['#foo'])
+ hashtags = ['#foo']
+ assert hashtagRuleResolve(tree, hashtags, moderated, content,
+ 'testsite.com')
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content,
+ 'othersite.net')
content = 'This is a test'
moderated = True
@@ -2215,9 +2231,9 @@ def testHashtagRuleTree():
['or', ['#foo'], ['#bar']]])
assert str(tagsInConditions) == str(['#foo', '#bar'])
hashtags = ['#foo']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#carrot', '#stick']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
moderated = False
conditionsStr = 'not moderated and #foo or #bar'
@@ -2228,9 +2244,9 @@ def testHashtagRuleTree():
str(['not', ['and', ['moderated'], ['or', ['#foo'], ['#bar']]]])
assert str(tagsInConditions) == str(['#foo', '#bar'])
hashtags = ['#foo']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#carrot', '#stick']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
moderated = True
conditionsStr = 'moderated and #foo or #bar'
@@ -2241,9 +2257,9 @@ def testHashtagRuleTree():
str(['and', ['moderated'], ['or', ['#foo'], ['#bar']]])
assert str(tagsInConditions) == str(['#foo', '#bar'])
hashtags = ['#foo']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#carrot', '#stick']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
conditionsStr = 'x'
tagsInConditions = []
@@ -2252,7 +2268,7 @@ def testHashtagRuleTree():
assert tree is None
assert tagsInConditions == []
hashtags = ['#foo']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
conditionsStr = '#x'
tagsInConditions = []
@@ -2261,9 +2277,9 @@ def testHashtagRuleTree():
assert str(tree) == str(['#x'])
assert str(tagsInConditions) == str(['#x'])
hashtags = ['#x']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#y', '#z']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
conditionsStr = 'not #b'
tagsInConditions = []
@@ -2272,9 +2288,9 @@ def testHashtagRuleTree():
assert str(tree) == str(['not', ['#b']])
assert str(tagsInConditions) == str(['#b'])
hashtags = ['#y', '#z']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#a', '#b', '#c']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
conditionsStr = '#foo or #bar and #a'
tagsInConditions = []
@@ -2283,13 +2299,13 @@ def testHashtagRuleTree():
assert str(tree) == str(['and', ['or', ['#foo'], ['#bar']], ['#a']])
assert str(tagsInConditions) == str(['#foo', '#bar', '#a'])
hashtags = ['#foo', '#bar', '#a']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#bar', '#a']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#foo', '#a']
- assert hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
hashtags = ['#x', '#a']
- assert not hashtagRuleResolve(tree, hashtags, moderated, content)
+ assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
def runAllTests():
From 32758ffe62d308eb93cc58562e23fd99fa3b52a8 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 21 Oct 2020 11:39:09 +0100
Subject: [PATCH 071/351] Option to archive news posts
---
daemon.py | 6 +++++-
epicyon.py | 8 +++++++-
newsdaemon.py | 11 +++++++++++
posts.py | 16 +++++++++++++---
tests.py | 9 ++++-----
utils.py | 19 +++++--------------
6 files changed, 45 insertions(+), 24 deletions(-)
diff --git a/daemon.py b/daemon.py
index dbb8062cb..2008e0006 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11981,7 +11981,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
tokensLookup[token] = nickname
-def runDaemon(maxMirroredArticles: int,
+def runDaemon(maxNewsPosts: int,
+ maxMirroredArticles: int,
maxNewswireFeedSizeKb: int,
maxNewswirePostsPerSource: int,
showPublishedDateOnly: bool,
@@ -12120,6 +12121,9 @@ def runDaemon(maxMirroredArticles: int,
# maximum number of news articles to mirror
httpd.maxMirroredArticles = maxMirroredArticles
+ # maximum number of posts in the news timeline/outbox
+ httpd.maxNewsPosts = maxNewsPosts
+
if registration == 'open':
httpd.registration = True
else:
diff --git a/epicyon.py b/epicyon.py
index 427a53032..0b980f4cb 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -125,6 +125,11 @@ parser.add_argument('--maxMirroredArticles',
default=100,
help='Maximum number of news articles to mirror.' +
' Set to zero for indefinite mirroring.')
+parser.add_argument('--maxNewsPosts',
+ dest='maxNewsPosts', type=int,
+ default=0,
+ help='Maximum number of news timeline posts to keep. ' +
+ 'Zero for no expiry.')
parser.add_argument('--postcache', dest='maxRecentPosts', type=int,
default=512,
help='The maximum number of recent posts to store in RAM')
@@ -1968,7 +1973,8 @@ if setTheme(baseDir, themeName, domain):
print('Theme set to ' + themeName)
if __name__ == "__main__":
- runDaemon(args.maxMirroredArticles,
+ runDaemon(args.maxNewsPosts,
+ args.maxMirroredArticles,
args.maxNewswireFeedSizeKb,
args.maxNewswirePostsPerSource,
args.dateonly,
diff --git a/newsdaemon.py b/newsdaemon.py
index aed9ff20b..3de4dce67 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -22,6 +22,7 @@ from collections import OrderedDict
from newswire import getDictFromNewswire
# from posts import sendSignedJson
from posts import createNewsPost
+from posts import archivePostsForPerson
from content import removeHtmlTag
from content import dangerousMarkup
from content import validHashTag
@@ -714,6 +715,16 @@ def runNewswireDaemon(baseDir: str, httpd,
httpd.maxMirroredArticles)
print('Newswire feed converted to ActivityPub')
+ if httpd.maxNewsPosts > 0:
+ archiveDir = baseDir + '/archive'
+ archiveSubdir = \
+ archiveDir + '/accounts/news@' + domain + '/outbox'
+ archivePostsForPerson(httpPrefix, 'news',
+ domain, baseDir, 'outbox',
+ archiveSubdir,
+ httpd.recentPostsCache,
+ httpd.maxNewsPosts)
+
# wait a while before the next feeds update
time.sleep(1200)
diff --git a/posts.py b/posts.py
index cd85388d5..e033957bb 100644
--- a/posts.py
+++ b/posts.py
@@ -3217,11 +3217,21 @@ def archivePostsForPerson(httpPrefix: str, nickname: str, domain: str,
if not os.path.isfile(filePath):
continue
if archiveDir:
- repliesPath = filePath.replace('.json', '.replies')
archivePath = os.path.join(archiveDir, postFilename)
os.rename(filePath, archivePath)
- if os.path.isfile(repliesPath):
- os.rename(repliesPath, archivePath)
+
+ extensions = ('replies', 'votes', 'arrived', 'muted')
+ for ext in extensions:
+ extPath = filePath.replace('.json', '.' + ext)
+ if os.path.isfile(extPath):
+ os.rename(extPath,
+ archivePath.replace('.json', '.' + ext))
+ else:
+ extPath = filePath.replace('.json',
+ '.json.' + ext)
+ if os.path.isfile(extPath):
+ os.rename(extPath,
+ archivePath.replace('.json', '.json.' + ext))
else:
deletePost(baseDir, httpPrefix, nickname, domain,
filePath, False, recentPostsCache)
diff --git a/tests.py b/tests.py
index 2e309c84b..ed308150a 100644
--- a/tests.py
+++ b/tests.py
@@ -290,7 +290,7 @@ def createServerAlice(path: str, domain: str, port: int,
onionDomain = None
i2pDomain = None
print('Server running: Alice')
- runDaemon(100, 1024, 5, False, 0, False, 1, False, False, False,
+ runDaemon(0, 100, 1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__,
"instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port,
@@ -353,7 +353,7 @@ def createServerBob(path: str, domain: str, port: int,
onionDomain = None
i2pDomain = None
print('Server running: Bob')
- runDaemon(100, 1024, 5, False, 0, False, 1, False, False, False,
+ runDaemon(0, 100, 1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__,
"instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port,
@@ -390,7 +390,7 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
onionDomain = None
i2pDomain = None
print('Server running: Eve')
- runDaemon(100, 1024, 5, False, 0, False, 1, False, False, False,
+ runDaemon(0, 100, 1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__,
"instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port,
@@ -2215,8 +2215,7 @@ def testHashtagRuleTree():
assert str(tree) == str(['and', ['#foo'], ['from', ['"testsite.com"']]])
assert str(tagsInConditions) == str(['#foo'])
hashtags = ['#foo']
- assert hashtagRuleResolve(tree, hashtags, moderated, content,
- 'testsite.com')
+ assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
assert not hashtagRuleResolve(tree, hashtags, moderated, content,
'othersite.net')
diff --git a/utils.py b/utils.py
index a8a60a54c..77e3162ba 100644
--- a/utils.py
+++ b/utils.py
@@ -762,20 +762,11 @@ def deletePost(baseDir: str, httpPrefix: str,
# remove any attachment
removeAttachment(baseDir, httpPrefix, domain, postJsonObject)
- # remove any mute file
- muteFilename = postFilename + '.muted'
- if os.path.isfile(muteFilename):
- os.remove(muteFilename)
-
- # remove any votes file
- votesFilename = postFilename + '.votes'
- if os.path.isfile(votesFilename):
- os.remove(votesFilename)
-
- # remove any arrived file
- arrivedFilename = postFilename + '.arrived'
- if os.path.isfile(arrivedFilename):
- os.remove(arrivedFilename)
+ extensions = ('votes', 'arrived', 'muted')
+ for ext in extensions:
+ extFilename = postFilename + '.' + ext
+ if os.path.isfile(extFilename):
+ os.remove(extFilename)
# remove cached html version of the post
cachedPostFilename = \
From 3013209cfc462216eb7723376cea354e18017356 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 21 Oct 2020 12:08:57 +0100
Subject: [PATCH 072/351] Maximum news posts in config file
---
epicyon.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/epicyon.py b/epicyon.py
index 0b980f4cb..474ff6a46 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -1960,6 +1960,11 @@ maxMirroredArticles = \
if maxMirroredArticles is not None:
args.maxMirroredArticles = int(maxMirroredArticles)
+maxNewsPosts = \
+ getConfigParam(baseDir, 'maxNewsPosts')
+if maxNewsPosts is not None:
+ args.maxNewsPosts = int(maxNewsPosts)
+
YTDomain = getConfigParam(baseDir, 'youtubedomain')
if YTDomain:
if '://' in YTDomain:
From 30a13c7f72a97b6e9b7ba8e6b59585fe9edf1760 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 22 Oct 2020 17:18:56 +0100
Subject: [PATCH 073/351] Rename indymedia theme
---
...er_indymedia.png => banner_indymediaclassic.png} | Bin
img/icons/{indymedia => indymediaclassic}/add.png | Bin
.../{indymedia => indymediaclassic}/avatar_news.png | Bin
.../{indymedia => indymediaclassic}/bookmark.png | Bin
.../bookmark_inactive.png | Bin
.../{indymedia => indymediaclassic}/calendar.png | Bin
.../calendar_notify.png | Bin
.../{indymedia => indymediaclassic}/delete.png | Bin
img/icons/{indymedia => indymediaclassic}/dm.png | Bin
.../{indymedia => indymediaclassic}/download.png | Bin
img/icons/{indymedia => indymediaclassic}/edit.png | Bin
.../{indymedia => indymediaclassic}/edit_notify.png | Bin
img/icons/{indymedia => indymediaclassic}/like.png | Bin
.../like_inactive.png | Bin
img/icons/{indymedia => indymediaclassic}/links.png | Bin
.../{indymedia => indymediaclassic}/logorss.png | Bin
img/icons/{indymedia => indymediaclassic}/mute.png | Bin
img/icons/{indymedia => indymediaclassic}/new.png | Bin
.../{indymedia => indymediaclassic}/newpost.png | Bin
.../{indymedia => indymediaclassic}/newswire.png | Bin
.../{indymedia => indymediaclassic}/pagedown.png | Bin
.../{indymedia => indymediaclassic}/pageup.png | Bin
.../{indymedia => indymediaclassic}/person.png | Bin
img/icons/{indymedia => indymediaclassic}/prev.png | Bin
.../{indymedia => indymediaclassic}/qrcode.png | Bin
.../{indymedia => indymediaclassic}/repeat.png | Bin
.../repeat_inactive.png | Bin
img/icons/{indymedia => indymediaclassic}/reply.png | Bin
img/icons/{indymedia => indymediaclassic}/rss3.png | Bin
.../{indymedia => indymediaclassic}/scope_blog.png | Bin
.../{indymedia => indymediaclassic}/scope_dm.png | Bin
.../{indymedia => indymediaclassic}/scope_event.png | Bin
.../scope_followers.png | Bin
.../scope_public.png | Bin
.../scope_question.png | Bin
.../scope_reminder.png | Bin
.../scope_report.png | Bin
.../{indymedia => indymediaclassic}/scope_share.png | Bin
.../scope_unlisted.png | Bin
.../{indymedia => indymediaclassic}/search.png | Bin
.../{indymedia => indymediaclassic}/showhide.png | Bin
.../{indymedia => indymediaclassic}/unmute.png | Bin
img/icons/{indymedia => indymediaclassic}/vote.png | Bin
...age_indymedia.png => image_indymediaclassic.png} | Bin
...edia.png => left_col_image_indymediaclassic.png} | Bin
...ia.jpg => login_background_indymediaclassic.jpg} | Bin
....jpg => options_background_indymediaclassic.jpg} | Bin
...dia.png => right_col_image_indymediaclassic.png} | Bin
...ndymedia.jpg => screenshot_indymediaclassic.jpg} | Bin
...media.png => search_banner_indymediaclassic.png} | Bin
theme.py | 7 ++++---
51 files changed, 4 insertions(+), 3 deletions(-)
rename img/{banner_indymedia.png => banner_indymediaclassic.png} (100%)
rename img/icons/{indymedia => indymediaclassic}/add.png (100%)
rename img/icons/{indymedia => indymediaclassic}/avatar_news.png (100%)
rename img/icons/{indymedia => indymediaclassic}/bookmark.png (100%)
rename img/icons/{indymedia => indymediaclassic}/bookmark_inactive.png (100%)
rename img/icons/{indymedia => indymediaclassic}/calendar.png (100%)
rename img/icons/{indymedia => indymediaclassic}/calendar_notify.png (100%)
rename img/icons/{indymedia => indymediaclassic}/delete.png (100%)
rename img/icons/{indymedia => indymediaclassic}/dm.png (100%)
rename img/icons/{indymedia => indymediaclassic}/download.png (100%)
rename img/icons/{indymedia => indymediaclassic}/edit.png (100%)
rename img/icons/{indymedia => indymediaclassic}/edit_notify.png (100%)
rename img/icons/{indymedia => indymediaclassic}/like.png (100%)
rename img/icons/{indymedia => indymediaclassic}/like_inactive.png (100%)
rename img/icons/{indymedia => indymediaclassic}/links.png (100%)
rename img/icons/{indymedia => indymediaclassic}/logorss.png (100%)
rename img/icons/{indymedia => indymediaclassic}/mute.png (100%)
rename img/icons/{indymedia => indymediaclassic}/new.png (100%)
rename img/icons/{indymedia => indymediaclassic}/newpost.png (100%)
rename img/icons/{indymedia => indymediaclassic}/newswire.png (100%)
rename img/icons/{indymedia => indymediaclassic}/pagedown.png (100%)
rename img/icons/{indymedia => indymediaclassic}/pageup.png (100%)
rename img/icons/{indymedia => indymediaclassic}/person.png (100%)
rename img/icons/{indymedia => indymediaclassic}/prev.png (100%)
rename img/icons/{indymedia => indymediaclassic}/qrcode.png (100%)
rename img/icons/{indymedia => indymediaclassic}/repeat.png (100%)
rename img/icons/{indymedia => indymediaclassic}/repeat_inactive.png (100%)
rename img/icons/{indymedia => indymediaclassic}/reply.png (100%)
rename img/icons/{indymedia => indymediaclassic}/rss3.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_blog.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_dm.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_event.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_followers.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_public.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_question.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_reminder.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_report.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_share.png (100%)
rename img/icons/{indymedia => indymediaclassic}/scope_unlisted.png (100%)
rename img/icons/{indymedia => indymediaclassic}/search.png (100%)
rename img/icons/{indymedia => indymediaclassic}/showhide.png (100%)
rename img/icons/{indymedia => indymediaclassic}/unmute.png (100%)
rename img/icons/{indymedia => indymediaclassic}/vote.png (100%)
rename img/{image_indymedia.png => image_indymediaclassic.png} (100%)
rename img/{left_col_image_indymedia.png => left_col_image_indymediaclassic.png} (100%)
rename img/{login_background_indymedia.jpg => login_background_indymediaclassic.jpg} (100%)
rename img/{options_background_indymedia.jpg => options_background_indymediaclassic.jpg} (100%)
rename img/{right_col_image_indymedia.png => right_col_image_indymediaclassic.png} (100%)
rename img/{screenshot_indymedia.jpg => screenshot_indymediaclassic.jpg} (100%)
rename img/{search_banner_indymedia.png => search_banner_indymediaclassic.png} (100%)
diff --git a/img/banner_indymedia.png b/img/banner_indymediaclassic.png
similarity index 100%
rename from img/banner_indymedia.png
rename to img/banner_indymediaclassic.png
diff --git a/img/icons/indymedia/add.png b/img/icons/indymediaclassic/add.png
similarity index 100%
rename from img/icons/indymedia/add.png
rename to img/icons/indymediaclassic/add.png
diff --git a/img/icons/indymedia/avatar_news.png b/img/icons/indymediaclassic/avatar_news.png
similarity index 100%
rename from img/icons/indymedia/avatar_news.png
rename to img/icons/indymediaclassic/avatar_news.png
diff --git a/img/icons/indymedia/bookmark.png b/img/icons/indymediaclassic/bookmark.png
similarity index 100%
rename from img/icons/indymedia/bookmark.png
rename to img/icons/indymediaclassic/bookmark.png
diff --git a/img/icons/indymedia/bookmark_inactive.png b/img/icons/indymediaclassic/bookmark_inactive.png
similarity index 100%
rename from img/icons/indymedia/bookmark_inactive.png
rename to img/icons/indymediaclassic/bookmark_inactive.png
diff --git a/img/icons/indymedia/calendar.png b/img/icons/indymediaclassic/calendar.png
similarity index 100%
rename from img/icons/indymedia/calendar.png
rename to img/icons/indymediaclassic/calendar.png
diff --git a/img/icons/indymedia/calendar_notify.png b/img/icons/indymediaclassic/calendar_notify.png
similarity index 100%
rename from img/icons/indymedia/calendar_notify.png
rename to img/icons/indymediaclassic/calendar_notify.png
diff --git a/img/icons/indymedia/delete.png b/img/icons/indymediaclassic/delete.png
similarity index 100%
rename from img/icons/indymedia/delete.png
rename to img/icons/indymediaclassic/delete.png
diff --git a/img/icons/indymedia/dm.png b/img/icons/indymediaclassic/dm.png
similarity index 100%
rename from img/icons/indymedia/dm.png
rename to img/icons/indymediaclassic/dm.png
diff --git a/img/icons/indymedia/download.png b/img/icons/indymediaclassic/download.png
similarity index 100%
rename from img/icons/indymedia/download.png
rename to img/icons/indymediaclassic/download.png
diff --git a/img/icons/indymedia/edit.png b/img/icons/indymediaclassic/edit.png
similarity index 100%
rename from img/icons/indymedia/edit.png
rename to img/icons/indymediaclassic/edit.png
diff --git a/img/icons/indymedia/edit_notify.png b/img/icons/indymediaclassic/edit_notify.png
similarity index 100%
rename from img/icons/indymedia/edit_notify.png
rename to img/icons/indymediaclassic/edit_notify.png
diff --git a/img/icons/indymedia/like.png b/img/icons/indymediaclassic/like.png
similarity index 100%
rename from img/icons/indymedia/like.png
rename to img/icons/indymediaclassic/like.png
diff --git a/img/icons/indymedia/like_inactive.png b/img/icons/indymediaclassic/like_inactive.png
similarity index 100%
rename from img/icons/indymedia/like_inactive.png
rename to img/icons/indymediaclassic/like_inactive.png
diff --git a/img/icons/indymedia/links.png b/img/icons/indymediaclassic/links.png
similarity index 100%
rename from img/icons/indymedia/links.png
rename to img/icons/indymediaclassic/links.png
diff --git a/img/icons/indymedia/logorss.png b/img/icons/indymediaclassic/logorss.png
similarity index 100%
rename from img/icons/indymedia/logorss.png
rename to img/icons/indymediaclassic/logorss.png
diff --git a/img/icons/indymedia/mute.png b/img/icons/indymediaclassic/mute.png
similarity index 100%
rename from img/icons/indymedia/mute.png
rename to img/icons/indymediaclassic/mute.png
diff --git a/img/icons/indymedia/new.png b/img/icons/indymediaclassic/new.png
similarity index 100%
rename from img/icons/indymedia/new.png
rename to img/icons/indymediaclassic/new.png
diff --git a/img/icons/indymedia/newpost.png b/img/icons/indymediaclassic/newpost.png
similarity index 100%
rename from img/icons/indymedia/newpost.png
rename to img/icons/indymediaclassic/newpost.png
diff --git a/img/icons/indymedia/newswire.png b/img/icons/indymediaclassic/newswire.png
similarity index 100%
rename from img/icons/indymedia/newswire.png
rename to img/icons/indymediaclassic/newswire.png
diff --git a/img/icons/indymedia/pagedown.png b/img/icons/indymediaclassic/pagedown.png
similarity index 100%
rename from img/icons/indymedia/pagedown.png
rename to img/icons/indymediaclassic/pagedown.png
diff --git a/img/icons/indymedia/pageup.png b/img/icons/indymediaclassic/pageup.png
similarity index 100%
rename from img/icons/indymedia/pageup.png
rename to img/icons/indymediaclassic/pageup.png
diff --git a/img/icons/indymedia/person.png b/img/icons/indymediaclassic/person.png
similarity index 100%
rename from img/icons/indymedia/person.png
rename to img/icons/indymediaclassic/person.png
diff --git a/img/icons/indymedia/prev.png b/img/icons/indymediaclassic/prev.png
similarity index 100%
rename from img/icons/indymedia/prev.png
rename to img/icons/indymediaclassic/prev.png
diff --git a/img/icons/indymedia/qrcode.png b/img/icons/indymediaclassic/qrcode.png
similarity index 100%
rename from img/icons/indymedia/qrcode.png
rename to img/icons/indymediaclassic/qrcode.png
diff --git a/img/icons/indymedia/repeat.png b/img/icons/indymediaclassic/repeat.png
similarity index 100%
rename from img/icons/indymedia/repeat.png
rename to img/icons/indymediaclassic/repeat.png
diff --git a/img/icons/indymedia/repeat_inactive.png b/img/icons/indymediaclassic/repeat_inactive.png
similarity index 100%
rename from img/icons/indymedia/repeat_inactive.png
rename to img/icons/indymediaclassic/repeat_inactive.png
diff --git a/img/icons/indymedia/reply.png b/img/icons/indymediaclassic/reply.png
similarity index 100%
rename from img/icons/indymedia/reply.png
rename to img/icons/indymediaclassic/reply.png
diff --git a/img/icons/indymedia/rss3.png b/img/icons/indymediaclassic/rss3.png
similarity index 100%
rename from img/icons/indymedia/rss3.png
rename to img/icons/indymediaclassic/rss3.png
diff --git a/img/icons/indymedia/scope_blog.png b/img/icons/indymediaclassic/scope_blog.png
similarity index 100%
rename from img/icons/indymedia/scope_blog.png
rename to img/icons/indymediaclassic/scope_blog.png
diff --git a/img/icons/indymedia/scope_dm.png b/img/icons/indymediaclassic/scope_dm.png
similarity index 100%
rename from img/icons/indymedia/scope_dm.png
rename to img/icons/indymediaclassic/scope_dm.png
diff --git a/img/icons/indymedia/scope_event.png b/img/icons/indymediaclassic/scope_event.png
similarity index 100%
rename from img/icons/indymedia/scope_event.png
rename to img/icons/indymediaclassic/scope_event.png
diff --git a/img/icons/indymedia/scope_followers.png b/img/icons/indymediaclassic/scope_followers.png
similarity index 100%
rename from img/icons/indymedia/scope_followers.png
rename to img/icons/indymediaclassic/scope_followers.png
diff --git a/img/icons/indymedia/scope_public.png b/img/icons/indymediaclassic/scope_public.png
similarity index 100%
rename from img/icons/indymedia/scope_public.png
rename to img/icons/indymediaclassic/scope_public.png
diff --git a/img/icons/indymedia/scope_question.png b/img/icons/indymediaclassic/scope_question.png
similarity index 100%
rename from img/icons/indymedia/scope_question.png
rename to img/icons/indymediaclassic/scope_question.png
diff --git a/img/icons/indymedia/scope_reminder.png b/img/icons/indymediaclassic/scope_reminder.png
similarity index 100%
rename from img/icons/indymedia/scope_reminder.png
rename to img/icons/indymediaclassic/scope_reminder.png
diff --git a/img/icons/indymedia/scope_report.png b/img/icons/indymediaclassic/scope_report.png
similarity index 100%
rename from img/icons/indymedia/scope_report.png
rename to img/icons/indymediaclassic/scope_report.png
diff --git a/img/icons/indymedia/scope_share.png b/img/icons/indymediaclassic/scope_share.png
similarity index 100%
rename from img/icons/indymedia/scope_share.png
rename to img/icons/indymediaclassic/scope_share.png
diff --git a/img/icons/indymedia/scope_unlisted.png b/img/icons/indymediaclassic/scope_unlisted.png
similarity index 100%
rename from img/icons/indymedia/scope_unlisted.png
rename to img/icons/indymediaclassic/scope_unlisted.png
diff --git a/img/icons/indymedia/search.png b/img/icons/indymediaclassic/search.png
similarity index 100%
rename from img/icons/indymedia/search.png
rename to img/icons/indymediaclassic/search.png
diff --git a/img/icons/indymedia/showhide.png b/img/icons/indymediaclassic/showhide.png
similarity index 100%
rename from img/icons/indymedia/showhide.png
rename to img/icons/indymediaclassic/showhide.png
diff --git a/img/icons/indymedia/unmute.png b/img/icons/indymediaclassic/unmute.png
similarity index 100%
rename from img/icons/indymedia/unmute.png
rename to img/icons/indymediaclassic/unmute.png
diff --git a/img/icons/indymedia/vote.png b/img/icons/indymediaclassic/vote.png
similarity index 100%
rename from img/icons/indymedia/vote.png
rename to img/icons/indymediaclassic/vote.png
diff --git a/img/image_indymedia.png b/img/image_indymediaclassic.png
similarity index 100%
rename from img/image_indymedia.png
rename to img/image_indymediaclassic.png
diff --git a/img/left_col_image_indymedia.png b/img/left_col_image_indymediaclassic.png
similarity index 100%
rename from img/left_col_image_indymedia.png
rename to img/left_col_image_indymediaclassic.png
diff --git a/img/login_background_indymedia.jpg b/img/login_background_indymediaclassic.jpg
similarity index 100%
rename from img/login_background_indymedia.jpg
rename to img/login_background_indymediaclassic.jpg
diff --git a/img/options_background_indymedia.jpg b/img/options_background_indymediaclassic.jpg
similarity index 100%
rename from img/options_background_indymedia.jpg
rename to img/options_background_indymediaclassic.jpg
diff --git a/img/right_col_image_indymedia.png b/img/right_col_image_indymediaclassic.png
similarity index 100%
rename from img/right_col_image_indymedia.png
rename to img/right_col_image_indymediaclassic.png
diff --git a/img/screenshot_indymedia.jpg b/img/screenshot_indymediaclassic.jpg
similarity index 100%
rename from img/screenshot_indymedia.jpg
rename to img/screenshot_indymediaclassic.jpg
diff --git a/img/search_banner_indymedia.png b/img/search_banner_indymediaclassic.png
similarity index 100%
rename from img/search_banner_indymedia.png
rename to img/search_banner_indymediaclassic.png
diff --git a/theme.py b/theme.py
index 750adb617..4493da989 100644
--- a/theme.py
+++ b/theme.py
@@ -25,8 +25,9 @@ def getThemesList() -> []:
and to lookup function names
"""
return ('Default', 'Blue', 'Hacker', 'Henge', 'HighVis',
- 'Indymedia', 'LCD', 'Light', 'Night', 'Purple',
- 'Solidaric', 'Starlight', 'Zen')
+ 'IndymediaClassic',
+ 'LCD', 'Light', 'Night', 'Purple', 'Solidaric',
+ 'Starlight', 'Zen')
def setThemeInConfig(baseDir: str, name: str) -> bool:
@@ -243,7 +244,7 @@ def setThemeDefault(baseDir: str):
setThemeFromDict(baseDir, name, themeParams, bgParams)
-def setThemeIndymedia(baseDir: str):
+def setThemeIndymediaClassic(baseDir: str):
name = 'indymedia'
removeTheme(baseDir)
setThemeInConfig(baseDir, name)
From 8a59810a870d375053278d252a91d6de0a6068fc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 22 Oct 2020 17:27:28 +0100
Subject: [PATCH 074/351] Rename indymedia theme to classic
---
translations/ar.json | 1 +
translations/ca.json | 1 +
translations/cy.json | 1 +
translations/de.json | 1 +
translations/en.json | 1 +
translations/es.json | 1 +
translations/fr.json | 1 +
translations/ga.json | 1 +
translations/hi.json | 1 +
translations/it.json | 1 +
translations/ja.json | 1 +
translations/oc.json | 1 +
translations/pt.json | 1 +
translations/ru.json | 1 +
translations/zh.json | 1 +
15 files changed, 15 insertions(+)
diff --git a/translations/ar.json b/translations/ar.json
index 4b89fe1a1..525334281 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا",
"Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag محظور",
"This is a blogging instance": "هذا مثال على المدونات",
"Edit Links": "تحرير الارتباطات",
diff --git a/translations/ca.json b/translations/ca.json
index d007be902..3055d9fc4 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags autogenerats",
"Autogenerated Content Warnings": "Advertiments de contingut autogenerats",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag bloquejat",
"This is a blogging instance": "Aquesta és una instància de blocs",
"Edit Links": "Edita els enllaços",
diff --git a/translations/cy.json b/translations/cy.json
index 555b01d0a..f0d54d3e0 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags awtogeneiddiedig",
"Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag wedi'i Blocio",
"This is a blogging instance": "Dyma enghraifft blogio",
"Edit Links": "Golygu Dolenni",
diff --git a/translations/de.json b/translations/de.json
index f1c88a27a..370eeb7a7 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Automatisch generierte Hashtags",
"Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "IndymediaClassic",
"Hashtag Blocked": "Hashtag blockiert",
"This is a blogging instance": "Dies ist eine Blogging-Instanz",
"Edit Links": "Links bearbeiten",
diff --git a/translations/en.json b/translations/en.json
index ea90d27f3..8cee6fcd7 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Autogenerated Hashtags",
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "IndymediaClassic",
"Hashtag Blocked": "Hashtag Blocked",
"This is a blogging instance": "This is a blogging instance",
"Edit Links": "Edit Links",
diff --git a/translations/es.json b/translations/es.json
index 453f97bf2..731ba5144 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags autogenerados",
"Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "IndymediaClassic",
"Hashtag Blocked": "Hashtag bloqueada",
"This is a blogging instance": "Esta es una instancia de blogs",
"Edit Links": "Editar enlaces",
diff --git a/translations/fr.json b/translations/fr.json
index a2774c54d..15d45372d 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags générés automatiquement",
"Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "IndymediaClassic",
"Hashtag Blocked": "Hashtag bloqué",
"This is a blogging instance": "Ceci est une instance de blog",
"Edit Links": "Modifier les liens",
diff --git a/translations/ga.json b/translations/ga.json
index 927c5d32a..96b36f75d 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags uathghinte",
"Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "IndymediaClassic",
"Hashtag Blocked": "Hashtag Blocáilte",
"This is a blogging instance": "Seo sampla blagála",
"Edit Links": "Cuir Naisc in eagar",
diff --git a/translations/hi.json b/translations/hi.json
index f3f64de1b..598bcb9b4 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग",
"Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "हैशटैग अवरुद्ध",
"This is a blogging instance": "यह एक ब्लॉगिंग उदाहरण है",
"Edit Links": "लिंक संपादित करें",
diff --git a/translations/it.json b/translations/it.json
index d6d636fe6..3a0ba71dd 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtag generati automaticamente",
"Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag bloccato",
"This is a blogging instance": "Questa è un'istanza di blog",
"Edit Links": "Modifica collegamenti",
diff --git a/translations/ja.json b/translations/ja.json
index a2bd44210..da071d8d4 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "自動生成されたハッシュタグ",
"Autogenerated Content Warnings": "自動生成されたコンテンツの警告",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "ハッシュタグがブロックされました",
"This is a blogging instance": "これはブログのインスタンスです",
"Edit Links": "リンクの編集",
diff --git a/translations/oc.json b/translations/oc.json
index 8f8c08b90..c61041323 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -283,6 +283,7 @@
"Autogenerated Hashtags": "Autogenerated Hashtags",
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag Blocked",
"This is a blogging instance": "This is a blogging instance",
"Edit Links": "Edit Links",
diff --git a/translations/pt.json b/translations/pt.json
index 8bdaec28a..43b001692 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Hashtags autogeradas",
"Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag bloqueada",
"This is a blogging instance": "Esta é uma instância de blog",
"Edit Links": "Editar Links",
diff --git a/translations/ru.json b/translations/ru.json
index e44288f5d..8e06fcf1d 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "Автоматически сгенерированные хештеги",
"Autogenerated Content Warnings": "Автоматические предупреждения о содержании",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Хештег заблокирован",
"This is a blogging instance": "Это экземпляр блога",
"Edit Links": "Редактировать ссылки",
diff --git a/translations/zh.json b/translations/zh.json
index 0d937a752..76a24bfce 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -287,6 +287,7 @@
"Autogenerated Hashtags": "自动生成的标签",
"Autogenerated Content Warnings": "自动生成的内容警告",
"Indymedia": "Indymedia",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "标签被阻止",
"This is a blogging instance": "这是一个博客实例",
"Edit Links": "编辑连结",
From de0871c157d4fad303f2d01404f9504a956328f4 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 22 Oct 2020 17:33:25 +0100
Subject: [PATCH 075/351] Space
---
translations/de.json | 2 +-
translations/en.json | 2 +-
translations/es.json | 2 +-
translations/fr.json | 2 +-
translations/ga.json | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/translations/de.json b/translations/de.json
index 370eeb7a7..82e4bb31b 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -287,7 +287,7 @@
"Autogenerated Hashtags": "Automatisch generierte Hashtags",
"Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten",
"Indymedia": "Indymedia",
- "IndymediaClassic": "IndymediaClassic",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag blockiert",
"This is a blogging instance": "Dies ist eine Blogging-Instanz",
"Edit Links": "Links bearbeiten",
diff --git a/translations/en.json b/translations/en.json
index 8cee6fcd7..58e7c1be4 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -287,7 +287,7 @@
"Autogenerated Hashtags": "Autogenerated Hashtags",
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
"Indymedia": "Indymedia",
- "IndymediaClassic": "IndymediaClassic",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag Blocked",
"This is a blogging instance": "This is a blogging instance",
"Edit Links": "Edit Links",
diff --git a/translations/es.json b/translations/es.json
index 731ba5144..58c3321f7 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -287,7 +287,7 @@
"Autogenerated Hashtags": "Hashtags autogenerados",
"Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente",
"Indymedia": "Indymedia",
- "IndymediaClassic": "IndymediaClassic",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag bloqueada",
"This is a blogging instance": "Esta es una instancia de blogs",
"Edit Links": "Editar enlaces",
diff --git a/translations/fr.json b/translations/fr.json
index 15d45372d..6b5f4ab29 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -287,7 +287,7 @@
"Autogenerated Hashtags": "Hashtags générés automatiquement",
"Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement",
"Indymedia": "Indymedia",
- "IndymediaClassic": "IndymediaClassic",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag bloqué",
"This is a blogging instance": "Ceci est une instance de blog",
"Edit Links": "Modifier les liens",
diff --git a/translations/ga.json b/translations/ga.json
index 96b36f75d..4e13676c6 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -287,7 +287,7 @@
"Autogenerated Hashtags": "Hashtags uathghinte",
"Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte",
"Indymedia": "Indymedia",
- "IndymediaClassic": "IndymediaClassic",
+ "IndymediaClassic": "Indymedia Classic",
"Hashtag Blocked": "Hashtag Blocáilte",
"This is a blogging instance": "Seo sampla blagála",
"Edit Links": "Cuir Naisc in eagar",
From 7e8d17731483d139b155267d87a4b6abe06d3557 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 22 Oct 2020 17:39:32 +0100
Subject: [PATCH 076/351] Theme name
---
theme.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/theme.py b/theme.py
index 4493da989..0ca64d8df 100644
--- a/theme.py
+++ b/theme.py
@@ -245,7 +245,7 @@ def setThemeDefault(baseDir: str):
def setThemeIndymediaClassic(baseDir: str):
- name = 'indymedia'
+ name = 'indymediaclassic'
removeTheme(baseDir)
setThemeInConfig(baseDir, name)
bgParams = {
From 40d5aa1e5902f8f90d0325bc96a2a632f8c19ca3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 22 Oct 2020 19:00:24 +0100
Subject: [PATCH 077/351] Birdsite emoji
---
emoji/default_emoji.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/emoji/default_emoji.json b/emoji/default_emoji.json
index 2f4fc92d1..9524f45df 100644
--- a/emoji/default_emoji.json
+++ b/emoji/default_emoji.json
@@ -450,6 +450,7 @@
"turkey": "1F983",
"turtle": "1F422",
"twitter": "E040",
+ "birdsite": "E040",
"two": "0032",
"umbrellawithraindrops": "2614",
"unamusedface": "1F612",
From b449e66b10b345166fef40e106ff1381b4a7fea5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 23 Oct 2020 10:53:45 +0100
Subject: [PATCH 078/351] Publish icon
---
img/icons/blue/publish.png | Bin 0 -> 992 bytes
img/icons/blue/scope_blog.png | Bin 1036 -> 992 bytes
img/icons/hacker/publish.png | Bin 0 -> 5518 bytes
img/icons/hacker/scope_blog.png | Bin 1033 -> 5518 bytes
img/icons/henge/publish.png | Bin 0 -> 5520 bytes
img/icons/henge/scope_blog.png | Bin 1454 -> 5520 bytes
img/icons/indymediaclassic/publish.png | Bin 0 -> 1539 bytes
img/icons/lcd/publish.png | Bin 0 -> 5518 bytes
img/icons/lcd/scope_blog.png | Bin 1456 -> 5518 bytes
img/icons/light/publish.png | Bin 0 -> 992 bytes
img/icons/light/scope_blog.png | Bin 1036 -> 992 bytes
img/icons/night/publish.png | Bin 0 -> 992 bytes
img/icons/night/scope_blog.png | Bin 1036 -> 992 bytes
img/icons/publish.png | Bin 0 -> 992 bytes
img/icons/purple/publish.png | Bin 0 -> 5523 bytes
img/icons/purple/scope_blog.png | Bin 1040 -> 5523 bytes
img/icons/scope_blog.png | Bin 1037 -> 992 bytes
img/icons/solidaric/publish.png | Bin 0 -> 5517 bytes
img/icons/solidaric/scope_blog.png | Bin 1436 -> 5517 bytes
img/icons/starlight/publish.png | Bin 0 -> 5517 bytes
img/icons/starlight/scope_blog.png | Bin 5677 -> 5517 bytes
img/icons/zen/publish.png | Bin 0 -> 5519 bytes
img/icons/zen/scope_blog.png | Bin 1454 -> 5519 bytes
23 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 img/icons/blue/publish.png
create mode 100644 img/icons/hacker/publish.png
create mode 100644 img/icons/henge/publish.png
create mode 100644 img/icons/indymediaclassic/publish.png
create mode 100644 img/icons/lcd/publish.png
create mode 100644 img/icons/light/publish.png
create mode 100644 img/icons/night/publish.png
create mode 100644 img/icons/publish.png
create mode 100644 img/icons/purple/publish.png
create mode 100644 img/icons/solidaric/publish.png
create mode 100644 img/icons/starlight/publish.png
create mode 100644 img/icons/zen/publish.png
diff --git a/img/icons/blue/publish.png b/img/icons/blue/publish.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3cdb1b81c61c74c8309fc817f31f63ea321d815
GIT binary patch
literal 992
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
Tm~~kaNC#3XlH{A?$4vbL%IOKV
literal 0
HcmV?d00001
diff --git a/img/icons/hacker/scope_blog.png b/img/icons/hacker/scope_blog.png
index b605e678634d6505ef67d6eab4b127e401a54886..82eb421f02950f64c87120ba95c662dc13022f6a 100644
GIT binary patch
literal 5518
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
Tm~~kaNC#3XlH{A?$4vbL%IOKV
delta 953
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
literal 0
HcmV?d00001
diff --git a/img/icons/lcd/publish.png b/img/icons/lcd/publish.png
new file mode 100644
index 0000000000000000000000000000000000000000..b26e97d5078299b7d44012027a5455234c4d42bb
GIT binary patch
literal 5518
zcmeHLc~}$I79T(sv0#1LLMdX5xKv3pS;*EzmXMMl2@t>ysw9&MjASDjNWiiP;#xti
z;PR*HSkkiDhxY@WuX(1U+1`Vb~NqD9a
zC81VK13}gczmz6aUn0G>
z*K~`kAFi+1BXSmIADx1py#DD?ePP+WH*_Nx%TIoO%Q<1q?FT7!9f!i{lNVl?Yq)4S
zUva3B78gWGwriF^vgB*
z^94*FeWdBrrT}Pb4dPy*FWhi5I5%MQCwb-7@MP`&-mKj_BZ@DrZOPo6Blh^cnS4FL
zA#~V9_hdO##mPL!&E;3%gf~#NvY8?*+;>9SmE3Z*fyf+PsFQ#{6Ak=8V$4{?hT-iWWr#
zujJGm-)gLtTg>Yzo3W_PvDJCc2+cZ0ZG=aB>L)W}TMx1-qejRQwBIOB?!|9*@jXq&
zm$QDZU0>i?d1jQSuuB=vzZ>JC&>eRZh5NG49N%`SoqaBO!M^Ss`6_ncyz<7aHGlVM
z)U0>k^zCxFhSa<$_|hZ?e^!)cdW@T(CBDpO=`BxrA>(NK@EvFUUGje*eG|f|bFIB)
zGLP>Y#mSj?ZPJn4kblW=TKDHY55_LY&Ux(u`H*~TLddWAS2)AgND5OMvbT=Gb4KSr
z{3*xsMM9hXJGb1ezdiixo6}T%^R^56dO{({p%4>^qNO6ylZF9pQkK0&C}{}xZJM7@
zHFv7#n`6?BN56l7G_`AI{FKnb=xGOjPBQ)E#lj?tL|^A`^9#nUjCb`)et&GDbI5I{
z#+Ei|n>gapd_~HrD_^7qbzEL}>7P!Ier3eA7NrHB3z$%{Y{bSJyI=o6?AG^V^OWrq
zH9wmxHY~o@dt_)onVQ=1
z(J{E}?99?yx!G-afND5-*zuheix~lpDd!~gH5GlIEzLcBXJz-=mUzbrUpY>V9?#3U
z9n^by@5U`<#*e;CE5FxtC}_!|PU`8M-#z%PM#quo)iaG%2OpL2;d|1oy;b7kybfJw
z*O980h4nq2@a@VOOS&Zvn;w7QXARnBIN_LmI5?&Cm%X*cXI)9M_a_?i3Peej!QWl?
zyLEHHtzW)pwm!~*niQtvm-onGA!y`E44h!IWs!Wuprb0)1{F%R>WtuogCIeW)u=>L
zQJkbglQF%Je5>vlnS`l@gSTwJ@m$Q!@
zClQ=Vl_4D$lF4A6G)N9+5wR5*2Llje8@(A<)1+X)0_y<)8Uu#86qrF_@M!k_U{)p@
z^46ONRRns{tV$z|PK9YY-7_9$T%7SV-g7<7ao|EiQ=n!;x(PwW8K@qgU?0?|O*h-e
zOgE#1$(FZPt)YRWY?1AK!lkn4As<4Jsosn$^$1E(0dgt^cu=m23$s~F3X{o!
zDJ%}mp{TfYl%ip4STLP|A_&8tBFcn;PEcy?vm&U}fQrS1(Lg1gN8zG$6@>-!Us)d>GRz^DYphUt{aD9xx(woMS*!Uu~1
z!YYa00L(T!SPNfdLY26|6lXAKg=B)8L{QpViX<3p7Jj4wu{qknsG8`v!Hx@6Cev&~
z0qsTLKQP6n7%ck#jpr$Jh$X~?TMVYuC{vVb0gB)+=6N1?h)Ds?Ix}v{l>W)0{sSi%
zC|3#aHJCE(;>V(C1EYZhNsHN}B9Ux|0AGm=$Zu9=plaI|038RWkQAjp83m{9V5>c`
zW6vpLwu;MTb3p~8FoQ#3ap_EoO07~;U@nt|qKJ~t&
zi=n1xg?Uma{{?5~KUkE1=57xg@D>@2nV@M?@F!lQU
zDe$Z6`oGEL{`3hO)q@*73wVAOp4>?M(&!Q$87H3W3^92gr7I410*eE#kjFy7SI>i=
z79j`KjPNjMne$i)7z{T`=9nRfI)V5&SgV3EfYB9~%EYeS?q0(K#vgsqcnX-_!o{<3
zkwHg1PCG0AmIu2eroGE{TF%+AXi<$ryw`SKaGlToR9B}l
zON5cTA3J<_$J_fx`RaYSo~s-edW-z1cYA~UyXubwchc5<(L4G?(&N(*TE7nVt1oef
X=4KR!D9sz?ARS05mWNe`CguMdNGAa9
literal 0
HcmV?d00001
diff --git a/img/icons/lcd/scope_blog.png b/img/icons/lcd/scope_blog.png
index f11aa6a172beeb82903c497b6cd30076d07d9c85..b26e97d5078299b7d44012027a5455234c4d42bb 100644
GIT binary patch
literal 5518
zcmeHLc~}$I79T(sv0#1LLMdX5xKv3pS;*EzmXMMl2@t>ysw9&MjASDjNWiiP;#xti
z;PR*HSkkiDhxY@WuX(1U+1`Vb~NqD9a
zC81VK13}gczmz6aUn0G>
z*K~`kAFi+1BXSmIADx1py#DD?ePP+WH*_Nx%TIoO%Q<1q?FT7!9f!i{lNVl?Yq)4S
zUva3B78gWGwriF^vgB*
z^94*FeWdBrrT}Pb4dPy*FWhi5I5%MQCwb-7@MP`&-mKj_BZ@DrZOPo6Blh^cnS4FL
zA#~V9_hdO##mPL!&E;3%gf~#NvY8?*+;>9SmE3Z*fyf+PsFQ#{6Ak=8V$4{?hT-iWWr#
zujJGm-)gLtTg>Yzo3W_PvDJCc2+cZ0ZG=aB>L)W}TMx1-qejRQwBIOB?!|9*@jXq&
zm$QDZU0>i?d1jQSuuB=vzZ>JC&>eRZh5NG49N%`SoqaBO!M^Ss`6_ncyz<7aHGlVM
z)U0>k^zCxFhSa<$_|hZ?e^!)cdW@T(CBDpO=`BxrA>(NK@EvFUUGje*eG|f|bFIB)
zGLP>Y#mSj?ZPJn4kblW=TKDHY55_LY&Ux(u`H*~TLddWAS2)AgND5OMvbT=Gb4KSr
z{3*xsMM9hXJGb1ezdiixo6}T%^R^56dO{({p%4>^qNO6ylZF9pQkK0&C}{}xZJM7@
zHFv7#n`6?BN56l7G_`AI{FKnb=xGOjPBQ)E#lj?tL|^A`^9#nUjCb`)et&GDbI5I{
z#+Ei|n>gapd_~HrD_^7qbzEL}>7P!Ier3eA7NrHB3z$%{Y{bSJyI=o6?AG^V^OWrq
zH9wmxHY~o@dt_)onVQ=1
z(J{E}?99?yx!G-afND5-*zuheix~lpDd!~gH5GlIEzLcBXJz-=mUzbrUpY>V9?#3U
z9n^by@5U`<#*e;CE5FxtC}_!|PU`8M-#z%PM#quo)iaG%2OpL2;d|1oy;b7kybfJw
z*O980h4nq2@a@VOOS&Zvn;w7QXARnBIN_LmI5?&Cm%X*cXI)9M_a_?i3Peej!QWl?
zyLEHHtzW)pwm!~*niQtvm-onGA!y`E44h!IWs!Wuprb0)1{F%R>WtuogCIeW)u=>L
zQJkbglQF%Je5>vlnS`l@gSTwJ@m$Q!@
zClQ=Vl_4D$lF4A6G)N9+5wR5*2Llje8@(A<)1+X)0_y<)8Uu#86qrF_@M!k_U{)p@
z^46ONRRns{tV$z|PK9YY-7_9$T%7SV-g7<7ao|EiQ=n!;x(PwW8K@qgU?0?|O*h-e
zOgE#1$(FZPt)YRWY?1AK!lkn4As<4Jsosn$^$1E(0dgt^cu=m23$s~F3X{o!
zDJ%}mp{TfYl%ip4STLP|A_&8tBFcn;PEcy?vm&U}fQrS1(Lg1gN8zG$6@>-!Us)d>GRz^DYphUt{aD9xx(woMS*!Uu~1
z!YYa00L(T!SPNfdLY26|6lXAKg=B)8L{QpViX<3p7Jj4wu{qknsG8`v!Hx@6Cev&~
z0qsTLKQP6n7%ck#jpr$Jh$X~?TMVYuC{vVb0gB)+=6N1?h)Ds?Ix}v{l>W)0{sSi%
zC|3#aHJCE(;>V(C1EYZhNsHN}B9Ux|0AGm=$Zu9=plaI|038RWkQAjp83m{9V5>c`
zW6vpLwu;MTb3p~8FoQ#3ap_EoO07~;U@nt|qKJ~t&
zi=n1xg?Uma{{?5~KUkE1=57xg@D>@2nV@M?@F!lQU
zDe$Z6`oGEL{`3hO)q@*73wVAOp4>?M(&!Q$87H3W3^92gr7I410*eE#kjFy7SI>i=
z79j`KjPNjMne$i)7z{T`=9nRfI)V5&SgV3EfYB9~%EYeS?q0(K#vgsqcnX-_!o{<3
zkwHg1PCG0AmIu2eroGE{TF%+AXi<$ryw`SKaGlToR9B}l
zON5cTA3J<_$J_fx`RaYSo~s-edW-z1cYA~UyXubwchc5<(L4G?(&N(*TE7nVt1oef
X=4KR!D9sz?ARS05mWNe`CguMdNGAa9
delta 1380
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
a)1bMjo2Dbi4?;mYkX#lTu-kvZ@_z%H*%L?r
literal 0
HcmV?d00001
diff --git a/img/icons/purple/scope_blog.png b/img/icons/purple/scope_blog.png
index 1332dca9acc88a5f6a636723ede15380ee510c6a..dd52b7e22cfe7cd4435bf354eaa08aca1ec63812 100644
GIT binary patch
literal 5523
zcmeHLYg7~079KqtP2Yb8bTXGgCJ;#JUAd6{FhK4JA3dqyZlBm1ldnXS4NTHh>30>^cp+~qmyX{
zjExE|kFS-RM{%`L534+ku-j6L&J%o6`B
z;cqDofvr`R?X8^c>p$?-U#R){ROO$SXbo3gwJs}JXR10J_cN~jR-bxUyNO6%D4eTr
zBrY?ScIUXqeWxCTUXS16+>+Hi=6XYUNl9|_`unt2rg_)+n9;tS?@So(;XHrd=?9e&
zS?|jRc+GPzn2*lQ*d!U`){Hl-PB^sU5#OuU{mHf`r>jy6ck!xsHq~6cdt>Y2(lr;E
z`7^tfc^z%eKddRbuV5V*y`=M-kG4Fj3@SUYt}8Sp^EO(PXbk3KMZylLIBT!wlKE`>
zr>D3z*UAeTe|2a=HxAi(UYfLHhy4cs!HZUmNU7kp#{L*Mg!Q&>g8|b`o!PSBz3Ok(
zTQx4cOP>V01$psqu9uuWz~0xs1>60;N5?c|((8HkXDfqT=DS(SmX^e4MtcU-cSIdy
zZyi0v`|U4;Dz{~ImpBgfd;CL8+m7rX6e$UtQ!vhPS7)_y?2Ou(Wsoi^KkATLjL6o=8T+W
z$8-t9J`^kK78SRuUsDx1ozRYu_}^2HDh|Iczb&~qaFHp@_4J+aNkyT#?oMU7QI1XR
zTW@}OhB?6!^ix-HY(Z1D|%fV{F5_3&*vTv5%xL+HuZ)YDj-UGRpnL6I--o}nMjA)iFJg2K_B>1L6m?$EoNPWcYb+v*
zp6EO2P0p?5CpN{-iCuCd))3YF!En#wLXWIr8&-KmUM_G|Y&7QxDiTCLJCDwveJ-Zx
z^YG35j&J9?pSyKtW^B1|?Z8ocoCYyQDyuR^Oj%oBk*j)3_D8adW!a7VZ%VTh?K8*T
zn=D-gV5$`b}HkXTVuETfB^?3C7Q;or}j4q?Eb7hy7|i8{I;X_C*Asd
z%P8@H^!*td+P0Ln+vQJ_SaRR`aIeQBoc74)&XT~Zc@F9NjJk@(7~}YI7fE5wj4P1i
znxlq{y+8U~X}Wl&@km_ZlP6H@55(4OyP0YTa@l}`V=PJ$B0}|A7NXXxFqT%@$XgGU&2TrFnI;VWQ;L5juW!RZ7Rkshi<(-Tpln&Ib5^D&D6
zffgeXx>=i~Gm6Y&hLu+YzEfs4gKmY8iDE{SB8)E86BwPx;;~?+%#5dU8NM{S524nG
z!UF<(DZog~h$l&dh|M;cOe~WpOHahHIYOb34RhICE)yV_#xxy?n3+1G2Sw4t5r7#{
z0ymJjUPq@m5tTlL6f+oLp599iW>J|L7<&UyV=KLpRI}w^zys?605%tf1x%RB;qY_+A*x{H_pF*IREEUs{9=1UZ
zNhwC#m?=h#GFkIZQft^CDQjd~pCGv+tj~v%F%H)ntR58F7O6)2aE25j$%;{pCo*vm>q?Sp7~m|V=@vfzG8SQ0{N^n_N-2*GtJ=KcvKuEioq
zgc6P81$**%Uf|8)3IT0DC=w%#pvWms4$R_mtuty=6bKj*P;9ssiNn|iU7U4-+7=O5
z3=kHfdIK<9?O-h;DS;uRo>1!bNn!?-E1jaWwiMl`w^>9XderJ@1!HQe-+DXFABkgI
zhd%5Vf&ao35wADt{x_a}XdlaTf;8!g#972FRRV^RFXnk3xQ{6uoOMQ$NR$7~qW%Nt
z(^Ia&z*kSC*~E{)l6yuy2T~Glm5NTc9s(i+?UCPzq+)98762W4rqFmq7l(nti?SHKYk4u<3E22Eaap
z*0Sz1eX_Os-g!0`Q#=N0+zQA4QZfhX!lC-Wzwi2`A&})Ls6ZjG=A-v_c**T{*cHT|jr+W3gE5{GV7X_LFW%9oE)E
z6P&p2mhq5hp~bQk((F6#{#|gU{m6pzp*ceMwb}I=_2_*-|>`8RA
zAGul_QqgUf+3D`yzAgK+r9(FkSm-W&lXbVtZ^HGXHIim_-m$LNPcP`MgRrA{@S8_B
a)1bMjo2Dbi4?;mYkX#lTu-kvZ@_z%H*%L?r
delta 960
zcmV;x13&zeE073~BLV{BktH90O_Jm&42AccVvb-*2#Mohej?@uef)V~%2}CJ-4Wd}
zi(aG&8!QV6-X}Y&LjUpa2>;-qEF7Ymr<~G(Bj%X7VBp7fTs<9eUDpx!BiujP!~K9^
z67+K2roR0P?E08sdjg%$_F#3#@f~PC_y{O7Gh?v*d!s)>I=>YClbL}&=-l4zSl0?!x!=H(TjFR*>-uBA{=Qu
zq!*30o%DO{mO`IbM32l*4@~bAU$q;}$LQDYN=#5Hg}tedt*j9oPe>_iT0%3Nim|Ph
z(HvW>Al5+hM5}=^G1MM^6sb|6L4Ea=DwY_zv!Q3MEX(F%oVm(G5ND&oCRth#2b&6N
z0yE!hp_MnUe3cxT8^Mza!3ZOZT|8?V%b4Wm6BV{58Q
z-_^pWx;Z|96%f=VvtxthIIj?=(xbLyaTfUL5yO6+$=VJ8iI7`=u^18%I1-6cqQjVt
z2s%3W6s&xf{Rsl3%8kMR8<7M!&NDaW7%g8`zRdWv0E9{s1DY%auwqoyk3~lf6%~y^
zRg;>!W-UolOqx=%G`~^B#Gm-GoPiRd}^#(Q?=?f*V4F2!|UHtv(UWdRyuW*QjcAG>ejvIUIuDy
z$cRHn8a907QEqCR>N7QZA@^%)w5d&EarxPU8q8)NA!vS1bTR`mjs)U1380{PGCQ1N
z!6$6gWNB<1&zPOjekaCE=+X)2e~lOZE|0^eM7C4
z^@}14(0mK0riToC*!oCb*vl{2dEN~dCwVk+W-In22e~?MF0Q*`IGt>b~8(pkrX6<4c7nw4c7reD4Tcy000Mc
zNliru