like.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. __filename__ = "like.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.0.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import os
  9. import json
  10. import time
  11. import commentjson
  12. from pprint import pprint
  13. from utils import urlPermitted
  14. from utils import getNicknameFromActor
  15. from utils import getDomainFromActor
  16. from utils import locatePost
  17. from utils import getCachedPostFilename
  18. from utils import loadJson
  19. from utils import saveJson
  20. from posts import sendSignedJson
  21. from session import postJson
  22. from webfinger import webfingerHandle
  23. from auth import createBasicAuthHeader
  24. from posts import getPersonBox
  25. def undoLikesCollectionEntry(baseDir: str,postFilename: str,objectUrl: str, \
  26. actor: str,domain: str,debug: bool) -> None:
  27. """Undoes a like for a particular actor
  28. """
  29. postJsonObject=loadJson(postFilename)
  30. if postJsonObject:
  31. # remove any cached version of this post so that the like icon is changed
  32. nickname=getNicknameFromActor(actor)
  33. cachedPostFilename= \
  34. getCachedPostFilename(baseDir,nickname,domain,postJsonObject)
  35. if os.path.isfile(cachedPostFilename):
  36. os.remove(cachedPostFilename)
  37. if not postJsonObject.get('type'):
  38. return
  39. if postJsonObject['type']!='Create':
  40. return
  41. if not postJsonObject.get('object'):
  42. if debug:
  43. pprint(postJsonObject)
  44. print('DEBUG: post '+objectUrl+' has no object')
  45. return
  46. if not isinstance(postJsonObject['object'], dict):
  47. return
  48. if not postJsonObject['object'].get('likes'):
  49. return
  50. if not isinstance(postJsonObject['object']['likes'], dict):
  51. return
  52. if not postJsonObject['object']['likes'].get('items'):
  53. return
  54. totalItems=0
  55. if postJsonObject['object']['likes'].get('totalItems'):
  56. totalItems=postJsonObject['object']['likes']['totalItems']
  57. itemFound=False
  58. for likeItem in postJsonObject['object']['likes']['items']:
  59. if likeItem.get('actor'):
  60. if likeItem['actor']==actor:
  61. if debug:
  62. print('DEBUG: like was removed for '+actor)
  63. postJsonObject['object']['likes']['items'].remove(likeItem)
  64. itemFound=True
  65. break
  66. if itemFound:
  67. if totalItems==1:
  68. if debug:
  69. print('DEBUG: likes was removed from post')
  70. del postJsonObject['object']['likes']
  71. else:
  72. postJsonObject['object']['likes']['totalItems']= \
  73. len(postJsonObject['likes']['items'])
  74. saveJson(postJsonObject,postFilename)
  75. def likedByPerson(postJsonObject: {}, nickname: str,domain: str) -> bool:
  76. """Returns True if the given post is liked by the given person
  77. """
  78. if noOfLikes(postJsonObject)==0:
  79. return False
  80. actorMatch=domain+'/users/'+nickname
  81. for item in postJsonObject['object']['likes']['items']:
  82. if item['actor'].endswith(actorMatch):
  83. return True
  84. return False
  85. def noOfLikes(postJsonObject: {}) -> int:
  86. """Returns the number of likes ona given post
  87. """
  88. if not postJsonObject.get('object'):
  89. return 0
  90. if not isinstance(postJsonObject['object'], dict):
  91. return 0
  92. if not postJsonObject['object'].get('likes'):
  93. return 0
  94. if not isinstance(postJsonObject['object']['likes'], dict):
  95. return 0
  96. if not postJsonObject['object']['likes'].get('items'):
  97. postJsonObject['object']['likes']['items']=[]
  98. postJsonObject['object']['likes']['totalItems']=0
  99. return len(postJsonObject['object']['likes']['items'])
  100. def updateLikesCollection(baseDir: str,postFilename: str, \
  101. objectUrl: str, \
  102. actor: str,domain: str,debug: bool) -> None:
  103. """Updates the likes collection within a post
  104. """
  105. postJsonObject=loadJson(postFilename)
  106. if postJsonObject:
  107. # remove any cached version of this post so that the like icon is changed
  108. nickname=getNicknameFromActor(actor)
  109. cachedPostFilename= \
  110. getCachedPostFilename(baseDir,nickname,domain,postJsonObject)
  111. if os.path.isfile(cachedPostFilename):
  112. os.remove(cachedPostFilename)
  113. if not postJsonObject.get('object'):
  114. if debug:
  115. pprint(postJsonObject)
  116. print('DEBUG: post '+objectUrl+' has no object')
  117. return
  118. if not objectUrl.endswith('/likes'):
  119. objectUrl=objectUrl+'/likes'
  120. if not postJsonObject['object'].get('likes'):
  121. if debug:
  122. print('DEBUG: Adding initial likes to '+objectUrl)
  123. likesJson = {
  124. "@context": "https://www.w3.org/ns/activitystreams",
  125. 'id': objectUrl,
  126. 'type': 'Collection',
  127. "totalItems": 1,
  128. 'items': [{
  129. 'type': 'Like',
  130. 'actor': actor
  131. }]
  132. }
  133. postJsonObject['object']['likes']=likesJson
  134. else:
  135. if not postJsonObject['object']['likes'].get('items'):
  136. postJsonObject['object']['likes']['items']=[]
  137. for likeItem in postJsonObject['object']['likes']['items']:
  138. if likeItem.get('actor'):
  139. if likeItem['actor']==actor:
  140. return
  141. newLike={
  142. 'type': 'Like',
  143. 'actor': actor
  144. }
  145. postJsonObject['object']['likes']['items'].append(newLike)
  146. postJsonObject['object']['likes']['totalItems']= \
  147. len(postJsonObject['object']['likes']['items'])
  148. if debug:
  149. print('DEBUG: saving post with likes added')
  150. pprint(postJsonObject)
  151. saveJson(postJsonObject,postFilename)
  152. def like(session,baseDir: str,federationList: [], \
  153. nickname: str,domain: str,port: int, \
  154. ccList: [],httpPrefix: str, \
  155. objectUrl: str,actorLiked: str, \
  156. clientToServer: bool, \
  157. sendThreads: [],postLog: [], \
  158. personCache: {},cachedWebfingers: {}, \
  159. debug: bool,projectVersion: str) -> {}:
  160. """Creates a like
  161. actor is the person doing the liking
  162. 'to' might be a specific person (actor) whose post was liked
  163. object is typically the url of the message which was liked
  164. """
  165. if not urlPermitted(objectUrl,federationList,"inbox:write"):
  166. return None
  167. fullDomain=domain
  168. if port:
  169. if port!=80 and port!=443:
  170. if ':' not in domain:
  171. fullDomain=domain+':'+str(port)
  172. likeTo=[]
  173. if '/statuses/' in objectUrl:
  174. likeTo=[objectUrl.split('/statuses/')[0]]
  175. newLikeJson = {
  176. "@context": "https://www.w3.org/ns/activitystreams",
  177. 'type': 'Like',
  178. 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
  179. 'object': objectUrl
  180. }
  181. if ccList:
  182. if len(ccList)>0:
  183. newLikeJson['cc']=ccList
  184. # Extract the domain and nickname from a statuses link
  185. likedPostNickname=None
  186. likedPostDomain=None
  187. likedPostPort=None
  188. if actorLiked:
  189. likedPostNickname=getNicknameFromActor(actorLiked)
  190. likedPostDomain,likedPostPort=getDomainFromActor(actorLiked)
  191. else:
  192. if '/users/' in objectUrl or \
  193. '/channel/' in objectUrl or \
  194. '/profile/' in objectUrl:
  195. likedPostNickname=getNicknameFromActor(objectUrl)
  196. likedPostDomain,likedPostPort=getDomainFromActor(objectUrl)
  197. if likedPostNickname:
  198. postFilename=locatePost(baseDir,nickname,domain,objectUrl)
  199. if not postFilename:
  200. print('DEBUG: like baseDir: '+baseDir)
  201. print('DEBUG: like nickname: '+nickname)
  202. print('DEBUG: like domain: '+domain)
  203. print('DEBUG: like objectUrl: '+objectUrl)
  204. return None
  205. updateLikesCollection(baseDir,postFilename,objectUrl, \
  206. newLikeJson['actor'],domain,debug)
  207. sendSignedJson(newLikeJson,session,baseDir, \
  208. nickname,domain,port, \
  209. likedPostNickname,likedPostDomain,likedPostPort, \
  210. 'https://www.w3.org/ns/activitystreams#Public', \
  211. httpPrefix,True,clientToServer,federationList, \
  212. sendThreads,postLog,cachedWebfingers,personCache, \
  213. debug,projectVersion)
  214. return newLikeJson
  215. def likePost(session,baseDir: str,federationList: [], \
  216. nickname: str,domain: str,port: int,httpPrefix: str, \
  217. likeNickname: str,likeDomain: str,likePort: int, \
  218. ccList: [], \
  219. likeStatusNumber: int,clientToServer: bool, \
  220. sendThreads: [],postLog: [], \
  221. personCache: {},cachedWebfingers: {}, \
  222. debug: bool,projectVersion: str) -> {}:
  223. """Likes a given status post. This is only used by unit tests
  224. """
  225. likeDomain=likeDomain
  226. if likePort:
  227. if likePort!=80 and likePort!=443:
  228. if ':' not in likeDomain:
  229. likeDomain=likeDomain+':'+str(likePort)
  230. actorLiked= \
  231. httpPrefix + '://'+likeDomain+'/users/'+likeNickname
  232. objectUrl=actorLiked+'/statuses/'+str(likeStatusNumber)
  233. ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname
  234. if likePort:
  235. if likePort!=80 and likePort!=443:
  236. if ':' not in likeDomain:
  237. ccUrl= \
  238. httpPrefix+'://'+likeDomain+':'+ \
  239. str(likePort)+'/users/'+likeNickname
  240. return like(session,baseDir,federationList,nickname,domain,port, \
  241. ccList,httpPrefix,objectUrl,actorLiked,clientToServer, \
  242. sendThreads,postLog,personCache,cachedWebfingers, \
  243. debug,projectVersion)
  244. def undolike(session,baseDir: str,federationList: [], \
  245. nickname: str,domain: str,port: int, \
  246. ccList: [],httpPrefix: str, \
  247. objectUrl: str,actorLiked: str, \
  248. clientToServer: bool, \
  249. sendThreads: [],postLog: [], \
  250. personCache: {},cachedWebfingers: {}, \
  251. debug: bool,projectVersion: str) -> {}:
  252. """Removes a like
  253. actor is the person doing the liking
  254. 'to' might be a specific person (actor) whose post was liked
  255. object is typically the url of the message which was liked
  256. """
  257. if not urlPermitted(objectUrl,federationList,"inbox:write"):
  258. return None
  259. fullDomain=domain
  260. if port:
  261. if port!=80 and port!=443:
  262. if ':' not in domain:
  263. fullDomain=domain+':'+str(port)
  264. likeTo=[]
  265. if '/statuses/' in objectUrl:
  266. likeTo=[objectUrl.split('/statuses/')[0]]
  267. newUndoLikeJson = {
  268. "@context": "https://www.w3.org/ns/activitystreams",
  269. 'type': 'Undo',
  270. 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
  271. 'object': {
  272. 'type': 'Like',
  273. 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
  274. 'object': objectUrl
  275. }
  276. }
  277. if ccList:
  278. if len(ccList)>0:
  279. newUndoLikeJson['cc']=ccList
  280. newUndoLikeJson['object']['cc']=ccList
  281. # Extract the domain and nickname from a statuses link
  282. likedPostNickname=None
  283. likedPostDomain=None
  284. likedPostPort=None
  285. if actorLiked:
  286. likedPostNickname=getNicknameFromActor(actorLiked)
  287. likedPostDomain,likedPostPort=getDomainFromActor(actorLiked)
  288. else:
  289. if '/users/' in objectUrl or \
  290. '/channel/' in objectUrl or \
  291. '/profile/' in objectUrl:
  292. likedPostNickname=getNicknameFromActor(objectUrl)
  293. likedPostDomain,likedPostPort=getDomainFromActor(objectUrl)
  294. if likedPostNickname:
  295. postFilename=locatePost(baseDir,nickname,domain,objectUrl)
  296. if not postFilename:
  297. return None
  298. undoLikesCollectionEntry(baseDir,postFilename,objectUrl, \
  299. newLikeJson['actor'],domain,debug)
  300. sendSignedJson(newUndoLikeJson,session,baseDir, \
  301. nickname,domain,port, \
  302. likedPostNickname,likedPostDomain,likedPostPort, \
  303. 'https://www.w3.org/ns/activitystreams#Public', \
  304. httpPrefix,True,clientToServer,federationList, \
  305. sendThreads,postLog,cachedWebfingers,personCache, \
  306. debug,projectVersion)
  307. else:
  308. return None
  309. return newUndoLikeJson
  310. def undoLikePost(session,baseDir: str,federationList: [], \
  311. nickname: str,domain: str,port: int,httpPrefix: str, \
  312. likeNickname: str,likeDomain: str,likePort: int, \
  313. ccList: [], \
  314. likeStatusNumber: int,clientToServer: bool, \
  315. sendThreads: [],postLog: [], \
  316. personCache: {},cachedWebfingers: {}, \
  317. debug: bool) -> {}:
  318. """Removes a liked post
  319. """
  320. likeDomain=likeDomain
  321. if likePort:
  322. if likePort!=80 and likePort!=443:
  323. if ':' not in likeDomain:
  324. likeDomain=likeDomain+':'+str(likePort)
  325. objectUrl = \
  326. httpPrefix + '://'+likeDomain+'/users/'+likeNickname+ \
  327. '/statuses/'+str(likeStatusNumber)
  328. ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname
  329. if likePort:
  330. if likePort!=80 and likePort!=443:
  331. if ':' not in likeDomain:
  332. ccUrl= \
  333. httpPrefix+'://'+likeDomain+':'+ \
  334. str(likePort)+'/users/'+likeNickname
  335. return undoLike(session,baseDir,federationList,nickname,domain,port, \
  336. ccList,httpPrefix,objectUrl,clientToServer, \
  337. sendThreads,postLog,personCache,cachedWebfingers,debug)
  338. def sendLikeViaServer(baseDir: str,session, \
  339. fromNickname: str,password: str,
  340. fromDomain: str,fromPort: int, \
  341. httpPrefix: str,likeUrl: str, \
  342. cachedWebfingers: {},personCache: {}, \
  343. debug: bool,projectVersion: str) -> {}:
  344. """Creates a like via c2s
  345. """
  346. if not session:
  347. print('WARN: No session for sendLikeViaServer')
  348. return 6
  349. fromDomainFull=fromDomain
  350. if fromPort:
  351. if fromPort!=80 and fromPort!=443:
  352. if ':' not in fromDomain:
  353. fromDomainFull=fromDomain+':'+str(fromPort)
  354. toUrl=['https://www.w3.org/ns/activitystreams#Public']
  355. ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  356. if '/statuses/' in likeUrl:
  357. toUrl=[likeUrl.split('/statuses/')[0]]
  358. newLikeJson = {
  359. "@context": "https://www.w3.org/ns/activitystreams",
  360. 'type': 'Like',
  361. 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
  362. 'object': likeUrl
  363. }
  364. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  365. # lookup the inbox for the To handle
  366. wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  367. fromDomain,projectVersion)
  368. if not wfRequest:
  369. if debug:
  370. print('DEBUG: announce webfinger failed for '+handle)
  371. return 1
  372. postToBox='outbox'
  373. # get the actor inbox for the To handle
  374. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  375. getPersonBox(baseDir,session,wfRequest,personCache, \
  376. projectVersion,httpPrefix,fromNickname, \
  377. fromDomain,postToBox)
  378. if not inboxUrl:
  379. if debug:
  380. print('DEBUG: No '+postToBox+' was found for '+handle)
  381. return 3
  382. if not fromPersonId:
  383. if debug:
  384. print('DEBUG: No actor was found for '+handle)
  385. return 4
  386. authHeader=createBasicAuthHeader(fromNickname,password)
  387. headers = {'host': fromDomain, \
  388. 'Content-type': 'application/json', \
  389. 'Authorization': authHeader}
  390. postResult = \
  391. postJson(session,newLikeJson,[],inboxUrl,headers,"inbox:write")
  392. #if not postResult:
  393. # if debug:
  394. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  395. # return 5
  396. if debug:
  397. print('DEBUG: c2s POST like success')
  398. return newLikeJson
  399. def sendUndoLikeViaServer(baseDir: str,session, \
  400. fromNickname: str,password: str, \
  401. fromDomain: str,fromPort: int, \
  402. httpPrefix: str,likeUrl: str, \
  403. cachedWebfingers: {},personCache: {}, \
  404. debug: bool,projectVersion: str) -> {}:
  405. """Undo a like via c2s
  406. """
  407. if not session:
  408. print('WARN: No session for sendUndoLikeViaServer')
  409. return 6
  410. fromDomainFull=fromDomain
  411. if fromPort:
  412. if fromPort!=80 and fromPort!=443:
  413. if ':' not in fromDomain:
  414. fromDomainFull=fromDomain+':'+str(fromPort)
  415. toUrl=['https://www.w3.org/ns/activitystreams#Public']
  416. ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  417. if '/statuses/' in likeUrl:
  418. toUrl=[likeUrl.split('/statuses/')[0]]
  419. newUndoLikeJson = {
  420. "@context": "https://www.w3.org/ns/activitystreams",
  421. 'type': 'Undo',
  422. 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
  423. 'object': {
  424. 'type': 'Like',
  425. 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
  426. 'object': likeUrl
  427. }
  428. }
  429. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  430. # lookup the inbox for the To handle
  431. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  432. fromDomain,projectVersion)
  433. if not wfRequest:
  434. if debug:
  435. print('DEBUG: announce webfinger failed for '+handle)
  436. return 1
  437. postToBox='outbox'
  438. # get the actor inbox for the To handle
  439. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  440. getPersonBox(baseDir,session,wfRequest,personCache, \
  441. projectVersion,httpPrefix,fromNickname, \
  442. fromDomain,postToBox)
  443. if not inboxUrl:
  444. if debug:
  445. print('DEBUG: No '+postToBox+' was found for '+handle)
  446. return 3
  447. if not fromPersonId:
  448. if debug:
  449. print('DEBUG: No actor was found for '+handle)
  450. return 4
  451. authHeader=createBasicAuthHeader(fromNickname,password)
  452. headers = {'host': fromDomain, \
  453. 'Content-type': 'application/json', \
  454. 'Authorization': authHeader}
  455. postResult = \
  456. postJson(session,newUndoLikeJson,[],inboxUrl,headers,"inbox:write")
  457. #if not postResult:
  458. # if debug:
  459. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  460. # return 5
  461. if debug:
  462. print('DEBUG: c2s POST undo like success')
  463. return newUndoLikeJson
  464. def outboxLike(baseDir: str,httpPrefix: str, \
  465. nickname: str,domain: str,port: int, \
  466. messageJson: {},debug: bool) -> None:
  467. """ When a like request is received by the outbox from c2s
  468. """
  469. if not messageJson.get('type'):
  470. if debug:
  471. print('DEBUG: like - no type')
  472. return
  473. if not messageJson['type']=='Like':
  474. if debug:
  475. print('DEBUG: not a like')
  476. return
  477. if not messageJson.get('object'):
  478. if debug:
  479. print('DEBUG: no object in like')
  480. return
  481. if not isinstance(messageJson['object'], str):
  482. if debug:
  483. print('DEBUG: like object is not string')
  484. return
  485. if debug:
  486. print('DEBUG: c2s like request arrived in outbox')
  487. messageId=messageJson['object'].replace('/activity','')
  488. if ':' in domain:
  489. domain=domain.split(':')[0]
  490. postFilename=locatePost(baseDir,nickname,domain,messageId)
  491. if not postFilename:
  492. if debug:
  493. print('DEBUG: c2s like post not found in inbox or outbox')
  494. print(messageId)
  495. return True
  496. updateLikesCollection(baseDir,postFilename,messageId, \
  497. messageJson['actor'],domain,debug)
  498. if debug:
  499. print('DEBUG: post liked via c2s - '+postFilename)
  500. def outboxUndoLike(baseDir: str,httpPrefix: str, \
  501. nickname: str,domain: str,port: int, \
  502. messageJson: {},debug: bool) -> None:
  503. """ When an undo like request is received by the outbox from c2s
  504. """
  505. if not messageJson.get('type'):
  506. return
  507. if not messageJson['type']=='Undo':
  508. return
  509. if not messageJson.get('object'):
  510. return
  511. if not isinstance(messageJson['object'], dict):
  512. if debug:
  513. print('DEBUG: undo like object is not dict')
  514. return
  515. if not messageJson['object'].get('type'):
  516. if debug:
  517. print('DEBUG: undo like - no type')
  518. return
  519. if not messageJson['object']['type']=='Like':
  520. if debug:
  521. print('DEBUG: not a undo like')
  522. return
  523. if not messageJson['object'].get('object'):
  524. if debug:
  525. print('DEBUG: no object in undo like')
  526. return
  527. if not isinstance(messageJson['object']['object'], str):
  528. if debug:
  529. print('DEBUG: undo like object is not string')
  530. return
  531. if debug:
  532. print('DEBUG: c2s undo like request arrived in outbox')
  533. messageId=messageJson['object']['object'].replace('/activity','')
  534. if ':' in domain:
  535. domain=domain.split(':')[0]
  536. postFilename=locatePost(baseDir,nickname,domain,messageId)
  537. if not postFilename:
  538. if debug:
  539. print('DEBUG: c2s undo like post not found in inbox or outbox')
  540. print(messageId)
  541. return True
  542. undoLikesCollectionEntry(baseDir,postFilename,messageId, \
  543. messageJson['actor'],domain,debug)
  544. if debug:
  545. print('DEBUG: post undo liked via c2s - '+postFilename)