Modern ActivityPub compliant server, designed for simplicity and accessibility. Includes calendar and sharing economy features to empower your federated community. https://code.freedombone.net/bashrc/epicyon Docs: https://epicyon.net/#install
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bookmarks.py 24KB

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