mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of gitlab.com:bashrc2/epicyon
commit
dd4ae57fbc
22
daemon.py
22
daemon.py
|
@ -6221,9 +6221,29 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
os.remove(filterFilename)
|
||||
except OSError:
|
||||
print('EX: _profileUpdate ' +
|
||||
'unable to delete ' +
|
||||
'unable to delete filter ' +
|
||||
filterFilename)
|
||||
|
||||
# save filtered words within bio list
|
||||
filterBioFilename = \
|
||||
acctDir(baseDir, nickname, domain) + \
|
||||
'/filters_bio.txt'
|
||||
if fields.get('filteredWordsBio'):
|
||||
try:
|
||||
with open(filterBioFilename, 'w+') as filterfile:
|
||||
filterfile.write(fields['filteredWordsBio'])
|
||||
except OSError:
|
||||
print('EX: unable to write bio filter ' +
|
||||
filterBioFilename)
|
||||
else:
|
||||
if os.path.isfile(filterBioFilename):
|
||||
try:
|
||||
os.remove(filterBioFilename)
|
||||
except OSError:
|
||||
print('EX: _profileUpdate ' +
|
||||
'unable to delete bio filter ' +
|
||||
filterBioFilename)
|
||||
|
||||
# word replacements
|
||||
switchFilename = \
|
||||
acctDir(baseDir, nickname, domain) + \
|
||||
|
|
|
@ -324,15 +324,16 @@ def _speakerPicospeaker(pitch: int, rate: int, systemLanguage: str,
|
|||
"""TTS using picospeaker
|
||||
"""
|
||||
speakerLang = 'en-GB'
|
||||
if systemLanguage:
|
||||
if systemLanguage.startswith('fr'):
|
||||
speakerLang = 'fr-FR'
|
||||
elif systemLanguage.startswith('es'):
|
||||
speakerLang = 'es-ES'
|
||||
elif systemLanguage.startswith('de'):
|
||||
speakerLang = 'de-DE'
|
||||
elif systemLanguage.startswith('it'):
|
||||
speakerLang = 'it-IT'
|
||||
supportedLanguages = {
|
||||
"fr": "fr-FR",
|
||||
"es": "es-ES",
|
||||
"de": "de-DE",
|
||||
"it": "it-IT"
|
||||
}
|
||||
for lang, speakerStr in supportedLanguages.items():
|
||||
if systemLanguage.startswith(lang):
|
||||
speakerLang = speakerStr
|
||||
break
|
||||
sayText = str(sayText).replace('"', "'")
|
||||
speakerCmd = 'picospeaker ' + \
|
||||
'-l ' + speakerLang + \
|
||||
|
@ -386,8 +387,7 @@ def _textToSpeech(sayStr: str, screenreader: str,
|
|||
if screenreader == 'espeak':
|
||||
_speakerEspeak(espeak, pitch, rate, srange, sayStr)
|
||||
elif screenreader == 'picospeaker':
|
||||
_speakerPicospeaker(pitch, rate,
|
||||
systemLanguage, sayStr)
|
||||
_speakerPicospeaker(pitch, rate, systemLanguage, sayStr)
|
||||
|
||||
|
||||
def _sayCommand(content: str, sayStr: str, screenreader: str,
|
||||
|
@ -624,15 +624,16 @@ def _showLikesOnPost(postJsonObject: {}, maxLikes: int) -> None:
|
|||
return
|
||||
if not postJsonObject['object'].get('likes'):
|
||||
return
|
||||
if not isinstance(postJsonObject['object']['likes'], dict):
|
||||
objectLikes = postJsonObject['object']['likes']
|
||||
if not isinstance(objectLikes, dict):
|
||||
return
|
||||
if not postJsonObject['object']['likes'].get('items'):
|
||||
if not objectLikes.get('items'):
|
||||
return
|
||||
if not isinstance(postJsonObject['object']['likes']['items'], list):
|
||||
if not isinstance(objectLikes['items'], list):
|
||||
return
|
||||
print('')
|
||||
ctr = 0
|
||||
for item in postJsonObject['object']['likes']['items']:
|
||||
for item in objectLikes['items']:
|
||||
print(' ❤ ' + str(item['actor']))
|
||||
ctr += 1
|
||||
if ctr >= maxLikes:
|
||||
|
@ -646,15 +647,16 @@ def _showRepliesOnPost(postJsonObject: {}, maxReplies: int) -> None:
|
|||
return
|
||||
if not postJsonObject['object'].get('replies'):
|
||||
return
|
||||
if not isinstance(postJsonObject['object']['replies'], dict):
|
||||
objectReplies = postJsonObject['object']['replies']
|
||||
if not isinstance(objectReplies, dict):
|
||||
return
|
||||
if not postJsonObject['object']['replies'].get('items'):
|
||||
if not objectReplies.get('items'):
|
||||
return
|
||||
if not isinstance(postJsonObject['object']['replies']['items'], list):
|
||||
if not isinstance(objectReplies['items'], list):
|
||||
return
|
||||
print('')
|
||||
ctr = 0
|
||||
for item in postJsonObject['object']['replies']['items']:
|
||||
for item in objectReplies['items']:
|
||||
print(' ↰ ' + str(item['url']))
|
||||
ctr += 1
|
||||
if ctr >= maxReplies:
|
||||
|
@ -867,7 +869,7 @@ def _desktopShowProfile(session, nickname: str, domain: str,
|
|||
isHttp = True
|
||||
actorJson, asHeader = \
|
||||
getActorJson(domain, actor, isHttp, False, False, True,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, session)
|
||||
|
||||
_desktopShowActor(baseDir, actorJson, translate,
|
||||
systemLanguage, screenreader, espeak)
|
||||
|
@ -888,7 +890,7 @@ def _desktopShowProfileFromHandle(session, nickname: str, domain: str,
|
|||
"""
|
||||
actorJson, asHeader = \
|
||||
getActorJson(domain, handle, False, False, False, True,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, session)
|
||||
|
||||
_desktopShowActor(baseDir, actorJson, translate,
|
||||
systemLanguage, screenreader, espeak)
|
||||
|
|
|
@ -2086,7 +2086,7 @@ if args.actor:
|
|||
else:
|
||||
print('Did not obtain instance actor key for ' + domain)
|
||||
getActorJson(domain, args.actor, args.http, args.gnunet,
|
||||
debug, False, signingPrivateKeyPem)
|
||||
debug, False, signingPrivateKeyPem, None)
|
||||
sys.exit()
|
||||
|
||||
if args.followers:
|
||||
|
|
14
filters.py
14
filters.py
|
@ -144,6 +144,20 @@ def isFilteredGlobally(baseDir: str, content: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def isFilteredBio(baseDir: str, nickname: str, domain: str, bio: str) -> bool:
|
||||
"""Should the given actor bio be filtered out?
|
||||
"""
|
||||
if isFilteredGlobally(baseDir, bio):
|
||||
return True
|
||||
|
||||
if not nickname or not domain:
|
||||
return False
|
||||
|
||||
accountFiltersFilename = \
|
||||
acctDir(baseDir, nickname, domain) + '/filters_bio.txt'
|
||||
return _isFilteredBase(accountFiltersFilename, bio)
|
||||
|
||||
|
||||
def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
|
||||
"""Should the given content be filtered out?
|
||||
This is a simple type of filter which just matches words, not a regex
|
||||
|
|
252
follow.py
252
follow.py
|
@ -11,11 +11,9 @@ from pprint import pprint
|
|||
import os
|
||||
from utils import hasObjectStringObject
|
||||
from utils import hasObjectStringType
|
||||
from utils import hasActor
|
||||
from utils import removeDomainPort
|
||||
from utils import hasUsersPath
|
||||
from utils import getFullDomain
|
||||
from utils import isSystemAccount
|
||||
from utils import getFollowersList
|
||||
from utils import validNickname
|
||||
from utils import domainPermitted
|
||||
|
@ -31,7 +29,6 @@ from utils import isAccountDir
|
|||
from utils import getUserPaths
|
||||
from utils import acctDir
|
||||
from utils import hasGroupType
|
||||
from utils import isGroupAccount
|
||||
from utils import localActorUrl
|
||||
from acceptreject import createAccept
|
||||
from acceptreject import createReject
|
||||
|
@ -39,7 +36,6 @@ from webfinger import webfingerHandle
|
|||
from auth import createBasicAuthHeader
|
||||
from session import getJson
|
||||
from session import postJson
|
||||
from cache import getPersonPubKey
|
||||
|
||||
|
||||
def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None:
|
||||
|
@ -418,8 +414,8 @@ def _getNoOfFollows(baseDir: str, nickname: str, domain: str,
|
|||
return ctr
|
||||
|
||||
|
||||
def _getNoOfFollowers(baseDir: str,
|
||||
nickname: str, domain: str, authenticated: bool) -> int:
|
||||
def getNoOfFollowers(baseDir: str,
|
||||
nickname: str, domain: str, authenticated: bool) -> int:
|
||||
"""Returns the number of followers of the given person
|
||||
"""
|
||||
return _getNoOfFollows(baseDir, nickname, domain,
|
||||
|
@ -562,9 +558,9 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str,
|
|||
return following
|
||||
|
||||
|
||||
def _followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
||||
domainToFollow: str, debug: bool,
|
||||
followRequestHandle: str) -> bool:
|
||||
def followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
||||
domainToFollow: str, debug: bool,
|
||||
followRequestHandle: str) -> bool:
|
||||
""" Returns the policy for follower approvals
|
||||
"""
|
||||
# has this handle already been manually approved?
|
||||
|
@ -591,10 +587,10 @@ def _followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
|||
return manuallyApproveFollows
|
||||
|
||||
|
||||
def _noOfFollowRequests(baseDir: str,
|
||||
nicknameToFollow: str, domainToFollow: str,
|
||||
nickname: str, domain: str, fromPort: int,
|
||||
followType: str) -> int:
|
||||
def noOfFollowRequests(baseDir: str,
|
||||
nicknameToFollow: str, domainToFollow: str,
|
||||
nickname: str, domain: str, fromPort: int,
|
||||
followType: str) -> int:
|
||||
"""Returns the current number of follow requests
|
||||
"""
|
||||
accountsDir = baseDir + '/accounts/' + \
|
||||
|
@ -608,7 +604,7 @@ def _noOfFollowRequests(baseDir: str,
|
|||
with open(approveFollowsFilename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
except OSError:
|
||||
print('EX: _noOfFollowRequests ' + approveFollowsFilename)
|
||||
print('EX: noOfFollowRequests ' + approveFollowsFilename)
|
||||
if lines:
|
||||
if followType == "onion":
|
||||
for fileLine in lines:
|
||||
|
@ -623,12 +619,12 @@ def _noOfFollowRequests(baseDir: str,
|
|||
return ctr
|
||||
|
||||
|
||||
def _storeFollowRequest(baseDir: str,
|
||||
nicknameToFollow: str, domainToFollow: str, port: int,
|
||||
nickname: str, domain: str, fromPort: int,
|
||||
followJson: {},
|
||||
debug: bool, personUrl: str,
|
||||
groupAccount: bool) -> bool:
|
||||
def storeFollowRequest(baseDir: str,
|
||||
nicknameToFollow: str, domainToFollow: str, port: int,
|
||||
nickname: str, domain: str, fromPort: int,
|
||||
followJson: {},
|
||||
debug: bool, personUrl: str,
|
||||
groupAccount: bool) -> bool:
|
||||
"""Stores the follow request for later use
|
||||
"""
|
||||
accountsDir = baseDir + '/accounts/' + \
|
||||
|
@ -651,7 +647,7 @@ def _storeFollowRequest(baseDir: str,
|
|||
with open(followersFilename, 'r') as fpFollowers:
|
||||
followersStr = fpFollowers.read()
|
||||
except OSError:
|
||||
print('EX: _storeFollowRequest ' + followersFilename)
|
||||
print('EX: storeFollowRequest ' + followersFilename)
|
||||
|
||||
if approveHandle in followersStr:
|
||||
alreadyFollowing = True
|
||||
|
@ -696,7 +692,7 @@ def _storeFollowRequest(baseDir: str,
|
|||
with open(approveFollowsFilename, 'a+') as fp:
|
||||
fp.write(approveHandleStored + '\n')
|
||||
except OSError:
|
||||
print('EX: _storeFollowRequest 2 ' + approveFollowsFilename)
|
||||
print('EX: storeFollowRequest 2 ' + approveFollowsFilename)
|
||||
else:
|
||||
if debug:
|
||||
print('DEBUG: ' + approveHandleStored +
|
||||
|
@ -706,7 +702,7 @@ def _storeFollowRequest(baseDir: str,
|
|||
with open(approveFollowsFilename, 'w+') as fp:
|
||||
fp.write(approveHandleStored + '\n')
|
||||
except OSError:
|
||||
print('EX: _storeFollowRequest 3 ' + approveFollowsFilename)
|
||||
print('EX: storeFollowRequest 3 ' + approveFollowsFilename)
|
||||
|
||||
# store the follow request in its own directory
|
||||
# We don't rely upon the inbox because items in there could expire
|
||||
|
@ -717,212 +713,6 @@ def _storeFollowRequest(baseDir: str,
|
|||
return saveJson(followJson, followActivityfilename)
|
||||
|
||||
|
||||
def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||
port: int, sendThreads: [], postLog: [],
|
||||
cachedWebfingers: {}, personCache: {},
|
||||
messageJson: {}, federationList: [],
|
||||
debug: bool, projectVersion: str,
|
||||
maxFollowers: int, onionDomain: str,
|
||||
signingPrivateKeyPem: str) -> bool:
|
||||
"""Receives a follow request within the POST section of HTTPServer
|
||||
"""
|
||||
if not messageJson['type'].startswith('Follow'):
|
||||
if not messageJson['type'].startswith('Join'):
|
||||
return False
|
||||
print('Receiving follow request')
|
||||
if not hasActor(messageJson, debug):
|
||||
return False
|
||||
if not hasUsersPath(messageJson['actor']):
|
||||
if debug:
|
||||
print('DEBUG: users/profile/accounts/channel missing from actor')
|
||||
return False
|
||||
domain, tempPort = getDomainFromActor(messageJson['actor'])
|
||||
fromPort = port
|
||||
domainFull = getFullDomain(domain, tempPort)
|
||||
if tempPort:
|
||||
fromPort = tempPort
|
||||
if not domainPermitted(domain, federationList):
|
||||
if debug:
|
||||
print('DEBUG: follower from domain not permitted - ' + domain)
|
||||
return False
|
||||
nickname = getNicknameFromActor(messageJson['actor'])
|
||||
if not nickname:
|
||||
# single user instance
|
||||
nickname = 'dev'
|
||||
if debug:
|
||||
print('DEBUG: follow request does not contain a ' +
|
||||
'nickname. Assuming single user instance.')
|
||||
if not messageJson.get('to'):
|
||||
messageJson['to'] = messageJson['object']
|
||||
if not hasUsersPath(messageJson['object']):
|
||||
if debug:
|
||||
print('DEBUG: users/profile/channel/accounts ' +
|
||||
'not found within object')
|
||||
return False
|
||||
domainToFollow, tempPort = getDomainFromActor(messageJson['object'])
|
||||
if not domainPermitted(domainToFollow, federationList):
|
||||
if debug:
|
||||
print('DEBUG: follow domain not permitted ' + domainToFollow)
|
||||
return True
|
||||
domainToFollowFull = getFullDomain(domainToFollow, tempPort)
|
||||
nicknameToFollow = getNicknameFromActor(messageJson['object'])
|
||||
if not nicknameToFollow:
|
||||
if debug:
|
||||
print('DEBUG: follow request does not contain a ' +
|
||||
'nickname for the account followed')
|
||||
return True
|
||||
if isSystemAccount(nicknameToFollow):
|
||||
if debug:
|
||||
print('DEBUG: Cannot follow system account - ' +
|
||||
nicknameToFollow)
|
||||
return True
|
||||
if maxFollowers > 0:
|
||||
if _getNoOfFollowers(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
True) > maxFollowers:
|
||||
print('WARN: ' + nicknameToFollow +
|
||||
' has reached their maximum number of followers')
|
||||
return True
|
||||
handleToFollow = nicknameToFollow + '@' + domainToFollow
|
||||
if domainToFollow == domain:
|
||||
if not os.path.isdir(baseDir + '/accounts/' + handleToFollow):
|
||||
if debug:
|
||||
print('DEBUG: followed account not found - ' +
|
||||
baseDir + '/accounts/' + handleToFollow)
|
||||
return True
|
||||
|
||||
if isFollowerOfPerson(baseDir,
|
||||
nicknameToFollow, domainToFollowFull,
|
||||
nickname, domainFull):
|
||||
if debug:
|
||||
print('DEBUG: ' + nickname + '@' + domain +
|
||||
' is already a follower of ' +
|
||||
nicknameToFollow + '@' + domainToFollow)
|
||||
return True
|
||||
|
||||
# what is the followers policy?
|
||||
approveHandle = nickname + '@' + domainFull
|
||||
if _followApprovalRequired(baseDir, nicknameToFollow,
|
||||
domainToFollow, debug, approveHandle):
|
||||
print('Follow approval is required')
|
||||
if domain.endswith('.onion'):
|
||||
if _noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'onion') > 5:
|
||||
print('Too many follow requests from onion addresses')
|
||||
return False
|
||||
elif domain.endswith('.i2p'):
|
||||
if _noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'i2p') > 5:
|
||||
print('Too many follow requests from i2p addresses')
|
||||
return False
|
||||
else:
|
||||
if _noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'') > 10:
|
||||
print('Too many follow requests')
|
||||
return False
|
||||
|
||||
# Get the actor for the follower and add it to the cache.
|
||||
# Getting their public key has the same result
|
||||
if debug:
|
||||
print('Obtaining the following actor: ' + messageJson['actor'])
|
||||
if not getPersonPubKey(baseDir, session, messageJson['actor'],
|
||||
personCache, debug, projectVersion,
|
||||
httpPrefix, domainToFollow, onionDomain,
|
||||
signingPrivateKeyPem):
|
||||
if debug:
|
||||
print('Unable to obtain following actor: ' +
|
||||
messageJson['actor'])
|
||||
|
||||
groupAccount = \
|
||||
hasGroupType(baseDir, messageJson['actor'], personCache)
|
||||
if groupAccount and isGroupAccount(baseDir, nickname, domain):
|
||||
print('Group cannot follow a group')
|
||||
return False
|
||||
|
||||
print('Storing follow request for approval')
|
||||
return _storeFollowRequest(baseDir,
|
||||
nicknameToFollow, domainToFollow, port,
|
||||
nickname, domain, fromPort,
|
||||
messageJson, debug, messageJson['actor'],
|
||||
groupAccount)
|
||||
else:
|
||||
print('Follow request does not require approval ' + approveHandle)
|
||||
# update the followers
|
||||
accountToBeFollowed = \
|
||||
acctDir(baseDir, nicknameToFollow, domainToFollow)
|
||||
if os.path.isdir(accountToBeFollowed):
|
||||
followersFilename = accountToBeFollowed + '/followers.txt'
|
||||
|
||||
# for actors which don't follow the mastodon
|
||||
# /users/ path convention store the full actor
|
||||
if '/users/' not in messageJson['actor']:
|
||||
approveHandle = messageJson['actor']
|
||||
|
||||
# Get the actor for the follower and add it to the cache.
|
||||
# Getting their public key has the same result
|
||||
if debug:
|
||||
print('Obtaining the following actor: ' + messageJson['actor'])
|
||||
if not getPersonPubKey(baseDir, session, messageJson['actor'],
|
||||
personCache, debug, projectVersion,
|
||||
httpPrefix, domainToFollow, onionDomain,
|
||||
signingPrivateKeyPem):
|
||||
if debug:
|
||||
print('Unable to obtain following actor: ' +
|
||||
messageJson['actor'])
|
||||
|
||||
print('Updating followers file: ' +
|
||||
followersFilename + ' adding ' + approveHandle)
|
||||
if os.path.isfile(followersFilename):
|
||||
if approveHandle not in open(followersFilename).read():
|
||||
groupAccount = \
|
||||
hasGroupType(baseDir,
|
||||
messageJson['actor'], personCache)
|
||||
if debug:
|
||||
print(approveHandle + ' / ' + messageJson['actor'] +
|
||||
' is Group: ' + str(groupAccount))
|
||||
if groupAccount and \
|
||||
isGroupAccount(baseDir, nickname, domain):
|
||||
print('Group cannot follow a group')
|
||||
return False
|
||||
try:
|
||||
with open(followersFilename, 'r+') as followersFile:
|
||||
content = followersFile.read()
|
||||
if approveHandle + '\n' not in content:
|
||||
followersFile.seek(0, 0)
|
||||
if not groupAccount:
|
||||
followersFile.write(approveHandle +
|
||||
'\n' + content)
|
||||
else:
|
||||
followersFile.write('!' + approveHandle +
|
||||
'\n' + content)
|
||||
except Exception as e:
|
||||
print('WARN: ' +
|
||||
'Failed to write entry to followers file ' +
|
||||
str(e))
|
||||
else:
|
||||
try:
|
||||
with open(followersFilename, 'w+') as followersFile:
|
||||
followersFile.write(approveHandle + '\n')
|
||||
except OSError:
|
||||
print('EX: unable to write ' + followersFilename)
|
||||
|
||||
print('Beginning follow accept')
|
||||
return followedAccountAccepts(session, baseDir, httpPrefix,
|
||||
nicknameToFollow, domainToFollow, port,
|
||||
nickname, domain, fromPort,
|
||||
messageJson['actor'], federationList,
|
||||
messageJson, sendThreads, postLog,
|
||||
cachedWebfingers, personCache,
|
||||
debug, projectVersion, True,
|
||||
signingPrivateKeyPem)
|
||||
|
||||
|
||||
def followedAccountAccepts(session, baseDir: str, httpPrefix: str,
|
||||
nicknameToFollow: str, domainToFollow: str,
|
||||
port: int,
|
||||
|
@ -1124,8 +914,8 @@ def sendFollowRequest(session, baseDir: str,
|
|||
newFollowJson['to'] = followedId
|
||||
print('Follow request: ' + str(newFollowJson))
|
||||
|
||||
if _followApprovalRequired(baseDir, nickname, domain, debug,
|
||||
followHandle):
|
||||
if followApprovalRequired(baseDir, nickname, domain, debug,
|
||||
followHandle):
|
||||
# Remove any follow requests rejected for the account being followed.
|
||||
# It's assumed that if you are following someone then you are
|
||||
# ok with them following back. If this isn't the case then a rejected
|
||||
|
|
255
inbox.py
255
inbox.py
|
@ -17,6 +17,9 @@ from languages import understoodPostLanguage
|
|||
from like import updateLikesCollection
|
||||
from reaction import updateReactionCollection
|
||||
from reaction import validEmojiContent
|
||||
from utils import domainPermitted
|
||||
from utils import isGroupAccount
|
||||
from utils import isSystemAccount
|
||||
from utils import invalidCiphertext
|
||||
from utils import removeHtml
|
||||
from utils import fileLastModified
|
||||
|
@ -65,9 +68,14 @@ from httpsig import verifyPostHeaders
|
|||
from session import createSession
|
||||
from follow import followerApprovalActive
|
||||
from follow import isFollowingActor
|
||||
from follow import receiveFollowRequest
|
||||
from follow import getFollowersOfActor
|
||||
from follow import unfollowerOfAccount
|
||||
from follow import isFollowerOfPerson
|
||||
from follow import followedAccountAccepts
|
||||
from follow import storeFollowRequest
|
||||
from follow import noOfFollowRequests
|
||||
from follow import getNoOfFollowers
|
||||
from follow import followApprovalRequired
|
||||
from pprint import pprint
|
||||
from cache import storePersonInCache
|
||||
from cache import getPersonPubKey
|
||||
|
@ -113,6 +121,7 @@ from notifyOnPost import notifyWhenPersonPosts
|
|||
from conversation import updateConversation
|
||||
from content import validHashTag
|
||||
from webapp_hashtagswarm import htmlHashTagSwarm
|
||||
from person import validSendingActor
|
||||
|
||||
|
||||
def _storeLastPostId(baseDir: str, nickname: str, domain: str,
|
||||
|
@ -2184,7 +2193,9 @@ def _validPostContent(baseDir: str, nickname: str, domain: str,
|
|||
"""Is the content of a received post valid?
|
||||
Check for bad html
|
||||
Check for hellthreads
|
||||
Check number of tags is reasonable
|
||||
Check that the language is understood
|
||||
Check if it's a git patch
|
||||
Check number of tags and mentions is reasonable
|
||||
"""
|
||||
if not hasObjectDict(messageJson):
|
||||
return True
|
||||
|
@ -3406,6 +3417,11 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
allowLocalNetworkAccess, debug,
|
||||
systemLanguage, httpPrefix,
|
||||
domainFull, personCache):
|
||||
# is the sending actor valid?
|
||||
if not validSendingActor(session, baseDir, nickname, domain,
|
||||
personCache, postJsonObject,
|
||||
signingPrivateKeyPem, debug, unitTest):
|
||||
return False
|
||||
|
||||
if postJsonObject.get('object'):
|
||||
jsonObj = postJsonObject['object']
|
||||
|
@ -3826,6 +3842,221 @@ def _checkJsonSignature(baseDir: str, queueJson: {}) -> (bool, bool):
|
|||
return hasJsonSignature, jwebsigType
|
||||
|
||||
|
||||
def _receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||
port: int, sendThreads: [], postLog: [],
|
||||
cachedWebfingers: {}, personCache: {},
|
||||
messageJson: {}, federationList: [],
|
||||
debug: bool, projectVersion: str,
|
||||
maxFollowers: int, onionDomain: str,
|
||||
signingPrivateKeyPem: str, unitTest: bool) -> bool:
|
||||
"""Receives a follow request within the POST section of HTTPServer
|
||||
"""
|
||||
if not messageJson['type'].startswith('Follow'):
|
||||
if not messageJson['type'].startswith('Join'):
|
||||
return False
|
||||
print('Receiving follow request')
|
||||
if not hasActor(messageJson, debug):
|
||||
return False
|
||||
if not hasUsersPath(messageJson['actor']):
|
||||
if debug:
|
||||
print('DEBUG: users/profile/accounts/channel missing from actor')
|
||||
return False
|
||||
domain, tempPort = getDomainFromActor(messageJson['actor'])
|
||||
fromPort = port
|
||||
domainFull = getFullDomain(domain, tempPort)
|
||||
if tempPort:
|
||||
fromPort = tempPort
|
||||
if not domainPermitted(domain, federationList):
|
||||
if debug:
|
||||
print('DEBUG: follower from domain not permitted - ' + domain)
|
||||
return False
|
||||
nickname = getNicknameFromActor(messageJson['actor'])
|
||||
if not nickname:
|
||||
# single user instance
|
||||
nickname = 'dev'
|
||||
if debug:
|
||||
print('DEBUG: follow request does not contain a ' +
|
||||
'nickname. Assuming single user instance.')
|
||||
if not messageJson.get('to'):
|
||||
messageJson['to'] = messageJson['object']
|
||||
if not hasUsersPath(messageJson['object']):
|
||||
if debug:
|
||||
print('DEBUG: users/profile/channel/accounts ' +
|
||||
'not found within object')
|
||||
return False
|
||||
domainToFollow, tempPort = getDomainFromActor(messageJson['object'])
|
||||
if not domainPermitted(domainToFollow, federationList):
|
||||
if debug:
|
||||
print('DEBUG: follow domain not permitted ' + domainToFollow)
|
||||
return True
|
||||
domainToFollowFull = getFullDomain(domainToFollow, tempPort)
|
||||
nicknameToFollow = getNicknameFromActor(messageJson['object'])
|
||||
if not nicknameToFollow:
|
||||
if debug:
|
||||
print('DEBUG: follow request does not contain a ' +
|
||||
'nickname for the account followed')
|
||||
return True
|
||||
if isSystemAccount(nicknameToFollow):
|
||||
if debug:
|
||||
print('DEBUG: Cannot follow system account - ' +
|
||||
nicknameToFollow)
|
||||
return True
|
||||
if maxFollowers > 0:
|
||||
if getNoOfFollowers(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
True) > maxFollowers:
|
||||
print('WARN: ' + nicknameToFollow +
|
||||
' has reached their maximum number of followers')
|
||||
return True
|
||||
handleToFollow = nicknameToFollow + '@' + domainToFollow
|
||||
if domainToFollow == domain:
|
||||
if not os.path.isdir(baseDir + '/accounts/' + handleToFollow):
|
||||
if debug:
|
||||
print('DEBUG: followed account not found - ' +
|
||||
baseDir + '/accounts/' + handleToFollow)
|
||||
return True
|
||||
|
||||
if isFollowerOfPerson(baseDir,
|
||||
nicknameToFollow, domainToFollowFull,
|
||||
nickname, domainFull):
|
||||
if debug:
|
||||
print('DEBUG: ' + nickname + '@' + domain +
|
||||
' is already a follower of ' +
|
||||
nicknameToFollow + '@' + domainToFollow)
|
||||
return True
|
||||
|
||||
approveHandle = nickname + '@' + domainFull
|
||||
|
||||
# is the actor sending the request valid?
|
||||
if not validSendingActor(session, baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
personCache, messageJson,
|
||||
signingPrivateKeyPem, debug, unitTest):
|
||||
print('REJECT spam follow request ' + approveHandle)
|
||||
return False
|
||||
|
||||
# what is the followers policy?
|
||||
if followApprovalRequired(baseDir, nicknameToFollow,
|
||||
domainToFollow, debug, approveHandle):
|
||||
print('Follow approval is required')
|
||||
if domain.endswith('.onion'):
|
||||
if noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'onion') > 5:
|
||||
print('Too many follow requests from onion addresses')
|
||||
return False
|
||||
elif domain.endswith('.i2p'):
|
||||
if noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'i2p') > 5:
|
||||
print('Too many follow requests from i2p addresses')
|
||||
return False
|
||||
else:
|
||||
if noOfFollowRequests(baseDir,
|
||||
nicknameToFollow, domainToFollow,
|
||||
nickname, domain, fromPort,
|
||||
'') > 10:
|
||||
print('Too many follow requests')
|
||||
return False
|
||||
|
||||
# Get the actor for the follower and add it to the cache.
|
||||
# Getting their public key has the same result
|
||||
if debug:
|
||||
print('Obtaining the following actor: ' + messageJson['actor'])
|
||||
if not getPersonPubKey(baseDir, session, messageJson['actor'],
|
||||
personCache, debug, projectVersion,
|
||||
httpPrefix, domainToFollow, onionDomain,
|
||||
signingPrivateKeyPem):
|
||||
if debug:
|
||||
print('Unable to obtain following actor: ' +
|
||||
messageJson['actor'])
|
||||
|
||||
groupAccount = \
|
||||
hasGroupType(baseDir, messageJson['actor'], personCache)
|
||||
if groupAccount and isGroupAccount(baseDir, nickname, domain):
|
||||
print('Group cannot follow a group')
|
||||
return False
|
||||
|
||||
print('Storing follow request for approval')
|
||||
return storeFollowRequest(baseDir,
|
||||
nicknameToFollow, domainToFollow, port,
|
||||
nickname, domain, fromPort,
|
||||
messageJson, debug, messageJson['actor'],
|
||||
groupAccount)
|
||||
else:
|
||||
print('Follow request does not require approval ' + approveHandle)
|
||||
# update the followers
|
||||
accountToBeFollowed = \
|
||||
acctDir(baseDir, nicknameToFollow, domainToFollow)
|
||||
if os.path.isdir(accountToBeFollowed):
|
||||
followersFilename = accountToBeFollowed + '/followers.txt'
|
||||
|
||||
# for actors which don't follow the mastodon
|
||||
# /users/ path convention store the full actor
|
||||
if '/users/' not in messageJson['actor']:
|
||||
approveHandle = messageJson['actor']
|
||||
|
||||
# Get the actor for the follower and add it to the cache.
|
||||
# Getting their public key has the same result
|
||||
if debug:
|
||||
print('Obtaining the following actor: ' + messageJson['actor'])
|
||||
if not getPersonPubKey(baseDir, session, messageJson['actor'],
|
||||
personCache, debug, projectVersion,
|
||||
httpPrefix, domainToFollow, onionDomain,
|
||||
signingPrivateKeyPem):
|
||||
if debug:
|
||||
print('Unable to obtain following actor: ' +
|
||||
messageJson['actor'])
|
||||
|
||||
print('Updating followers file: ' +
|
||||
followersFilename + ' adding ' + approveHandle)
|
||||
if os.path.isfile(followersFilename):
|
||||
if approveHandle not in open(followersFilename).read():
|
||||
groupAccount = \
|
||||
hasGroupType(baseDir,
|
||||
messageJson['actor'], personCache)
|
||||
if debug:
|
||||
print(approveHandle + ' / ' + messageJson['actor'] +
|
||||
' is Group: ' + str(groupAccount))
|
||||
if groupAccount and \
|
||||
isGroupAccount(baseDir, nickname, domain):
|
||||
print('Group cannot follow a group')
|
||||
return False
|
||||
try:
|
||||
with open(followersFilename, 'r+') as followersFile:
|
||||
content = followersFile.read()
|
||||
if approveHandle + '\n' not in content:
|
||||
followersFile.seek(0, 0)
|
||||
if not groupAccount:
|
||||
followersFile.write(approveHandle +
|
||||
'\n' + content)
|
||||
else:
|
||||
followersFile.write('!' + approveHandle +
|
||||
'\n' + content)
|
||||
except Exception as e:
|
||||
print('WARN: ' +
|
||||
'Failed to write entry to followers file ' +
|
||||
str(e))
|
||||
else:
|
||||
try:
|
||||
with open(followersFilename, 'w+') as followersFile:
|
||||
followersFile.write(approveHandle + '\n')
|
||||
except OSError:
|
||||
print('EX: unable to write ' + followersFilename)
|
||||
|
||||
print('Beginning follow accept')
|
||||
return followedAccountAccepts(session, baseDir, httpPrefix,
|
||||
nicknameToFollow, domainToFollow, port,
|
||||
nickname, domain, fromPort,
|
||||
messageJson['actor'], federationList,
|
||||
messageJson, sendThreads, postLog,
|
||||
cachedWebfingers, personCache,
|
||||
debug, projectVersion, True,
|
||||
signingPrivateKeyPem)
|
||||
|
||||
|
||||
def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||
projectVersion: str,
|
||||
baseDir: str, httpPrefix: str, sendThreads: [], postLog: [],
|
||||
|
@ -4126,16 +4357,16 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
|
||||
if debug:
|
||||
print('DEBUG: checking for follow requests')
|
||||
if receiveFollowRequest(session,
|
||||
baseDir, httpPrefix, port,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache,
|
||||
queueJson['post'],
|
||||
federationList,
|
||||
debug, projectVersion,
|
||||
maxFollowers, onionDomain,
|
||||
signingPrivateKeyPem):
|
||||
if _receiveFollowRequest(session,
|
||||
baseDir, httpPrefix, port,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache,
|
||||
queueJson['post'],
|
||||
federationList,
|
||||
debug, projectVersion,
|
||||
maxFollowers, onionDomain,
|
||||
signingPrivateKeyPem, unitTest):
|
||||
if os.path.isfile(queueFilename):
|
||||
try:
|
||||
os.remove(queueFilename)
|
||||
|
|
|
@ -86,7 +86,7 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
|
|||
gnunet = True
|
||||
personJson = \
|
||||
getActorJson(domain, personUrl, httpPrefix, gnunet, debug, False,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, None)
|
||||
if not personJson:
|
||||
return ctr
|
||||
if not personJson.get('movedTo'):
|
||||
|
|
118
person.py
118
person.py
|
@ -38,6 +38,8 @@ from roles import setRole
|
|||
from roles import setRolesFromList
|
||||
from roles import getActorRolesList
|
||||
from media import processMetaData
|
||||
from utils import removeHtml
|
||||
from utils import containsInvalidChars
|
||||
from utils import replaceUsersWithAt
|
||||
from utils import removeLineEndings
|
||||
from utils import removeDomainPort
|
||||
|
@ -63,6 +65,8 @@ from session import getJson
|
|||
from webfinger import webfingerHandle
|
||||
from pprint import pprint
|
||||
from cache import getPersonFromCache
|
||||
from cache import storePersonInCache
|
||||
from filters import isFilteredBio
|
||||
|
||||
|
||||
def generateRSAKey() -> (str, str):
|
||||
|
@ -1415,7 +1419,8 @@ def _detectUsersPath(url: str) -> str:
|
|||
|
||||
def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
|
||||
debug: bool, quiet: bool,
|
||||
signingPrivateKeyPem: str) -> ({}, {}):
|
||||
signingPrivateKeyPem: str,
|
||||
existingSession) -> ({}, {}):
|
||||
"""Returns the actor json
|
||||
"""
|
||||
if debug:
|
||||
|
@ -1498,7 +1503,10 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
|
|||
httpPrefix = 'https'
|
||||
else:
|
||||
httpPrefix = 'http'
|
||||
session = createSession(proxyType)
|
||||
if existingSession:
|
||||
session = existingSession
|
||||
else:
|
||||
session = createSession(proxyType)
|
||||
if nickname == 'inbox':
|
||||
nickname = domain
|
||||
|
||||
|
@ -1634,3 +1642,109 @@ def addActorUpdateTimestamp(actorJson: {}) -> None:
|
|||
# add updated timestamp to avatar and banner
|
||||
actorJson['icon']['updated'] = currDateStr
|
||||
actorJson['image']['updated'] = currDateStr
|
||||
|
||||
|
||||
def validSendingActor(session, baseDir: str,
|
||||
nickname: str, domain: str,
|
||||
personCache: {},
|
||||
postJsonObject: {},
|
||||
signingPrivateKeyPem: str,
|
||||
debug: bool, unitTest: bool) -> bool:
|
||||
"""When a post arrives in the inbox this is used to check that
|
||||
the sending actor is valid
|
||||
"""
|
||||
# who sent this post?
|
||||
sendingActor = postJsonObject['actor']
|
||||
|
||||
# sending to yourself (reminder)
|
||||
if sendingActor.endswith(domain + '/users/' + nickname):
|
||||
return True
|
||||
|
||||
# get their actor
|
||||
actorJson = getPersonFromCache(baseDir, sendingActor, personCache, True)
|
||||
downloadedActor = False
|
||||
if not actorJson:
|
||||
# download the actor
|
||||
actorJson, _ = getActorJson(domain, sendingActor,
|
||||
True, False, debug, True,
|
||||
signingPrivateKeyPem, session)
|
||||
if actorJson:
|
||||
downloadedActor = True
|
||||
if not actorJson:
|
||||
# if the actor couldn't be obtained then proceed anyway
|
||||
return True
|
||||
if not actorJson.get('preferredUsername'):
|
||||
print('REJECT: no preferredUsername within actor ' + str(actorJson))
|
||||
return False
|
||||
# does the actor have a bio ?
|
||||
if not unitTest:
|
||||
if not actorJson.get('summary'):
|
||||
# allow no bio if it's an actor in this instance
|
||||
if domain not in sendingActor:
|
||||
# probably a spam actor with no bio
|
||||
print('REJECT: spam actor ' + sendingActor)
|
||||
return False
|
||||
bioStr = removeHtml(actorJson['summary'])
|
||||
bioStr += ' ' + removeHtml(actorJson['preferredUsername'])
|
||||
|
||||
if actorJson.get('attachment'):
|
||||
if isinstance(actorJson['attachment'], list):
|
||||
for tag in actorJson['attachment']:
|
||||
if not isinstance(tag, dict):
|
||||
continue
|
||||
if not tag.get('name'):
|
||||
continue
|
||||
if isinstance(tag['name'], str):
|
||||
bioStr += ' ' + tag['name']
|
||||
if tag.get('value'):
|
||||
continue
|
||||
if isinstance(tag['value'], str):
|
||||
bioStr += ' ' + tag['value']
|
||||
|
||||
if actorJson.get('name'):
|
||||
bioStr += ' ' + removeHtml(actorJson['name'])
|
||||
if containsInvalidChars(bioStr):
|
||||
print('REJECT: post actor bio contains invalid characters')
|
||||
return False
|
||||
if isFilteredBio(baseDir, nickname, domain, bioStr):
|
||||
print('REJECT: post actor bio contains filtered text')
|
||||
return False
|
||||
else:
|
||||
print('Skipping check for missing bio in ' + sendingActor)
|
||||
|
||||
# Check any attached fields for the actor.
|
||||
# Spam actors will sometimes have attached fields which are all empty
|
||||
if actorJson.get('attachment'):
|
||||
if isinstance(actorJson['attachment'], list):
|
||||
noOfTags = 0
|
||||
tagsWithoutValue = 0
|
||||
for tag in actorJson['attachment']:
|
||||
if not isinstance(tag, dict):
|
||||
continue
|
||||
if not tag.get('name'):
|
||||
continue
|
||||
noOfTags += 1
|
||||
if not tag.get('value'):
|
||||
tagsWithoutValue += 1
|
||||
continue
|
||||
if not isinstance(tag['value'], str):
|
||||
tagsWithoutValue += 1
|
||||
continue
|
||||
if not tag['value'].strip():
|
||||
tagsWithoutValue += 1
|
||||
continue
|
||||
if len(tag['value']) < 2:
|
||||
tagsWithoutValue += 1
|
||||
continue
|
||||
if noOfTags > 0:
|
||||
if int(tagsWithoutValue * 100 / noOfTags) > 50:
|
||||
print('REJECT: actor has empty attachments ' +
|
||||
sendingActor)
|
||||
return False
|
||||
|
||||
if downloadedActor:
|
||||
# if the actor is valid and was downloaded then
|
||||
# store it in the cache, but don't write it to file
|
||||
storePersonInCache(baseDir, sendingActor, actorJson, personCache,
|
||||
False)
|
||||
return True
|
||||
|
|
4
pgp.py
4
pgp.py
|
@ -343,7 +343,7 @@ def _getPGPPublicKeyFromActor(signingPrivateKeyPem: str,
|
|||
if not actorJson:
|
||||
actorJson, asHeader = \
|
||||
getActorJson(domain, handle, False, False, False, True,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, None)
|
||||
if not actorJson:
|
||||
return None
|
||||
if not actorJson.get('attachment'):
|
||||
|
@ -491,7 +491,7 @@ def pgpPublicKeyUpload(baseDir: str, session,
|
|||
|
||||
actorJson, asHeader = \
|
||||
getActorJson(domainFull, handle, False, False, debug, True,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, session)
|
||||
if not actorJson:
|
||||
if debug:
|
||||
print('No actor returned for ' + handle)
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "عنوان الارتباط الجديد وعنوان URL",
|
||||
"Theme Designer": "مصمم المظهر",
|
||||
"Reset": "إعادة ضبط",
|
||||
"Encryption Keys": "مفاتيح التشفير"
|
||||
"Encryption Keys": "مفاتيح التشفير",
|
||||
"Filtered words within bio": "كلمات مفلترة داخل السيرة الذاتية"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Títol i URL de l'enllaç nous",
|
||||
"Theme Designer": "Dissenyador temàtic",
|
||||
"Reset": "Restableix",
|
||||
"Encryption Keys": "Claus de xifratge"
|
||||
"Encryption Keys": "Claus de xifratge",
|
||||
"Filtered words within bio": "Paraules filtrades dins de la biografia"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Teitl dolen ac URL newydd",
|
||||
"Theme Designer": "Dylunydd Thema",
|
||||
"Reset": "Ail gychwyn",
|
||||
"Encryption Keys": "Allweddi Amgryptio"
|
||||
"Encryption Keys": "Allweddi Amgryptio",
|
||||
"Filtered words within bio": "Geiriau wedi'u hidlo o fewn cofiant"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Neuer Linktitel und URL",
|
||||
"Theme Designer": "Themendesigner",
|
||||
"Reset": "Zurücksetzen",
|
||||
"Encryption Keys": "Verschlüsselungsschlüssel"
|
||||
"Encryption Keys": "Verschlüsselungsschlüssel",
|
||||
"Filtered words within bio": "Gefilterte Wörter in der Biografie"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "New link title and URL",
|
||||
"Theme Designer": "Theme Designer",
|
||||
"Reset": "Reset",
|
||||
"Encryption Keys": "Encryption Keys"
|
||||
"Encryption Keys": "Encryption Keys",
|
||||
"Filtered words within bio": "Filtered words within bio"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Nuevo título de enlace y URL",
|
||||
"Theme Designer": "Diseñadora de temas",
|
||||
"Reset": "Reiniciar",
|
||||
"Encryption Keys": "Claves de cifrado"
|
||||
"Encryption Keys": "Claves de cifrado",
|
||||
"Filtered words within bio": "Palabras filtradas dentro de la biografía"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Nouveau titre et URL du lien",
|
||||
"Theme Designer": "Concepteur de thème",
|
||||
"Reset": "Réinitialiser",
|
||||
"Encryption Keys": "Clés de cryptage"
|
||||
"Encryption Keys": "Clés de cryptage",
|
||||
"Filtered words within bio": "Mots filtrés dans la biographie"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Teideal nasc nua agus URL",
|
||||
"Theme Designer": "Dearthóir Téama",
|
||||
"Reset": "Athshocraigh",
|
||||
"Encryption Keys": "Eochracha Criptithe"
|
||||
"Encryption Keys": "Eochracha Criptithe",
|
||||
"Filtered words within bio": "Focail scagtha laistigh den bheathaisnéis"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "नया लिंक शीर्षक और URL",
|
||||
"Theme Designer": "थीम डिजाइनर",
|
||||
"Reset": "रीसेट",
|
||||
"Encryption Keys": "एन्क्रिप्शन कुंजी"
|
||||
"Encryption Keys": "एन्क्रिप्शन कुंजी",
|
||||
"Filtered words within bio": "जीवनी के भीतर फ़िल्टर किए गए शब्द"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Nuovo titolo e URL del collegamento",
|
||||
"Theme Designer": "Progettista di temi",
|
||||
"Reset": "Ripristina",
|
||||
"Encryption Keys": "Chiavi di crittografia"
|
||||
"Encryption Keys": "Chiavi di crittografia",
|
||||
"Filtered words within bio": "Parole filtrate all'interno della biografia"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "新しいリンクのタイトルとURL",
|
||||
"Theme Designer": "テーマデザイナー",
|
||||
"Reset": "リセット",
|
||||
"Encryption Keys": "暗号化キー"
|
||||
"Encryption Keys": "暗号化キー",
|
||||
"Filtered words within bio": "伝記内のフィルタリングされた単語"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Sernav û URL-ya girêdana nû",
|
||||
"Theme Designer": "Theme Designer",
|
||||
"Reset": "Reset",
|
||||
"Encryption Keys": "Bişkojkên Şîfrekirinê"
|
||||
"Encryption Keys": "Bişkojkên Şîfrekirinê",
|
||||
"Filtered words within bio": "Peyvên fîlterkirî di hundurê biyografiyê de"
|
||||
}
|
||||
|
|
|
@ -497,5 +497,6 @@
|
|||
"New link title and URL": "New link title and URL",
|
||||
"Theme Designer": "Theme Designer",
|
||||
"Reset": "Reset",
|
||||
"Encryption Keys": "Encryption Keys"
|
||||
"Encryption Keys": "Encryption Keys",
|
||||
"Filtered words within bio": "Filtered words within bio"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Novo título e URL do link",
|
||||
"Theme Designer": "Designer de Tema",
|
||||
"Reset": "Redefinir",
|
||||
"Encryption Keys": "Chaves de criptografia"
|
||||
"Encryption Keys": "Chaves de criptografia",
|
||||
"Filtered words within bio": "Palavras filtradas na biografia"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Новое название ссылки и URL",
|
||||
"Theme Designer": "Дизайнер тем",
|
||||
"Reset": "Сброс настроек",
|
||||
"Encryption Keys": "Ключи шифрования"
|
||||
"Encryption Keys": "Ключи шифрования",
|
||||
"Filtered words within bio": "Отфильтрованные слова в биографии"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "Kichwa kipya cha kiungo na URL",
|
||||
"Theme Designer": "Mbuni wa Mandhari",
|
||||
"Reset": "Weka upya",
|
||||
"Encryption Keys": "Vifunguo vya Usimbaji"
|
||||
"Encryption Keys": "Vifunguo vya Usimbaji",
|
||||
"Filtered words within bio": "Maneno yaliyochujwa ndani ya wasifu"
|
||||
}
|
||||
|
|
|
@ -501,5 +501,6 @@
|
|||
"New link title and URL": "新链接标题和 URL",
|
||||
"Theme Designer": "主题设计师",
|
||||
"Reset": "重启",
|
||||
"Encryption Keys": "加密密钥"
|
||||
"Encryption Keys": "加密密钥",
|
||||
"Filtered words within bio": "传记中的过滤词"
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ def htmlProfileAfterSearch(cssCache: {},
|
|||
gnunet = True
|
||||
profileJson, asHeader = \
|
||||
getActorJson(domain, profileHandle, http, gnunet, debug, False,
|
||||
signingPrivateKeyPem)
|
||||
signingPrivateKeyPem, session)
|
||||
if not profileJson:
|
||||
return None
|
||||
|
||||
|
@ -1604,6 +1604,13 @@ def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
|
|||
with open(filterFilename, 'r') as filterfile:
|
||||
filterStr = filterfile.read()
|
||||
|
||||
filterBioStr = ''
|
||||
filterBioFilename = \
|
||||
acctDir(baseDir, nickname, domain) + '/filters_bio.txt'
|
||||
if os.path.isfile(filterBioFilename):
|
||||
with open(filterBioFilename, 'r') as filterfile:
|
||||
filterBioStr = filterfile.read()
|
||||
|
||||
switchStr = ''
|
||||
switchFilename = \
|
||||
acctDir(baseDir, nickname, domain) + '/replacewords.txt'
|
||||
|
@ -1700,6 +1707,13 @@ def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
|
|||
'name="filteredWords" style="height:200px" spellcheck="false">' + \
|
||||
filterStr + '</textarea>\n' + \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['Filtered words within bio'] + '</label></b>\n' + \
|
||||
' <br><label class="labels">' + \
|
||||
translate['One per line'] + '</label>\n' + \
|
||||
' <textarea id="message" ' + \
|
||||
'name="filteredWordsBio" style="height:200px" spellcheck="false">' + \
|
||||
filterBioStr + '</textarea>\n' + \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['Word Replacements'] + '</label></b>\n' + \
|
||||
' <br><label class="labels">A -> B</label>\n' + \
|
||||
' <textarea id="message" name="switchWords" ' + \
|
||||
|
|
Loading…
Reference in New Issue