mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon
commit
e98a5d0d50
|
|
@ -77,7 +77,8 @@ def _acceptFollow(baseDir: str, domain: str, messageJson: {},
|
||||||
if not messageJson['object'].get('type'):
|
if not messageJson['object'].get('type'):
|
||||||
return
|
return
|
||||||
if not messageJson['object']['type'] == 'Follow':
|
if not messageJson['object']['type'] == 'Follow':
|
||||||
return
|
if not messageJson['object']['type'] == 'Join':
|
||||||
|
return
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: receiving Follow activity')
|
print('DEBUG: receiving Follow activity')
|
||||||
if not messageJson['object'].get('actor'):
|
if not messageJson['object'].get('actor'):
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ def _validHashtagCategory(category: str) -> bool:
|
||||||
if not category:
|
if not category:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
invalidChars = (',', ' ', '<', ';', '\\')
|
invalidChars = (',', ' ', '<', ';', '\\', '"', '&', '#')
|
||||||
for ch in invalidChars:
|
for ch in invalidChars:
|
||||||
if ch in category:
|
if ch in category:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
12
content.py
12
content.py
|
|
@ -10,6 +10,7 @@ import os
|
||||||
import email.parser
|
import email.parser
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
from utils import isValidLanguage
|
||||||
from utils import getImageExtensions
|
from utils import getImageExtensions
|
||||||
from utils import loadJson
|
from utils import loadJson
|
||||||
from utils import fileLastModified
|
from utils import fileLastModified
|
||||||
|
|
@ -377,12 +378,19 @@ def validHashTag(hashtag: str) -> bool:
|
||||||
# long hashtags are not valid
|
# long hashtags are not valid
|
||||||
if len(hashtag) >= 32:
|
if len(hashtag) >= 32:
|
||||||
return False
|
return False
|
||||||
# TODO: this may need to be an international character set
|
|
||||||
validChars = set('0123456789' +
|
validChars = set('0123456789' +
|
||||||
'abcdefghijklmnopqrstuvwxyz' +
|
'abcdefghijklmnopqrstuvwxyz' +
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
||||||
|
'¡¿ÄäÀàÁáÂâÃãÅåǍǎĄąĂăÆæĀā' +
|
||||||
|
'ÇçĆćĈĉČčĎđĐďðÈèÉéÊêËëĚěĘęĖėĒē' +
|
||||||
|
'ĜĝĢģĞğĤĥÌìÍíÎîÏïıĪīĮįĴĵĶķ' +
|
||||||
|
'ĹĺĻļŁłĽľĿŀÑñŃńŇňŅņÖöÒòÓóÔôÕõŐőØøŒœ' +
|
||||||
|
'ŔŕŘřẞߌśŜŝŞşŠšȘșŤťŢţÞþȚțÜüÙùÚúÛûŰűŨũŲųŮůŪū' +
|
||||||
|
'ŴŵÝýŸÿŶŷŹźŽžŻż')
|
||||||
if set(hashtag).issubset(validChars):
|
if set(hashtag).issubset(validChars):
|
||||||
return True
|
return True
|
||||||
|
if isValidLanguage(hashtag):
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
30
context.py
30
context.py
|
|
@ -13,7 +13,8 @@ validContexts = (
|
||||||
"https://w3id.org/security/v1",
|
"https://w3id.org/security/v1",
|
||||||
"*/apschema/v1.9",
|
"*/apschema/v1.9",
|
||||||
"*/apschema/v1.21",
|
"*/apschema/v1.21",
|
||||||
"*/litepub-0.1.jsonld"
|
"*/litepub-0.1.jsonld",
|
||||||
|
"https://litepub.social/litepub/context.jsonld"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,6 +130,33 @@ def getApschemaV1_21() -> {}:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def getLitepubSocial() -> {}:
|
||||||
|
# https://litepub.social/litepub/context.jsonld
|
||||||
|
return {
|
||||||
|
'@context': [
|
||||||
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
'https://w3id.org/security/v1',
|
||||||
|
{
|
||||||
|
'Emoji': 'toot:Emoji',
|
||||||
|
'Hashtag': 'as:Hashtag',
|
||||||
|
'PropertyValue': 'schema:PropertyValue',
|
||||||
|
'atomUri': 'ostatus:atomUri',
|
||||||
|
'conversation': {
|
||||||
|
'@id': 'ostatus:conversation',
|
||||||
|
'@type': '@id'
|
||||||
|
},
|
||||||
|
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||||
|
'ostatus': 'http://ostatus.org#',
|
||||||
|
'schema': 'http://schema.org',
|
||||||
|
'sensitive': 'as:sensitive',
|
||||||
|
'toot': 'http://joinmastodon.org/ns#',
|
||||||
|
'totalItems': 'as:totalItems',
|
||||||
|
'value': 'schema:value'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def getLitepubV0_1() -> {}:
|
def getLitepubV0_1() -> {}:
|
||||||
# https://domain/schemas/litepub-0.1.jsonld
|
# https://domain/schemas/litepub-0.1.jsonld
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
12
daemon.py
12
daemon.py
|
|
@ -2344,15 +2344,15 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if debug:
|
if debug:
|
||||||
print('You cannot follow the news actor')
|
print('You cannot follow the news actor')
|
||||||
else:
|
else:
|
||||||
if debug:
|
print('Sending follow request from ' +
|
||||||
print('Sending follow request from ' +
|
followerNickname + ' to ' + followingActor)
|
||||||
followerNickname + ' to ' + followingActor)
|
|
||||||
sendFollowRequest(self.server.session,
|
sendFollowRequest(self.server.session,
|
||||||
baseDir, followerNickname,
|
baseDir, followerNickname,
|
||||||
domain, port,
|
domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
followingNickname,
|
followingNickname,
|
||||||
followingDomain,
|
followingDomain,
|
||||||
|
followingActor,
|
||||||
followingPort, httpPrefix,
|
followingPort, httpPrefix,
|
||||||
False, self.server.federationList,
|
False, self.server.federationList,
|
||||||
self.server.sendThreads,
|
self.server.sendThreads,
|
||||||
|
|
@ -13537,8 +13537,10 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
return
|
return
|
||||||
|
|
||||||
# refuse to receive non-json content
|
# refuse to receive non-json content
|
||||||
if self.headers['Content-type'] != 'application/json' and \
|
contentTypeStr = self.headers['Content-type']
|
||||||
self.headers['Content-type'] != 'application/activity+json':
|
if not contentTypeStr.startswith('application/json') and \
|
||||||
|
not contentTypeStr.startswith('application/activity+json') and \
|
||||||
|
not contentTypeStr.startswith('application/ld+json'):
|
||||||
print("POST is not json: " + self.headers['Content-type'])
|
print("POST is not json: " + self.headers['Content-type'])
|
||||||
if self.server.debug:
|
if self.server.debug:
|
||||||
print(str(self.headers))
|
print(str(self.headers))
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -98,6 +98,7 @@
|
||||||
"confused": "1F615",
|
"confused": "1F615",
|
||||||
"confusedface": "1F615",
|
"confusedface": "1F615",
|
||||||
"copyleftsymbol": "1F12F",
|
"copyleftsymbol": "1F12F",
|
||||||
|
"copyleft": "1F12F",
|
||||||
"copyright": "00A9",
|
"copyright": "00A9",
|
||||||
"couchandlamp": "1F6CB",
|
"couchandlamp": "1F6CB",
|
||||||
"couplewithheart": "1F491",
|
"couplewithheart": "1F491",
|
||||||
|
|
|
||||||
10
epicyon.py
10
epicyon.py
|
|
@ -1379,6 +1379,10 @@ if args.actor:
|
||||||
nickname = args.actor.split('/accounts/')[1]
|
nickname = args.actor.split('/accounts/')[1]
|
||||||
nickname = nickname.replace('\n', '').replace('\r', '')
|
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||||
domain = args.actor.split('/accounts/')[0]
|
domain = args.actor.split('/accounts/')[0]
|
||||||
|
elif '/u/' in args.actor:
|
||||||
|
nickname = args.actor.split('/u/')[1]
|
||||||
|
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||||
|
domain = args.actor.split('/u/')[0]
|
||||||
else:
|
else:
|
||||||
# format: @nick@domain
|
# format: @nick@domain
|
||||||
if '@' not in args.actor:
|
if '@' not in args.actor:
|
||||||
|
|
@ -1445,6 +1449,7 @@ if args.actor:
|
||||||
personUrl = personUrl.replace('/accounts/', '/actor/')
|
personUrl = personUrl.replace('/accounts/', '/actor/')
|
||||||
personUrl = personUrl.replace('/channel/', '/actor/')
|
personUrl = personUrl.replace('/channel/', '/actor/')
|
||||||
personUrl = personUrl.replace('/profile/', '/actor/')
|
personUrl = personUrl.replace('/profile/', '/actor/')
|
||||||
|
personUrl = personUrl.replace('/u/', '/actor/')
|
||||||
if not personUrl:
|
if not personUrl:
|
||||||
# try single user instance
|
# try single user instance
|
||||||
personUrl = httpPrefix + '://' + domain
|
personUrl = httpPrefix + '://' + domain
|
||||||
|
|
@ -1508,6 +1513,10 @@ if args.followers:
|
||||||
nickname = args.followers.split('/accounts/')[1]
|
nickname = args.followers.split('/accounts/')[1]
|
||||||
nickname = nickname.replace('\n', '').replace('\r', '')
|
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||||
domain = args.followers.split('/accounts/')[0]
|
domain = args.followers.split('/accounts/')[0]
|
||||||
|
elif '/u/' in args.followers:
|
||||||
|
nickname = args.followers.split('/u/')[1]
|
||||||
|
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||||
|
domain = args.followers.split('/u/')[0]
|
||||||
else:
|
else:
|
||||||
# format: @nick@domain
|
# format: @nick@domain
|
||||||
if '@' not in args.followers:
|
if '@' not in args.followers:
|
||||||
|
|
@ -1572,6 +1581,7 @@ if args.followers:
|
||||||
personUrl = personUrl.replace('/accounts/', '/actor/')
|
personUrl = personUrl.replace('/accounts/', '/actor/')
|
||||||
personUrl = personUrl.replace('/channel/', '/actor/')
|
personUrl = personUrl.replace('/channel/', '/actor/')
|
||||||
personUrl = personUrl.replace('/profile/', '/actor/')
|
personUrl = personUrl.replace('/profile/', '/actor/')
|
||||||
|
personUrl = personUrl.replace('/u/', '/actor/')
|
||||||
if not personUrl:
|
if not personUrl:
|
||||||
# try single user instance
|
# try single user instance
|
||||||
personUrl = httpPrefix + '://' + domain
|
personUrl = httpPrefix + '://' + domain
|
||||||
|
|
|
||||||
16
follow.py
16
follow.py
|
|
@ -204,6 +204,9 @@ def isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
|
||||||
elif '://' + followerDomain + \
|
elif '://' + followerDomain + \
|
||||||
'/accounts/' + followerNickname in followersStr:
|
'/accounts/' + followerNickname in followersStr:
|
||||||
alreadyFollowing = True
|
alreadyFollowing = True
|
||||||
|
elif '://' + followerDomain + \
|
||||||
|
'/u/' + followerNickname in followersStr:
|
||||||
|
alreadyFollowing = True
|
||||||
|
|
||||||
return alreadyFollowing
|
return alreadyFollowing
|
||||||
|
|
||||||
|
|
@ -542,6 +545,8 @@ def _storeFollowRequest(baseDir: str,
|
||||||
alreadyFollowing = True
|
alreadyFollowing = True
|
||||||
elif '://' + domainFull + '/accounts/' + nickname in followersStr:
|
elif '://' + domainFull + '/accounts/' + nickname in followersStr:
|
||||||
alreadyFollowing = True
|
alreadyFollowing = True
|
||||||
|
elif '://' + domainFull + '/u/' + nickname in followersStr:
|
||||||
|
alreadyFollowing = True
|
||||||
|
|
||||||
if alreadyFollowing:
|
if alreadyFollowing:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -598,7 +603,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||||
"""Receives a follow request within the POST section of HTTPServer
|
"""Receives a follow request within the POST section of HTTPServer
|
||||||
"""
|
"""
|
||||||
if not messageJson['type'].startswith('Follow'):
|
if not messageJson['type'].startswith('Follow'):
|
||||||
return False
|
if not messageJson['type'].startswith('Join'):
|
||||||
|
return False
|
||||||
print('Receiving follow request')
|
print('Receiving follow request')
|
||||||
if not messageJson.get('actor'):
|
if not messageJson.get('actor'):
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -866,6 +872,7 @@ def followedAccountRejects(session, baseDir: str, httpPrefix: str,
|
||||||
def sendFollowRequest(session, baseDir: str,
|
def sendFollowRequest(session, baseDir: str,
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
nickname: str, domain: str, port: int, httpPrefix: str,
|
||||||
followNickname: str, followDomain: str,
|
followNickname: str, followDomain: str,
|
||||||
|
followedActor: str,
|
||||||
followPort: int, followHttpPrefix: str,
|
followPort: int, followHttpPrefix: str,
|
||||||
clientToServer: bool, federationList: [],
|
clientToServer: bool, federationList: [],
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -874,6 +881,7 @@ def sendFollowRequest(session, baseDir: str,
|
||||||
"""Gets the json object for sending a follow request
|
"""Gets the json object for sending a follow request
|
||||||
"""
|
"""
|
||||||
if not domainPermitted(followDomain, federationList):
|
if not domainPermitted(followDomain, federationList):
|
||||||
|
print('You are not permitted to follow the domain ' + followDomain)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
fullDomain = getFullDomain(domain, port)
|
fullDomain = getFullDomain(domain, port)
|
||||||
|
|
@ -884,8 +892,7 @@ def sendFollowRequest(session, baseDir: str,
|
||||||
statusNumber, published = getStatusNumber()
|
statusNumber, published = getStatusNumber()
|
||||||
|
|
||||||
if followNickname:
|
if followNickname:
|
||||||
followedId = followHttpPrefix + '://' + \
|
followedId = followedActor
|
||||||
requestDomain + '/users/' + followNickname
|
|
||||||
followHandle = followNickname + '@' + requestDomain
|
followHandle = followNickname + '@' + requestDomain
|
||||||
else:
|
else:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -1162,7 +1169,8 @@ def outboxUndoFollow(baseDir: str, messageJson: {}, debug: bool) -> None:
|
||||||
if not messageJson['object'].get('type'):
|
if not messageJson['object'].get('type'):
|
||||||
return
|
return
|
||||||
if not messageJson['object']['type'] == 'Follow':
|
if not messageJson['object']['type'] == 'Follow':
|
||||||
return
|
if not messageJson['object']['type'] == 'Join':
|
||||||
|
return
|
||||||
if not messageJson['object'].get('object'):
|
if not messageJson['object'].get('object'):
|
||||||
return
|
return
|
||||||
if not messageJson['object'].get('actor'):
|
if not messageJson['object'].get('actor'):
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 29 KiB |
33
inbox.py
33
inbox.py
|
|
@ -105,6 +105,8 @@ def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not tag.get('type'):
|
if not tag.get('type'):
|
||||||
continue
|
continue
|
||||||
|
if not isinstance(tag['type'], str):
|
||||||
|
continue
|
||||||
if tag['type'] != 'Hashtag':
|
if tag['type'] != 'Hashtag':
|
||||||
continue
|
continue
|
||||||
if not tag.get('name'):
|
if not tag.get('name'):
|
||||||
|
|
@ -274,8 +276,30 @@ def inboxMessageHasParams(messageJson: {}) -> bool:
|
||||||
# print('inboxMessageHasParams: ' +
|
# print('inboxMessageHasParams: ' +
|
||||||
# param + ' ' + str(messageJson))
|
# param + ' ' + str(messageJson))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# actor should be a string
|
||||||
|
if not isinstance(messageJson['actor'], str):
|
||||||
|
print('WARN: actor should be a string, but is actually: ' +
|
||||||
|
str(messageJson['actor']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# type should be a string
|
||||||
|
if not isinstance(messageJson['type'], str):
|
||||||
|
print('WARN: type from ' + str(messageJson['actor']) +
|
||||||
|
' should be a string, but is actually: ' +
|
||||||
|
str(messageJson['type']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# object should be a dict or a string
|
||||||
|
if not isinstance(messageJson['object'], dict):
|
||||||
|
if not isinstance(messageJson['object'], str):
|
||||||
|
print('WARN: object from ' + str(messageJson['actor']) +
|
||||||
|
' should be a dict or string, but is actually: ' +
|
||||||
|
str(messageJson['object']))
|
||||||
|
return False
|
||||||
|
|
||||||
if not messageJson.get('to'):
|
if not messageJson.get('to'):
|
||||||
allowedWithoutToParam = ['Like', 'Follow', 'Request',
|
allowedWithoutToParam = ['Like', 'Follow', 'Join', 'Request',
|
||||||
'Accept', 'Capability', 'Undo']
|
'Accept', 'Capability', 'Undo']
|
||||||
if messageJson['type'] not in allowedWithoutToParam:
|
if messageJson['type'] not in allowedWithoutToParam:
|
||||||
return False
|
return False
|
||||||
|
|
@ -297,7 +321,7 @@ def inboxPermittedMessage(domain: str, messageJson: {},
|
||||||
if not urlPermitted(actor, federationList):
|
if not urlPermitted(actor, federationList):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
alwaysAllowedTypes = ('Follow', 'Like', 'Delete', 'Announce')
|
alwaysAllowedTypes = ('Follow', 'Join', 'Like', 'Delete', 'Announce')
|
||||||
if messageJson['type'] not in alwaysAllowedTypes:
|
if messageJson['type'] not in alwaysAllowedTypes:
|
||||||
if not messageJson.get('object'):
|
if not messageJson.get('object'):
|
||||||
return True
|
return True
|
||||||
|
|
@ -693,7 +717,8 @@ def _receiveUndo(session, baseDir: str, httpPrefix: str,
|
||||||
print('DEBUG: ' + messageJson['type'] +
|
print('DEBUG: ' + messageJson['type'] +
|
||||||
' object within object is not a string')
|
' object within object is not a string')
|
||||||
return False
|
return False
|
||||||
if messageJson['object']['type'] == 'Follow':
|
if messageJson['object']['type'] == 'Follow' or \
|
||||||
|
messageJson['object']['type'] == 'Join':
|
||||||
return _receiveUndoFollow(session, baseDir, httpPrefix,
|
return _receiveUndoFollow(session, baseDir, httpPrefix,
|
||||||
port, messageJson,
|
port, messageJson,
|
||||||
federationList, debug)
|
federationList, debug)
|
||||||
|
|
@ -731,7 +756,7 @@ def _personReceiveUpdate(baseDir: str,
|
||||||
' ' + str(personJson))
|
' ' + str(personJson))
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
updateDomainFull = getFullDomain(updateDomain, updatePort)
|
updateDomainFull = getFullDomain(updateDomain, updatePort)
|
||||||
usersPaths = ('users', 'profile', 'channel', 'accounts')
|
usersPaths = ('users', 'profile', 'channel', 'accounts', 'u')
|
||||||
usersStrFound = False
|
usersStrFound = False
|
||||||
for usersStr in usersPaths:
|
for usersStr in usersPaths:
|
||||||
actor = updateDomainFull + '/' + usersStr + '/' + updateNickname
|
actor = updateDomainFull + '/' + usersStr + '/' + updateNickname
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,9 @@ def manualApproveFollowRequest(session, baseDir: str,
|
||||||
elif reqPrefix + '/accounts/' + reqNick in approveFollowsStr:
|
elif reqPrefix + '/accounts/' + reqNick in approveFollowsStr:
|
||||||
exists = True
|
exists = True
|
||||||
approveHandleFull = reqPrefix + '/accounts/' + reqNick
|
approveHandleFull = reqPrefix + '/accounts/' + reqNick
|
||||||
|
elif reqPrefix + '/u/' + reqNick in approveFollowsStr:
|
||||||
|
exists = True
|
||||||
|
approveHandleFull = reqPrefix + '/u/' + reqNick
|
||||||
if not exists:
|
if not exists:
|
||||||
print('Manual follow accept: ' + approveHandleFull +
|
print('Manual follow accept: ' + approveHandleFull +
|
||||||
' not in requests file "' +
|
' not in requests file "' +
|
||||||
|
|
|
||||||
3
posts.py
3
posts.py
|
|
@ -2414,7 +2414,8 @@ def sendToNamedAddresses(session, baseDir: str,
|
||||||
print('DEBUG: ' +
|
print('DEBUG: ' +
|
||||||
'no "to" field when sending to named addresses')
|
'no "to" field when sending to named addresses')
|
||||||
if postJsonObject['object'].get('type'):
|
if postJsonObject['object'].get('type'):
|
||||||
if postJsonObject['object']['type'] == 'Follow':
|
if postJsonObject['object']['type'] == 'Follow' or \
|
||||||
|
postJsonObject['object']['type'] == 'Join':
|
||||||
if isinstance(postJsonObject['object']['object'], str):
|
if isinstance(postJsonObject['object']['object'], str):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: "to" field assigned to Follow')
|
print('DEBUG: "to" field assigned to Follow')
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ from numbers import Integral, Real
|
||||||
from context import getApschemaV1_9
|
from context import getApschemaV1_9
|
||||||
from context import getApschemaV1_21
|
from context import getApschemaV1_21
|
||||||
from context import getLitepubV0_1
|
from context import getLitepubV0_1
|
||||||
|
from context import getLitepubSocial
|
||||||
from context import getV1Schema
|
from context import getV1Schema
|
||||||
from context import getV1SecuritySchema
|
from context import getV1SecuritySchema
|
||||||
from context import getActivitystreamsSchema
|
from context import getActivitystreamsSchema
|
||||||
|
|
@ -420,6 +421,13 @@ def load_document(url):
|
||||||
'document': getLitepubV0_1()
|
'document': getLitepubV0_1()
|
||||||
}
|
}
|
||||||
return doc
|
return doc
|
||||||
|
elif url == 'https://litepub.social/litepub/context.jsonld':
|
||||||
|
doc = {
|
||||||
|
'contextUrl': None,
|
||||||
|
'documentUrl': url,
|
||||||
|
'document': getLitepubSocial()
|
||||||
|
}
|
||||||
|
return doc
|
||||||
return None
|
return None
|
||||||
except JsonLdError as e:
|
except JsonLdError as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = epicyon
|
name = epicyon
|
||||||
version = 1.2.0
|
version = 1.3.0
|
||||||
author = Bob Mottram
|
author = Bob Mottram
|
||||||
author_email = bob@freedombone.net
|
author_email = bob@freedombone.net
|
||||||
maintainer = Bob Mottram
|
maintainer = Bob Mottram
|
||||||
|
|
|
||||||
21
tests.py
21
tests.py
|
|
@ -76,6 +76,7 @@ from inbox import jsonPostAllowsComments
|
||||||
from inbox import validInbox
|
from inbox import validInbox
|
||||||
from inbox import validInboxFilenames
|
from inbox import validInboxFilenames
|
||||||
from categories import guessHashtagCategory
|
from categories import guessHashtagCategory
|
||||||
|
from content import validHashTag
|
||||||
from content import htmlReplaceEmailQuote
|
from content import htmlReplaceEmailQuote
|
||||||
from content import htmlReplaceQuoteMarks
|
from content import htmlReplaceQuoteMarks
|
||||||
from content import dangerousCSS
|
from content import dangerousCSS
|
||||||
|
|
@ -848,10 +849,12 @@ def testFollowBetweenServers():
|
||||||
alicePersonCache = {}
|
alicePersonCache = {}
|
||||||
aliceCachedWebfingers = {}
|
aliceCachedWebfingers = {}
|
||||||
alicePostLog = []
|
alicePostLog = []
|
||||||
|
bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
|
||||||
sendResult = \
|
sendResult = \
|
||||||
sendFollowRequest(sessionAlice, aliceDir,
|
sendFollowRequest(sessionAlice, aliceDir,
|
||||||
'alice', aliceDomain, alicePort, httpPrefix,
|
'alice', aliceDomain, alicePort, httpPrefix,
|
||||||
'bob', bobDomain, bobPort, httpPrefix,
|
'bob', bobDomain, bobActor,
|
||||||
|
bobPort, httpPrefix,
|
||||||
clientToServer, federationList,
|
clientToServer, federationList,
|
||||||
aliceSendThreads, alicePostLog,
|
aliceSendThreads, alicePostLog,
|
||||||
aliceCachedWebfingers, alicePersonCache,
|
aliceCachedWebfingers, alicePersonCache,
|
||||||
|
|
@ -3088,9 +3091,25 @@ def testPrepareHtmlPostNickname():
|
||||||
assert result == expectedHtml
|
assert result == expectedHtml
|
||||||
|
|
||||||
|
|
||||||
|
def testValidHashTag():
|
||||||
|
print('testValidHashTag')
|
||||||
|
assert validHashTag('ThisIsValid')
|
||||||
|
assert validHashTag('ThisIsValid12345')
|
||||||
|
assert validHashTag('ThisIsVälid')
|
||||||
|
assert validHashTag('यहमान्यहै')
|
||||||
|
assert not validHashTag('ThisIsNotValid!')
|
||||||
|
assert not validHashTag('#ThisIsAlsoNotValid')
|
||||||
|
assert not validHashTag('#यहमान्यहै')
|
||||||
|
assert not validHashTag('ThisIsAlso&NotValid')
|
||||||
|
assert not validHashTag('ThisIsAlsoNotValid"')
|
||||||
|
assert not validHashTag('This Is Also Not Valid"')
|
||||||
|
assert not validHashTag('This=IsAlsoNotValid"')
|
||||||
|
|
||||||
|
|
||||||
def runAllTests():
|
def runAllTests():
|
||||||
print('Running tests...')
|
print('Running tests...')
|
||||||
testFunctions()
|
testFunctions()
|
||||||
|
testValidHashTag()
|
||||||
testPrepareHtmlPostNickname()
|
testPrepareHtmlPostNickname()
|
||||||
testDomainHandling()
|
testDomainHandling()
|
||||||
testMastoApi()
|
testMastoApi()
|
||||||
|
|
|
||||||
91
utils.py
91
utils.py
|
|
@ -67,7 +67,7 @@ def getLockedAccount(actorJson: {}) -> bool:
|
||||||
def hasUsersPath(pathStr: str) -> bool:
|
def hasUsersPath(pathStr: str) -> bool:
|
||||||
"""Whether there is a /users/ path (or equivalent) in the given string
|
"""Whether there is a /users/ path (or equivalent) in the given string
|
||||||
"""
|
"""
|
||||||
usersList = ('users', 'accounts', 'channel', 'profile')
|
usersList = ('users', 'accounts', 'channel', 'profile', 'u')
|
||||||
for usersStr in usersList:
|
for usersStr in usersList:
|
||||||
if '/' + usersStr + '/' in pathStr:
|
if '/' + usersStr + '/' in pathStr:
|
||||||
return True
|
return True
|
||||||
|
|
@ -656,6 +656,12 @@ def getNicknameFromActor(actor: str) -> str:
|
||||||
return nickStr
|
return nickStr
|
||||||
else:
|
else:
|
||||||
return nickStr.split('/')[0]
|
return nickStr.split('/')[0]
|
||||||
|
elif '/u/' in actor:
|
||||||
|
nickStr = actor.split('/u/')[1].replace('@', '')
|
||||||
|
if '/' not in nickStr:
|
||||||
|
return nickStr
|
||||||
|
else:
|
||||||
|
return nickStr.split('/')[0]
|
||||||
elif '/@' in actor:
|
elif '/@' in actor:
|
||||||
# https://domain/@nick
|
# https://domain/@nick
|
||||||
nickStr = actor.split('/@')[1]
|
nickStr = actor.split('/@')[1]
|
||||||
|
|
@ -696,6 +702,10 @@ def getDomainFromActor(actor: str) -> (str, int):
|
||||||
domain = actor.split('/users/')[0]
|
domain = actor.split('/users/')[0]
|
||||||
for prefix in prefixes:
|
for prefix in prefixes:
|
||||||
domain = domain.replace(prefix, '')
|
domain = domain.replace(prefix, '')
|
||||||
|
elif '/u/' in actor:
|
||||||
|
domain = actor.split('/u/')[0]
|
||||||
|
for prefix in prefixes:
|
||||||
|
domain = domain.replace(prefix, '')
|
||||||
elif '/@' in actor:
|
elif '/@' in actor:
|
||||||
domain = actor.split('/@')[0]
|
domain = actor.split('/@')[0]
|
||||||
for prefix in prefixes:
|
for prefix in prefixes:
|
||||||
|
|
@ -1163,14 +1173,58 @@ def deletePost(baseDir: str, httpPrefix: str,
|
||||||
os.remove(postFilename)
|
os.remove(postFilename)
|
||||||
|
|
||||||
|
|
||||||
def validNickname(domain: str, nickname: str) -> bool:
|
def isValidLanguage(text: str) -> bool:
|
||||||
forbiddenChars = ('.', ' ', '/', '?', ':', ';', '@', '#')
|
"""Returns true if the given text contains a valid
|
||||||
for c in forbiddenChars:
|
natural language string
|
||||||
if c in nickname:
|
"""
|
||||||
return False
|
naturalLanguages = {
|
||||||
# this should only apply for the shared inbox
|
"Latin": [65, 866],
|
||||||
if nickname == domain:
|
"Cyrillic": [1024, 1274],
|
||||||
return False
|
"Greek": [880, 1280],
|
||||||
|
"isArmenian": [1328, 1424],
|
||||||
|
"isHebrew": [1424, 1536],
|
||||||
|
"Arabic": [1536, 1792],
|
||||||
|
"Syriac": [1792, 1872],
|
||||||
|
"Thaan": [1920, 1984],
|
||||||
|
"Devanagari": [2304, 2432],
|
||||||
|
"Bengali": [2432, 2560],
|
||||||
|
"Gurmukhi": [2560, 2688],
|
||||||
|
"Gujarati": [2688, 2816],
|
||||||
|
"Oriya": [2816, 2944],
|
||||||
|
"Tamil": [2944, 3072],
|
||||||
|
"Telugu": [3072, 3200],
|
||||||
|
"Kannada": [3200, 3328],
|
||||||
|
"Malayalam": [3328, 3456],
|
||||||
|
"Sinhala": [3456, 3584],
|
||||||
|
"Thai": [3584, 3712],
|
||||||
|
"Lao": [3712, 3840],
|
||||||
|
"Tibetan": [3840, 4096],
|
||||||
|
"Myanmar": [4096, 4256],
|
||||||
|
"Georgian": [4256, 4352],
|
||||||
|
"HangulJamo": [4352, 4608],
|
||||||
|
"Cherokee": [5024, 5120],
|
||||||
|
"UCAS": [5120, 5760],
|
||||||
|
"Ogham": [5760, 5792],
|
||||||
|
"Runic": [5792, 5888],
|
||||||
|
"Khmer": [6016, 6144],
|
||||||
|
"Mongolian": [6144, 6320]
|
||||||
|
}
|
||||||
|
for langName, langRange in naturalLanguages.items():
|
||||||
|
okLang = True
|
||||||
|
for ch in text:
|
||||||
|
if ch.isdigit():
|
||||||
|
continue
|
||||||
|
if ord(ch) not in range(langRange[0], langRange[1]):
|
||||||
|
okLang = False
|
||||||
|
break
|
||||||
|
if okLang:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _isReservedName(nickname: str) -> bool:
|
||||||
|
"""Is the given nickname reserved for some special function?
|
||||||
|
"""
|
||||||
reservedNames = ('inbox', 'dm', 'outbox', 'following',
|
reservedNames = ('inbox', 'dm', 'outbox', 'following',
|
||||||
'public', 'followers', 'category',
|
'public', 'followers', 'category',
|
||||||
'channel', 'calendar',
|
'channel', 'calendar',
|
||||||
|
|
@ -1180,10 +1234,27 @@ def validNickname(domain: str, nickname: str) -> bool:
|
||||||
'activity', 'undo', 'pinned',
|
'activity', 'undo', 'pinned',
|
||||||
'reply', 'replies', 'question', 'like',
|
'reply', 'replies', 'question', 'like',
|
||||||
'likes', 'users', 'statuses', 'tags',
|
'likes', 'users', 'statuses', 'tags',
|
||||||
'accounts', 'channels', 'profile',
|
'accounts', 'channels', 'profile', 'u',
|
||||||
'updates', 'repeat', 'announce',
|
'updates', 'repeat', 'announce',
|
||||||
'shares', 'fonts', 'icons', 'avatars')
|
'shares', 'fonts', 'icons', 'avatars')
|
||||||
if nickname in reservedNames:
|
if nickname in reservedNames:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validNickname(domain: str, nickname: str) -> bool:
|
||||||
|
"""Is the given nickname valid?
|
||||||
|
"""
|
||||||
|
if not isValidLanguage(nickname):
|
||||||
|
return False
|
||||||
|
forbiddenChars = ('.', ' ', '/', '?', ':', ';', '@', '#')
|
||||||
|
for c in forbiddenChars:
|
||||||
|
if c in nickname:
|
||||||
|
return False
|
||||||
|
# this should only apply for the shared inbox
|
||||||
|
if nickname == domain:
|
||||||
|
return False
|
||||||
|
if _isReservedName(nickname):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,12 +136,12 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
|
||||||
'icons/scope_reminder.png"/><b>' + \
|
'icons/scope_reminder.png"/><b>' + \
|
||||||
translate['Reminder'] + '</b><br>' + \
|
translate['Reminder'] + '</b><br>' + \
|
||||||
translate['Scheduled note to yourself'] + '</a></li>\n'
|
translate['Scheduled note to yourself'] + '</a></li>\n'
|
||||||
dropDownContent += \
|
# dropDownContent += \
|
||||||
'<li><a href="' + pathBase + dropdownEventSuffix + \
|
# '<li><a href="' + pathBase + dropdownEventSuffix + \
|
||||||
'"><img loading="lazy" alt="" title="" src="/' + \
|
# '"><img loading="lazy" alt="" title="" src="/' + \
|
||||||
'icons/scope_event.png"/><b>' + \
|
# 'icons/scope_event.png"/><b>' + \
|
||||||
translate['Event'] + '</b><br>' + \
|
# translate['Event'] + '</b><br>' + \
|
||||||
translate['Create an event'] + '</a></li>\n'
|
# translate['Create an event'] + '</a></li>\n'
|
||||||
dropDownContent += \
|
dropDownContent += \
|
||||||
'<li><a href="' + pathBase + dropdownReportSuffix + \
|
'<li><a href="' + pathBase + dropdownReportSuffix + \
|
||||||
'"><img loading="lazy" alt="" title="" src="/' + \
|
'"><img loading="lazy" alt="" title="" src="/' + \
|
||||||
|
|
@ -198,7 +198,9 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
|
||||||
newPostText = \
|
newPostText = \
|
||||||
'<p class="new-post-text">' + \
|
'<p class="new-post-text">' + \
|
||||||
translate['Write your reply to'] + \
|
translate['Write your reply to'] + \
|
||||||
' <a href="' + inReplyTo + '">' + \
|
' <a href="' + inReplyTo + \
|
||||||
|
'" rel="nofollow noopener noreferrer" ' + \
|
||||||
|
'target="_blank">' + \
|
||||||
translate['this post'] + '</a></p>\n'
|
translate['this post'] + '</a></p>\n'
|
||||||
replyStr = '<input type="hidden" ' + \
|
replyStr = '<input type="hidden" ' + \
|
||||||
'name="replyTo" value="' + inReplyTo + '">\n'
|
'name="replyTo" value="' + inReplyTo + '">\n'
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,11 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
|
||||||
if len(hashTagName) > maxTagLength:
|
if len(hashTagName) > maxTagLength:
|
||||||
# NoIncrediblyLongAndBoringHashtagsShownHere
|
# NoIncrediblyLongAndBoringHashtagsShownHere
|
||||||
continue
|
continue
|
||||||
|
if '#' in hashTagName or \
|
||||||
|
'&' in hashTagName or \
|
||||||
|
'"' in hashTagName or \
|
||||||
|
"'" in hashTagName:
|
||||||
|
continue
|
||||||
if '#' + hashTagName + '\n' in blockedStr:
|
if '#' + hashTagName + '\n' in blockedStr:
|
||||||
continue
|
continue
|
||||||
with open(tagsFilename, 'r') as fp:
|
with open(tagsFilename, 'r') as fp:
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
repliesButton = 'buttonhighlighted'
|
repliesButton = 'buttonhighlighted'
|
||||||
mediaButton = 'button'
|
mediaButton = 'button'
|
||||||
bookmarksButton = 'button'
|
bookmarksButton = 'button'
|
||||||
eventsButton = 'button'
|
# eventsButton = 'button'
|
||||||
sentButton = 'button'
|
sentButton = 'button'
|
||||||
sharesButton = 'button'
|
sharesButton = 'button'
|
||||||
if newShare:
|
if newShare:
|
||||||
|
|
@ -196,8 +196,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
sharesButton = 'buttonselectedhighlighted'
|
sharesButton = 'buttonselectedhighlighted'
|
||||||
elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
|
elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
|
||||||
bookmarksButton = 'buttonselected'
|
bookmarksButton = 'buttonselected'
|
||||||
elif boxName == 'tlevents':
|
# elif boxName == 'tlevents':
|
||||||
eventsButton = 'buttonselected'
|
# eventsButton = 'buttonselected'
|
||||||
|
|
||||||
# get the full domain, including any port number
|
# get the full domain, including any port number
|
||||||
fullDomain = getFullDomain(domain, port)
|
fullDomain = getFullDomain(domain, port)
|
||||||
|
|
@ -254,11 +254,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
'<a href="' + usersPath + '/tlbookmarks"><button class="' + \
|
'<a href="' + usersPath + '/tlbookmarks"><button class="' + \
|
||||||
bookmarksButton + '"><span>' + translate['Bookmarks'] + \
|
bookmarksButton + '"><span>' + translate['Bookmarks'] + \
|
||||||
'</span></button></a>'
|
'</span></button></a>'
|
||||||
|
#
|
||||||
eventsButtonStr = \
|
# eventsButtonStr = \
|
||||||
'<a href="' + usersPath + '/tlevents"><button class="' + \
|
# '<a href="' + usersPath + '/tlevents"><button class="' + \
|
||||||
eventsButton + '"><span>' + translate['Events'] + \
|
# eventsButton + '"><span>' + translate['Events'] + \
|
||||||
'</span></button></a>'
|
# '</span></button></a>'
|
||||||
|
|
||||||
instanceTitle = \
|
instanceTitle = \
|
||||||
getConfigParam(baseDir, 'instanceTitle')
|
getConfigParam(baseDir, 'instanceTitle')
|
||||||
|
|
@ -400,8 +400,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
translate['Bookmarks']
|
translate['Bookmarks']
|
||||||
menuShares = \
|
menuShares = \
|
||||||
htmlHideFromScreenReader('🤝') + ' ' + sharesStr
|
htmlHideFromScreenReader('🤝') + ' ' + sharesStr
|
||||||
menuEvents = \
|
# menuEvents = \
|
||||||
htmlHideFromScreenReader('🎫') + ' ' + translate['Events']
|
# htmlHideFromScreenReader('🎫') + ' ' + translate['Events']
|
||||||
menuBlogs = \
|
menuBlogs = \
|
||||||
htmlHideFromScreenReader('📝') + ' ' + translate['Blogs']
|
htmlHideFromScreenReader('📝') + ' ' + translate['Blogs']
|
||||||
menuNewswire = \
|
menuNewswire = \
|
||||||
|
|
@ -426,7 +426,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
menuBookmarks: usersPath + '/tlbookmarks#timeline',
|
menuBookmarks: usersPath + '/tlbookmarks#timeline',
|
||||||
menuShares: usersPath + '/tlshares#timeline',
|
menuShares: usersPath + '/tlshares#timeline',
|
||||||
menuBlogs: usersPath + '/tlblogs#timeline',
|
menuBlogs: usersPath + '/tlblogs#timeline',
|
||||||
menuEvents: usersPath + '/tlevents#timeline',
|
# menuEvents: usersPath + '/tlevents#timeline',
|
||||||
menuNewswire: '#newswire',
|
menuNewswire: '#newswire',
|
||||||
menuLinks: '#links'
|
menuLinks: '#links'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue