Implement reply interval in hours

The time after publication of a post during which replies are permitted
main
Bob Mottram 2021-09-08 19:37:04 +01:00
parent f84149cd3a
commit 9601220e66
22 changed files with 199 additions and 32 deletions

View File

@ -232,6 +232,7 @@ from categories import updateHashtagCategories
from languages import getActorLanguages
from languages import setActorLanguages
from like import updateLikesCollection
from utils import canReplyTo
from utils import isDM
from utils import replaceUsersWithAt
from utils import localActorUrl
@ -857,6 +858,14 @@ class PubServer(BaseHTTPRequestHandler):
'This is nothing less ' +
'than an utter triumph')
def _403(self) -> None:
if self.server.translate:
self._httpReturnCode(403, self.server.translate['Forbidden'],
self.server.translate["You're not allowed"])
else:
self._httpReturnCode(403, 'Forbidden',
"You're not allowed")
def _404(self) -> None:
if self.server.translate:
self._httpReturnCode(404, self.server.translate['Not Found'],
@ -11287,6 +11296,18 @@ class PubServer(BaseHTTPRequestHandler):
if isNewPostEndpoint:
nickname = getNicknameFromActor(path)
if inReplyToUrl:
replyIntervalHours = self.server.defaultReplyIntervalHours
if not canReplyTo(baseDir, nickname, domain,
inReplyToUrl, replyIntervalHours):
print('Reply outside of time window ' + inReplyToUrl)
self._403()
self.server.GETbusy = False
return True
elif self.server.debug:
print('Reply is within time interval: ' +
str(replyIntervalHours) + ' hours')
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
@ -16245,7 +16266,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
break
def runDaemon(lowBandwidth: bool,
def runDaemon(defaultReplyIntervalHours: int,
lowBandwidth: bool,
maxLikeCount: int,
sharedItemsFederatedDomains: [],
userAgentsBlocked: [],
@ -16376,6 +16398,11 @@ def runDaemon(lowBandwidth: bool,
'Public': 'p',
'Reminder': 'r'
}
# how many hours after a post was publushed can a reply be made
defaultReplyIntervalHours = 9999999
httpd.defaultReplyIntervalHours = defaultReplyIntervalHours
httpd.keyShortcuts = {}
loadAccessKeysForAccounts(baseDir, httpd.keyShortcuts, httpd.accessKeys)
@ -16704,7 +16731,8 @@ def runDaemon(lowBandwidth: bool,
httpd.themeName,
httpd.systemLanguage,
httpd.maxLikeCount,
httpd.signingPrivateKeyPem), daemon=True)
httpd.signingPrivateKeyPem,
httpd.defaultReplyIntervalHours), daemon=True)
print('Creating scheduled post thread')
httpd.thrPostSchedule = \

View File

@ -170,6 +170,11 @@ parser.add_argument('--dormantMonths',
default=3,
help='How many months does a followed account need to ' +
'be unseen for before being considered dormant')
parser.add_argument('--defaultReplyIntervalHours',
dest='defaultReplyIntervalHours', type=int,
default=9999999999,
help='How many hours after publication of a post ' +
'are replies to it permitted')
parser.add_argument('--sendThreadsTimeoutMins',
dest='sendThreadsTimeoutMins', type=int,
default=30,
@ -3031,7 +3036,8 @@ if args.defaultCurrency:
print('Default currency set to ' + args.defaultCurrency)
if __name__ == "__main__":
runDaemon(args.lowBandwidth, args.maxLikeCount,
runDaemon(args.defaultReplyIntervalHours,
args.lowBandwidth, args.maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
args.logLoginFailures,

View File

@ -15,6 +15,8 @@ import random
from linked_data_sig import verifyJsonSignature
from languages import understoodPostLanguage
from like import updateLikesCollection
from utils import getReplyIntervalHours
from utils import canReplyTo
from utils import getUserPaths
from utils import getBaseContentFromPost
from utils import acctDir
@ -2484,7 +2486,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
lastBounceMessage: [],
themeName: str, systemLanguage: str,
maxLikeCount: int,
signingPrivateKeyPem: str) -> bool:
signingPrivateKeyPem: str,
defaultReplyIntervalHours: int) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
actor = keyId
@ -2765,11 +2768,29 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
if isinstance(inReplyTo, str):
if not isMuted(baseDir, nickname, domain,
inReplyTo, conversationId):
actUrl = \
localActorUrl(httpPrefix,
nickname, domain)
_replyNotify(baseDir, handle,
actUrl + '/tlreplies')
# check if the reply is within the allowed
# time period after publication
hrs = defaultReplyIntervalHours
replyIntervalHours = \
getReplyIntervalHours(baseDir,
nickname,
domain, hrs)
if canReplyTo(baseDir, nickname, domain,
inReplyTo,
replyIntervalHours):
actUrl = \
localActorUrl(httpPrefix,
nickname, domain)
_replyNotify(baseDir, handle,
actUrl + '/tlreplies')
else:
if debug:
print('Reply to ' + inReplyTo +
' is outside of the ' +
'permitted interval of ' +
str(replyIntervalHours) +
' hours')
return False
else:
isReplyToMutedPost = True
@ -3119,7 +3140,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
verifyAllSignatures: bool,
themeName: str, systemLanguage: str,
maxLikeCount: int, signingPrivateKeyPem: str) -> None:
maxLikeCount: int, signingPrivateKeyPem: str,
defaultReplyIntervalHours: int) -> None:
"""Processes received items and moves them to the appropriate
directories
"""
@ -3534,7 +3556,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
lastBounceMessage,
themeName, systemLanguage,
maxLikeCount,
signingPrivateKeyPem)
signingPrivateKeyPem,
defaultReplyIntervalHours)
if debug:
pprint(queueJson['post'])
print('Queue: Queue post accepted')

View File

@ -649,8 +649,10 @@ def createServerAlice(path: str, domain: str, port: int,
logLoginFailures = False
userAgentsBlocked = []
maxLikeCount = 10
defaultReplyIntervalHours = 9999999999
print('Server running: Alice')
runDaemon(lowBandwidth, maxLikeCount,
runDaemon(defaultReplyIntervalHours,
lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@ -785,8 +787,10 @@ def createServerBob(path: str, domain: str, port: int,
logLoginFailures = False
userAgentsBlocked = []
maxLikeCount = 10
defaultReplyIntervalHours = 9999999999
print('Server running: Bob')
runDaemon(lowBandwidth, maxLikeCount,
runDaemon(defaultReplyIntervalHours,
lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@ -850,8 +854,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
userAgentsBlocked = []
maxLikeCount = 10
lowBandwidth = True
defaultReplyIntervalHours = 9999999999
print('Server running: Eve')
runDaemon(lowBandwidth, maxLikeCount,
runDaemon(defaultReplyIntervalHours,
lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@ -917,8 +923,10 @@ def createServerGroup(path: str, domain: str, port: int,
userAgentsBlocked = []
maxLikeCount = 10
lowBandwidth = True
defaultReplyIntervalHours = 9999999999
print('Server running: Group')
runDaemon(lowBandwidth, maxLikeCount,
runDaemon(defaultReplyIntervalHours,
lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "البحث عن العناصر المطلوبة",
"Website": "موقع إلكتروني",
"Low Bandwidth": "انخفاض النطاق الترددي",
"accommodation": "الإقامة"
"accommodation": "الإقامة",
"Forbidden": "محرم",
"You're not allowed": "كنت لا يسمح"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Cerca d'articles desitjats",
"Website": "Lloc web",
"Low Bandwidth": "Ample de banda baixa",
"accommodation": "allotjament"
"accommodation": "allotjament",
"Forbidden": "Prohibit",
"You're not allowed": "No està permès"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Chwilio Eitemau Eisiau",
"Website": "Gwefan",
"Low Bandwidth": "Lled band isel",
"accommodation": "llety"
"accommodation": "llety",
"Forbidden": "Wedi'i wahardd",
"You're not allowed": "Ni chaniateir i chi"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Gesuchte Artikel suchen",
"Website": "Webseite",
"Low Bandwidth": "Niedrige Bandbreite",
"accommodation": "unterkunft"
"accommodation": "unterkunft",
"Forbidden": "Verboten",
"You're not allowed": "Du darfst nicht"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
"Low Bandwidth": "Low Bandwidth",
"accommodation": "accommodation"
"accommodation": "accommodation",
"Forbidden": "Forbidden",
"You're not allowed": "You're not allowed"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Búsqueda de artículos deseados",
"Website": "Sitio web",
"Low Bandwidth": "Ancho de banda bajo",
"accommodation": "alojamiento"
"accommodation": "alojamiento",
"Forbidden": "Prohibida",
"You're not allowed": "No tienes permiso"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Recherche d'objets recherchés",
"Website": "Site Internet",
"Low Bandwidth": "Bas débit",
"accommodation": "hébergement"
"accommodation": "hébergement",
"Forbidden": "Interdite",
"You're not allowed": "Tu n'as pas le droit"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Cuardaigh Míreanna Teastaíonn",
"Website": "Suíomh gréasáin",
"Low Bandwidth": "Bandaleithead íseal",
"accommodation": "lóistín"
"accommodation": "lóistín",
"Forbidden": "Toirmiscthe",
"You're not allowed": "Níl cead agat"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "वांटेड आइटम सर्च",
"Website": "वेबसाइट",
"Low Bandwidth": "कम बैंडविड्थ",
"accommodation": "निवास स्थान"
"accommodation": "निवास स्थान",
"Forbidden": "निषिद्ध",
"You're not allowed": "आपको अनुमति नहीं है"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Ricerca articoli ricercati",
"Website": "Sito web",
"Low Bandwidth": "Bassa larghezza di banda",
"accommodation": "struttura ricettiva"
"accommodation": "struttura ricettiva",
"Forbidden": "Proibita",
"You're not allowed": "Non ti è permesso"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "欲しいアイテム検索",
"Website": "Webサイト",
"Low Bandwidth": "低帯域幅",
"accommodation": "宿泊施設"
"accommodation": "宿泊施設",
"Forbidden": "禁断",
"You're not allowed": "あなたは許可されていません"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Malper",
"Low Bandwidth": "Bandwidth kêm",
"accommodation": "cih"
"accommodation": "cih",
"Forbidden": "Qedexekirî",
"You're not allowed": "Destûrê nadin te"
}

View File

@ -471,5 +471,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
"Low Bandwidth": "Low Bandwidth",
"accommodation": "accommodation"
"accommodation": "accommodation",
"Forbidden": "Forbidden",
"You're not allowed": "You're not allowed"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Pesquisa de Itens Desejados",
"Website": "Local na rede Internet",
"Low Bandwidth": "Baixa largura de banda",
"accommodation": "alojamento"
"accommodation": "alojamento",
"Forbidden": "Proibida",
"You're not allowed": "Você não tem permissão"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Поиск требуемых предметов",
"Website": "Интернет сайт",
"Low Bandwidth": "Низкая пропускная способность",
"accommodation": "размещение"
"accommodation": "размещение",
"Forbidden": "Запрещенный",
"You're not allowed": "Вам не разрешено"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "Utafutaji wa Vitu vinavyotafutwa",
"Website": "Tovuti",
"Low Bandwidth": "Bandwidth ya chini",
"accommodation": "malazi"
"accommodation": "malazi",
"Forbidden": "Imekatazwa",
"You're not allowed": "Hauruhusiwi"
}

View File

@ -475,5 +475,7 @@
"Wanted Items Search": "通缉物品搜索",
"Website": "网站",
"Low Bandwidth": "低带宽",
"accommodation": "住所"
"accommodation": "住所",
"Forbidden": "禁止的",
"You're not allowed": "你不被允许"
}

View File

@ -1317,6 +1317,74 @@ def locatePost(baseDir: str, nickname: str, domain: str,
return None
def _getPublishedDate(postJsonObject: {}) -> str:
"""Returns the published date on the given post
"""
published = None
if postJsonObject.get('published'):
published = postJsonObject['published']
elif postJsonObject.get('object'):
if isinstance(postJsonObject['object'], dict):
if postJsonObject['object'].get('published'):
published = postJsonObject['object']['published']
if not published:
return None
if not isinstance(published, str):
return None
return published
def getReplyIntervalHours(baseDir: str, nickname: str, domain: str,
defaultReplyIntervalHours: int) -> int:
"""Returns the reply interval for the given account.
The reply interval is the number of hours after a post being made
during which replies are allowed
"""
replyIntervalFilename = \
acctDir(baseDir, nickname, domain) + '/.replyIntervalHours'
if os.path.isfile(replyIntervalFilename):
with open(replyIntervalFilename, 'r') as fp:
hoursStr = fp.read()
if hoursStr.isdigit():
return int(hoursStr)
return defaultReplyIntervalHours
def canReplyTo(baseDir: str, nickname: str, domain: str,
postUrl: str, replyIntervalHours: int,
currDateStr: str = None) -> bool:
"""Is replying to the given post permitted?
This is a spam mitigation feature, so that spammers can't
add a lot of replies to old post which you don't notice.
"""
postFilename = locatePost(baseDir, nickname, domain, postUrl)
if not postFilename:
return False
postJsonObject = loadJson(postFilename)
if not postJsonObject:
return False
published = _getPublishedDate(postJsonObject)
if not published:
return False
try:
pubDate = datetime.datetime.strptime(published, '%Y-%m-%dT%H:%M:%SZ')
except BaseException:
return False
if not currDateStr:
currDate = datetime.datetime.utcnow()
else:
try:
currDate = datetime.datetime.strptime(currDateStr,
'%Y-%m-%dT%H:%M:%SZ')
except BaseException:
return False
hoursSincePublication = int((currDate - pubDate).total_seconds() / 3600)
if hoursSincePublication < 0 or \
hoursSincePublication > replyIntervalHours:
return False
return True
def _removeAttachment(baseDir: str, httpPrefix: str, domain: str,
postJson: {}):
if not postJson.get('attachment'):