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