From 01f768528f5154f7fc7dd28c706a81f246ee060b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 11:16:45 +0000 Subject: [PATCH 1/9] Authorized access to following/followers collections for use by c2s --- daemon.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/daemon.py b/daemon.py index 48e0cf892..0e7fe3dec 100644 --- a/daemon.py +++ b/daemon.py @@ -9666,9 +9666,7 @@ class PubServer(BaseHTTPRequestHandler): divertToLoginScreen = False else: if path.endswith('/following') or \ - '/following?page=' in path or \ path.endswith('/followers') or \ - '/followers?page=' in path or \ path.endswith('/skills') or \ path.endswith('/roles') or \ path.endswith('/shares'): @@ -10276,6 +10274,75 @@ class PubServer(BaseHTTPRequestHandler): return True return False + def _getFollowingPage(self, baseDir: str, path: str, + callingDomain: str, + httpPrefix: str, + domain: str, domainFull: str, + followingItemsPerPage: int, + debug: bool, listName='following') -> None: + """Returns json collection for following.txt + """ + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + pageNumberStr = path.split('?page=')[1] + if '?' in pageNumberStr: + pageNumberStr = pageNumberStr.split('?')[0] + followingFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/' + listName + '.txt' + if not os.path.isfile(followingFilename): + self._404() + return + if debug: + print('Getting ' + listName + ' list json for ' + nickname + + ' page ' + pageNumberStr) + followingList = [] + with open(followingFilename, 'r') as fp: + followingList = fp.readlines() + + objectUrl = \ + httpPrefix + '://' + domainFull + '/users/' + nickname + \ + '/' + listName + '?page=' + pageNumberStr + followingJson = { + "@context": "https://www.w3.org/ns/activitystreams", + 'id': objectUrl, + 'type': 'Collection', + "totalItems": 0, + 'items': [] + } + + if not pageNumberStr.isdigit(): + pageNumber = 1 + else: + pageNumber = int(pageNumberStr) + page = 1 + ctr = 0 + for handle in followingList: + ctr += 1 + if ctr >= followingItemsPerPage: + ctr = 0 + page += 1 + if page > pageNumber: + break + if page < pageNumber: + continue + if '://' in handle: + handleNickname = getNicknameFromActor(handle) + handleDomain, handlePort = getDomainFromActor(handle) + handle = handleNickname + '@' + handleDomain + followingJson['items'].append({ + 'type': 'Document', + 'name': handle + }) + followingJson['totalItems'] = len(followingJson['items']) + msg = json.dumps(followingJson, + ensure_ascii=False).encode('utf-8') + msglen = len(msg) + self._set_headers('application/json', + msglen, None, callingDomain) + self._write(msg) + def do_GET(self): callingDomain = self.server.domainFull if self.headers.get('Host'): @@ -10573,6 +10640,28 @@ class PubServer(BaseHTTPRequestHandler): if '/users/' in self.path: usersInPath = True + if authorized and not htmlGET and usersInPath: + if '/following?page=' in self.path: + self._getFollowingPage(self.server.baseDir, + self.path, + callingDomain, + self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.followingItemsPerPage, + self.server.debug, 'following') + return + elif '/followers?page=' in self.path: + self._getFollowingPage(self.server.baseDir, + self.path, + callingDomain, + self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.followingItemsPerPage, + self.server.debug, 'followers') + return + # authorized endpoint used for TTS of posts # arriving in your inbox if authorized and usersInPath and \ @@ -14510,6 +14599,7 @@ def runDaemon(brochMode: bool, # for it to be considered dormant? httpd.dormantMonths = dormantMonths + httpd.followingItemsPerPage = 10 if registration == 'open': httpd.registration = True else: From 263e28cbc063032d41b2694f6e433b24953c97d9 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 12:43:24 +0000 Subject: [PATCH 2/9] Use existing feed function --- daemon.py | 65 +++++++--------------------------------------- epicyon.py | 35 +++++++++++++++++++++++++ follow.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 56 deletions(-) diff --git a/daemon.py b/daemon.py index 0e7fe3dec..51e10fdff 100644 --- a/daemon.py +++ b/daemon.py @@ -9284,7 +9284,8 @@ class PubServer(BaseHTTPRequestHandler): """ following = \ getFollowingFeed(baseDir, domain, port, path, - httpPrefix, authorized, followsPerPage) + httpPrefix, authorized, followsPerPage, + 'following') if following: if self._requestHTTP(): pageNumber = 1 @@ -10277,65 +10278,17 @@ class PubServer(BaseHTTPRequestHandler): def _getFollowingPage(self, baseDir: str, path: str, callingDomain: str, httpPrefix: str, - domain: str, domainFull: str, + domain: str, port: int, followingItemsPerPage: int, debug: bool, listName='following') -> None: """Returns json collection for following.txt """ - nickname = path.split('/users/')[1] - if '/' in nickname: - nickname = nickname.split('/')[0] - pageNumberStr = path.split('?page=')[1] - if '?' in pageNumberStr: - pageNumberStr = pageNumberStr.split('?')[0] - followingFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/' + listName + '.txt' - if not os.path.isfile(followingFilename): + followingJson = \ + getFollowingFeed(baseDir, domain, port, path, httpPrefix, + True, followingItemsPerPage) + if not followingJson: self._404() return - if debug: - print('Getting ' + listName + ' list json for ' + nickname + - ' page ' + pageNumberStr) - followingList = [] - with open(followingFilename, 'r') as fp: - followingList = fp.readlines() - - objectUrl = \ - httpPrefix + '://' + domainFull + '/users/' + nickname + \ - '/' + listName + '?page=' + pageNumberStr - followingJson = { - "@context": "https://www.w3.org/ns/activitystreams", - 'id': objectUrl, - 'type': 'Collection', - "totalItems": 0, - 'items': [] - } - - if not pageNumberStr.isdigit(): - pageNumber = 1 - else: - pageNumber = int(pageNumberStr) - page = 1 - ctr = 0 - for handle in followingList: - ctr += 1 - if ctr >= followingItemsPerPage: - ctr = 0 - page += 1 - if page > pageNumber: - break - if page < pageNumber: - continue - if '://' in handle: - handleNickname = getNicknameFromActor(handle) - handleDomain, handlePort = getDomainFromActor(handle) - handle = handleNickname + '@' + handleDomain - followingJson['items'].append({ - 'type': 'Document', - 'name': handle - }) - followingJson['totalItems'] = len(followingJson['items']) msg = json.dumps(followingJson, ensure_ascii=False).encode('utf-8') msglen = len(msg) @@ -10647,7 +10600,7 @@ class PubServer(BaseHTTPRequestHandler): callingDomain, self.server.httpPrefix, self.server.domain, - self.server.domainFull, + self.server.port, self.server.followingItemsPerPage, self.server.debug, 'following') return @@ -10657,7 +10610,7 @@ class PubServer(BaseHTTPRequestHandler): callingDomain, self.server.httpPrefix, self.server.domain, - self.server.domainFull, + self.server.port, self.server.followingItemsPerPage, self.server.debug, 'followers') return diff --git a/epicyon.py b/epicyon.py index b5575c1a4..0d5590011 100644 --- a/epicyon.py +++ b/epicyon.py @@ -46,6 +46,7 @@ from filters import addFilter from filters import removeFilter from pprint import pprint from daemon import runDaemon +from follow import getFollowingViaServer from follow import clearFollows from follow import followerOfPerson from follow import sendFollowRequestViaServer @@ -248,6 +249,12 @@ parser.add_argument('--rss', dest='rss', type=str, default=None, help='Show an rss feed for a given url') parser.add_argument('-f', '--federate', nargs='+', dest='federationList', help='Specify federation list separated by spaces') +parser.add_argument("--following", "--following", + dest='following', + type=str2bool, nargs='?', + const=True, default=True, + help="Get the following list. Use nickname and " + + "domain options to specify the account") parser.add_argument("--repliesEnabled", "--commentsEnabled", dest='commentsEnabled', type=str2bool, nargs='?', @@ -1484,6 +1491,34 @@ if args.unfollow: print('Ok') sys.exit() +if args.following: + # following list via c2s protocol + if not args.nickname: + print('Please specify the nickname for the account with --nickname') + sys.exit() + if not args.password: + args.password = getpass.getpass('Password: ') + if not args.password: + print('Specify a password with the --password option') + sys.exit() + args.password = args.password.replace('\n', '') + + session = createSession(proxyType) + personCache = {} + cachedWebfingers = {} + followHttpPrefix = httpPrefix + + followingJson = \ + getFollowingViaServer(baseDir, session, + args.nickname, args.password, + domain, port, + httpPrefix, args.pageNumber, + cachedWebfingers, personCache, + debug, __version__) + if followingJson: + pprint(followingJson) + sys.exit() + nickname = 'admin' if args.domain: domain = args.domain diff --git a/follow.py b/follow.py index 51b116a89..77ac66ea1 100644 --- a/follow.py +++ b/follow.py @@ -26,6 +26,7 @@ from acceptreject import createAccept from acceptreject import createReject from webfinger import webfingerHandle from auth import createBasicAuthHeader +from session import getJson from session import postJson @@ -1131,6 +1132,81 @@ def sendUnfollowRequestViaServer(baseDir: str, session, return unfollowJson +def getFollowingViaServer(baseDir: str, session, + nickname: str, password: str, + domain: str, port: int, + httpPrefix: str, pageNumber: int, + cachedWebfingers: {}, personCache: {}, + debug: bool, projectVersion: str) -> {}: + """Gets a page from the following collection as json + """ + if not session: + print('WARN: No session for getFollowingViaServer') + return 6 + + domainFull = getFullDomain(domain, port) + + followActor = httpPrefix + '://' + domainFull + '/users/' + nickname + handle = httpPrefix + '://' + domainFull + '/@' + nickname + + # lookup the inbox for the To handle + wfRequest = \ + webfingerHandle(session, handle, httpPrefix, cachedWebfingers, + domain, projectVersion, debug) + if not wfRequest: + if debug: + print('DEBUG: following list webfinger failed for ' + handle) + return 1 + if not isinstance(wfRequest, dict): + print('WARN: following list Webfinger for ' + handle + + ' did not return a dict. ' + str(wfRequest)) + return 1 + + postToBox = 'outbox' + + # get the actor inbox for the To handle + (inboxUrl, pubKeyId, pubKey, + fromPersonId, sharedInbox, avatarUrl, + displayName) = getPersonBox(baseDir, session, wfRequest, personCache, + projectVersion, httpPrefix, nickname, + domain, postToBox, 52025) + + if not inboxUrl: + if debug: + print('DEBUG: following list no ' + postToBox + + ' was found for ' + handle) + return 3 + if not fromPersonId: + if debug: + print('DEBUG: following list no actor was found for ' + handle) + return 4 + + authHeader = createBasicAuthHeader(nickname, password) + + headers = { + 'host': domain, + 'Content-type': 'application/json', + 'Authorization': authHeader + } + + if pageNumber < 1: + pageNumber = 1 + url = followActor + '/following?page=' + str(pageNumber) + followingJson = \ + getJson(session, url, headers, {}, debug, + __version__, httpPrefix, + domain, 10, True) + if not followingJson: + if debug: + print('DEBUG: GET following list failed for c2s to ' + url) + return 5 + + if debug: + print('DEBUG: c2s GET following list request success') + + return followingJson + + def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}: """In a shared inbox if we receive a post we know who it's from and if it's addressed to followers then we need to get a list of those. From ad1a569c683cd69e6b3ffc8c32306fbc3fcc5d57 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 13:15:43 +0000 Subject: [PATCH 3/9] Default to false --- daemon.py | 12 +++++++----- epicyon.py | 2 +- follow.py | 10 +++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/daemon.py b/daemon.py index 51e10fdff..ea7d66939 100644 --- a/daemon.py +++ b/daemon.py @@ -10275,7 +10275,7 @@ class PubServer(BaseHTTPRequestHandler): return True return False - def _getFollowingPage(self, baseDir: str, path: str, + def _getFollowingJson(self, baseDir: str, path: str, callingDomain: str, httpPrefix: str, domain: str, port: int, @@ -10285,8 +10285,10 @@ class PubServer(BaseHTTPRequestHandler): """ followingJson = \ getFollowingFeed(baseDir, domain, port, path, httpPrefix, - True, followingItemsPerPage) + True, followingItemsPerPage, listName) if not followingJson: + if debug: + print(listName + ' json feed not found for ' + path) self._404() return msg = json.dumps(followingJson, @@ -10595,7 +10597,7 @@ class PubServer(BaseHTTPRequestHandler): if authorized and not htmlGET and usersInPath: if '/following?page=' in self.path: - self._getFollowingPage(self.server.baseDir, + self._getFollowingJson(self.server.baseDir, self.path, callingDomain, self.server.httpPrefix, @@ -10605,7 +10607,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.debug, 'following') return elif '/followers?page=' in self.path: - self._getFollowingPage(self.server.baseDir, + self._getFollowingJson(self.server.baseDir, self.path, callingDomain, self.server.httpPrefix, @@ -14552,7 +14554,7 @@ def runDaemon(brochMode: bool, # for it to be considered dormant? httpd.dormantMonths = dormantMonths - httpd.followingItemsPerPage = 10 + httpd.followingItemsPerPage = 12 if registration == 'open': httpd.registration = True else: diff --git a/epicyon.py b/epicyon.py index 0d5590011..fb2f5808d 100644 --- a/epicyon.py +++ b/epicyon.py @@ -252,7 +252,7 @@ parser.add_argument('-f', '--federate', nargs='+', dest='federationList', parser.add_argument("--following", "--following", dest='following', type=str2bool, nargs='?', - const=True, default=True, + const=True, default=False, help="Get the following list. Use nickname and " + "domain options to specify the account") parser.add_argument("--repliesEnabled", "--commentsEnabled", diff --git a/follow.py b/follow.py index 77ac66ea1..471cc15b8 100644 --- a/follow.py +++ b/follow.py @@ -352,14 +352,14 @@ def _getNoOfFollowers(baseDir: str, def getFollowingFeed(baseDir: str, domain: str, port: int, path: str, - httpPrefix: str, authenticated: bool, + httpPrefix: str, authorized: bool, followsPerPage=12, followFile='following') -> {}: """Returns the following and followers feeds from GET requests. This accesses the following.txt or followers.txt and builds a collection. """ - # Show a small number of follows to non-authenticated viewers - if not authenticated: + # Show a small number of follows to non-authorized viewers + if not authorized: followsPerPage = 6 if '/' + followFile not in path: @@ -369,7 +369,7 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str, pageNumber = None if '?page=' in path: pageNumber = path.split('?page=')[1] - if pageNumber == 'true' or not authenticated: + if pageNumber == 'true' or not authorized: pageNumber = 1 else: try: @@ -401,7 +401,7 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str, httpPrefix + '://' + domain + '/users/' + \ nickname + '/' + followFile totalStr = \ - _getNoOfFollows(baseDir, nickname, domain, authenticated) + _getNoOfFollows(baseDir, nickname, domain, authorized) following = { '@context': 'https://www.w3.org/ns/activitystreams', 'first': firstStr, From 0f70a98ed440dd9e4008a9322f963fe2bdad3d9b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 13:52:20 +0000 Subject: [PATCH 4/9] Get followers list via c2s --- epicyon.py | 41 +++++++++++++++++++++++++++--- follow.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/epicyon.py b/epicyon.py index fb2f5808d..7a1437404 100644 --- a/epicyon.py +++ b/epicyon.py @@ -47,6 +47,7 @@ from filters import removeFilter from pprint import pprint from daemon import runDaemon from follow import getFollowingViaServer +from follow import getFollowersViaServer from follow import clearFollows from follow import followerOfPerson from follow import sendFollowRequestViaServer @@ -249,12 +250,18 @@ parser.add_argument('--rss', dest='rss', type=str, default=None, help='Show an rss feed for a given url') parser.add_argument('-f', '--federate', nargs='+', dest='federationList', help='Specify federation list separated by spaces') -parser.add_argument("--following", "--following", - dest='following', +parser.add_argument("--following", "--followingList", + dest='followingList', type=str2bool, nargs='?', const=True, default=False, help="Get the following list. Use nickname and " + "domain options to specify the account") +parser.add_argument("--followersList", + dest='followersList', + type=str2bool, nargs='?', + const=True, default=False, + help="Get the followers list. Use nickname and " + + "domain options to specify the account") parser.add_argument("--repliesEnabled", "--commentsEnabled", dest='commentsEnabled', type=str2bool, nargs='?', @@ -1491,7 +1498,7 @@ if args.unfollow: print('Ok') sys.exit() -if args.following: +if args.followingList: # following list via c2s protocol if not args.nickname: print('Please specify the nickname for the account with --nickname') @@ -1519,6 +1526,34 @@ if args.following: pprint(followingJson) sys.exit() +if args.followersList: + # following list via c2s protocol + if not args.nickname: + print('Please specify the nickname for the account with --nickname') + sys.exit() + if not args.password: + args.password = getpass.getpass('Password: ') + if not args.password: + print('Specify a password with the --password option') + sys.exit() + args.password = args.password.replace('\n', '') + + session = createSession(proxyType) + personCache = {} + cachedWebfingers = {} + followHttpPrefix = httpPrefix + + followersJson = \ + getFollowersViaServer(baseDir, session, + args.nickname, args.password, + domain, port, + httpPrefix, args.pageNumber, + cachedWebfingers, personCache, + debug, __version__) + if followersJson: + pprint(followersJson) + sys.exit() + nickname = 'admin' if args.domain: domain = args.domain diff --git a/follow.py b/follow.py index 471cc15b8..a39e2f803 100644 --- a/follow.py +++ b/follow.py @@ -1207,6 +1207,80 @@ def getFollowingViaServer(baseDir: str, session, return followingJson +def getFollowersViaServer(baseDir: str, session, + nickname: str, password: str, + domain: str, port: int, + httpPrefix: str, pageNumber: int, + cachedWebfingers: {}, personCache: {}, + debug: bool, projectVersion: str) -> {}: + """Gets a page from the followers collection as json + """ + if not session: + print('WARN: No session for getFollowersViaServer') + return 6 + + domainFull = getFullDomain(domain, port) + + followActor = httpPrefix + '://' + domainFull + '/users/' + nickname + handle = httpPrefix + '://' + domainFull + '/@' + nickname + + # lookup the inbox for the To handle + wfRequest = \ + webfingerHandle(session, handle, httpPrefix, cachedWebfingers, + domain, projectVersion, debug) + if not wfRequest: + if debug: + print('DEBUG: followers list webfinger failed for ' + handle) + return 1 + if not isinstance(wfRequest, dict): + print('WARN: followers list Webfinger for ' + handle + + ' did not return a dict. ' + str(wfRequest)) + return 1 + + postToBox = 'outbox' + + # get the actor inbox for the To handle + (inboxUrl, pubKeyId, pubKey, + fromPersonId, sharedInbox, avatarUrl, + displayName) = getPersonBox(baseDir, session, wfRequest, personCache, + projectVersion, httpPrefix, nickname, + domain, postToBox, 52025) + + if not inboxUrl: + if debug: + print('DEBUG: followers list no ' + postToBox + + ' was found for ' + handle) + return 3 + if not fromPersonId: + if debug: + print('DEBUG: followers list no actor was found for ' + handle) + return 4 + + authHeader = createBasicAuthHeader(nickname, password) + + headers = { + 'host': domain, + 'Content-type': 'application/json', + 'Authorization': authHeader + } + + if pageNumber < 1: + pageNumber = 1 + url = followActor + '/followers?page=' + str(pageNumber) + followersJson = \ + getJson(session, url, headers, {}, debug, + __version__, httpPrefix, domain, 10, True) + if not followersJson: + if debug: + print('DEBUG: GET followers list failed for c2s to ' + url) + return 5 + + if debug: + print('DEBUG: c2s GET followers list request success') + + return followersJson + + def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}: """In a shared inbox if we receive a post we know who it's from and if it's addressed to followers then we need to get a list of those. From 76fba0e5ddb42786c3804f546bc51fad2d8ac36f Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 15:07:17 +0000 Subject: [PATCH 5/9] Getting follow requests collection via c2s --- daemon.py | 11 ++++++++ epicyon.py | 35 +++++++++++++++++++++++++ follow.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/daemon.py b/daemon.py index ea7d66939..d37f5f32b 100644 --- a/daemon.py +++ b/daemon.py @@ -10616,6 +10616,17 @@ class PubServer(BaseHTTPRequestHandler): self.server.followingItemsPerPage, self.server.debug, 'followers') return + elif '/followrequests?page=' in self.path: + self._getFollowingJson(self.server.baseDir, + self.path, + callingDomain, + self.server.httpPrefix, + self.server.domain, + self.server.port, + self.server.followingItemsPerPage, + self.server.debug, + 'followrequests') + return # authorized endpoint used for TTS of posts # arriving in your inbox diff --git a/epicyon.py b/epicyon.py index 7a1437404..a6d265f25 100644 --- a/epicyon.py +++ b/epicyon.py @@ -46,6 +46,7 @@ from filters import addFilter from filters import removeFilter from pprint import pprint from daemon import runDaemon +from follow import getFollowRequestsViaServer from follow import getFollowingViaServer from follow import getFollowersViaServer from follow import clearFollows @@ -262,6 +263,12 @@ parser.add_argument("--followersList", const=True, default=False, help="Get the followers list. Use nickname and " + "domain options to specify the account") +parser.add_argument("--followRequestsList", + dest='followRequestsList', + type=str2bool, nargs='?', + const=True, default=False, + help="Get the follow requests list. Use nickname and " + + "domain options to specify the account") parser.add_argument("--repliesEnabled", "--commentsEnabled", dest='commentsEnabled', type=str2bool, nargs='?', @@ -1554,6 +1561,34 @@ if args.followersList: pprint(followersJson) sys.exit() +if args.followRequestsList: + # follow requests list via c2s protocol + if not args.nickname: + print('Please specify the nickname for the account with --nickname') + sys.exit() + if not args.password: + args.password = getpass.getpass('Password: ') + if not args.password: + print('Specify a password with the --password option') + sys.exit() + args.password = args.password.replace('\n', '') + + session = createSession(proxyType) + personCache = {} + cachedWebfingers = {} + followHttpPrefix = httpPrefix + + followRequestsJson = \ + getFollowRequestsViaServer(baseDir, session, + args.nickname, args.password, + domain, port, + httpPrefix, args.pageNumber, + cachedWebfingers, personCache, + debug, __version__) + if followRequestsJson: + pprint(followRequestsJson) + sys.exit() + nickname = 'admin' if args.domain: domain = args.domain diff --git a/follow.py b/follow.py index a39e2f803..87cccca78 100644 --- a/follow.py +++ b/follow.py @@ -1281,6 +1281,82 @@ def getFollowersViaServer(baseDir: str, session, return followersJson +def getFollowRequestsViaServer(baseDir: str, session, + nickname: str, password: str, + domain: str, port: int, + httpPrefix: str, pageNumber: int, + cachedWebfingers: {}, personCache: {}, + debug: bool, projectVersion: str) -> {}: + """Gets a page from the follow requests collection as json + """ + if not session: + print('WARN: No session for getFollowRequestsViaServer') + return 6 + + domainFull = getFullDomain(domain, port) + + followActor = httpPrefix + '://' + domainFull + '/users/' + nickname + handle = httpPrefix + '://' + domainFull + '/@' + nickname + + # lookup the inbox for the To handle + wfRequest = \ + webfingerHandle(session, handle, httpPrefix, cachedWebfingers, + domain, projectVersion, debug) + if not wfRequest: + if debug: + print('DEBUG: follow requests list webfinger failed for ' + + handle) + return 1 + if not isinstance(wfRequest, dict): + print('WARN: follow requests list Webfinger for ' + handle + + ' did not return a dict. ' + str(wfRequest)) + return 1 + + postToBox = 'outbox' + + # get the actor inbox for the To handle + (inboxUrl, pubKeyId, pubKey, + fromPersonId, sharedInbox, avatarUrl, + displayName) = getPersonBox(baseDir, session, wfRequest, personCache, + projectVersion, httpPrefix, nickname, + domain, postToBox, 42759) + + if not inboxUrl: + if debug: + print('DEBUG: follow requests list no ' + postToBox + + ' was found for ' + handle) + return 3 + if not fromPersonId: + if debug: + print('DEBUG: follow requests list no actor was found for ' + + handle) + return 4 + + authHeader = createBasicAuthHeader(nickname, password) + + headers = { + 'host': domain, + 'Content-type': 'application/json', + 'Authorization': authHeader + } + + if pageNumber < 1: + pageNumber = 1 + url = followActor + '/followrequests?page=' + str(pageNumber) + followersJson = \ + getJson(session, url, headers, {}, debug, + __version__, httpPrefix, domain, 10, True) + if not followersJson: + if debug: + print('DEBUG: GET follow requests list failed for c2s to ' + url) + return 5 + + if debug: + print('DEBUG: c2s GET follow requests list request success') + + return followersJson + + def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}: """In a shared inbox if we receive a post we know who it's from and if it's addressed to followers then we need to get a list of those. From 542f34a7d167e45aded695f29407d57e8e0a439e Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 16:00:36 +0000 Subject: [PATCH 6/9] Show follow requests via desktop client command --- desktop_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/desktop_client.py b/desktop_client.py index 3347b967c..1414fd6bb 100644 --- a/desktop_client.py +++ b/desktop_client.py @@ -29,6 +29,7 @@ from speaker import getSpeakerRate from speaker import getSpeakerRange from like import sendLikeViaServer from like import sendUndoLikeViaServer +from follow import getFollowRequestsViaServer from follow import sendFollowRequestViaServer from follow import sendUnfollowRequestViaServer from posts import sendBlockViaServer @@ -1887,6 +1888,30 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, True, __version__) refreshTimeline = True print('') + elif (commandStr == 'follow requests' or + commandStr.startswith('follow requests ')): + currPage = 1 + if ' ' in commandStr: + pageNum = commandStr.split(' ')[-1].strip() + if pageNum.isdigit(): + currPage = int(pageNum) + followRequestsJson = \ + getFollowRequestsViaServer(baseDir, session, + nickname, password, + domain, port, + httpPrefix, currPage, + cachedWebfingers, personCache, + debug, __version__) + if followRequestsJson: + print('') + for item in followRequestsJson['orderedItems']: + handleNickname = getNicknameFromActor(item) + handleDomain, handlePort = getDomainFromActor(item) + handleDomainFull = \ + getFullDomain(handleDomain, handlePort) + print(' 👤 ' + + handleNickname + '@' + handleDomainFull) + print('') elif (commandStr == 'follow' or commandStr.startswith('follow ')): if commandStr == 'follow': From 16a93ac74d31eeb747108790f6399fa15b45d0f1 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 17:07:14 +0000 Subject: [PATCH 7/9] Show follow requests with timeline in desktop client --- desktop_client.py | 82 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/desktop_client.py b/desktop_client.py index 1414fd6bb..b01abdffa 100644 --- a/desktop_client.py +++ b/desktop_client.py @@ -872,7 +872,10 @@ def _highlightText(text: str) -> str: return '\33[7m' + text + '\33[0m' -def _desktopShowBox(yourActor: str, boxName: str, boxJson: {}, +def _desktopShowBox(indent: str, + followRequestsJson: {}, + yourActor: str, boxName: str, boxJson: {}, + translate: {}, screenreader: str, systemLanguage: str, espeak, pageNumber=1, newReplies=False, @@ -882,7 +885,6 @@ def _desktopShowBox(yourActor: str, boxName: str, boxJson: {}, numberWidth = 2 nameWidth = 16 contentWidth = 50 - indent = ' ' # title _desktopClearScreen() @@ -1024,6 +1026,9 @@ def _desktopShowBox(yourActor: str, boxName: str, boxJson: {}, print(lineStr) ctr += 1 + if followRequestsJson: + _desktopShowFollowRequests(followRequestsJson, translate) + print('') # say the post number range @@ -1176,6 +1181,24 @@ def _desktopNewDMbase(session, toHandle: str, _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak) +def _desktopShowFollowRequests(followRequestsJson: {}, translate: {}) -> None: + """Shows any follow requests + """ + if not followRequestsJson['orderedItems']: + return + indent = ' ' + print('') + print(indent + 'Follow requests:') + print('') + for item in followRequestsJson['orderedItems']: + handleNickname = getNicknameFromActor(item) + handleDomain, handlePort = getDomainFromActor(item) + handleDomainFull = \ + getFullDomain(handleDomain, handlePort) + print(indent + ' 👤 ' + + handleNickname + '@' + handleDomainFull) + + def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, nickname: str, domain: str, port: int, password: str, screenreader: str, @@ -1297,6 +1320,14 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) + followRequestsJson = \ + getFollowRequestsViaServer(baseDir, session, + nickname, password, + domain, port, + httpPrefix, 1, + cachedWebfingers, personCache, + debug, __version__) + if not (currTimeline == 'inbox' and pageNumber == 1): # monitor the inbox to generate notifications inboxJson = c2sBoxJson(baseDir, session, @@ -1330,7 +1361,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, timelineFirstId = _getFirstItemId(boxJson) if timelineFirstId != prevTimelineFirstId: _desktopClearScreen() - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, None, systemLanguage, espeak, pageNumber, newRepliesExist, @@ -1368,7 +1401,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1383,7 +1418,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1399,7 +1436,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1416,7 +1455,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1441,7 +1482,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, currTimeline, pageNumber, debug) if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1451,7 +1494,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, else: postIndexStr = commandStr.split('read ')[1] if boxJson and postIndexStr.isdigit(): - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1480,7 +1525,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, postIndexStr = commandStr.split('profile ')[1] if not actorJson and boxJson and postIndexStr.isdigit(): - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) @@ -1903,15 +1950,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, cachedWebfingers, personCache, debug, __version__) if followRequestsJson: - print('') - for item in followRequestsJson['orderedItems']: - handleNickname = getNicknameFromActor(item) - handleDomain, handlePort = getDomainFromActor(item) - handleDomainFull = \ - getFullDomain(handleDomain, handlePort) - print(' 👤 ' + - handleNickname + '@' + handleDomainFull) - print('') + _desktopShowFollowRequests(followRequestsJson, translate) + print('') elif (commandStr == 'follow' or commandStr.startswith('follow ')): if commandStr == 'follow': @@ -2130,7 +2170,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, if refreshTimeline: if boxJson: - _desktopShowBox(yourActor, currTimeline, boxJson, + _desktopShowBox(indent, followRequestsJson, + yourActor, currTimeline, boxJson, + translate, screenreader, systemLanguage, espeak, pageNumber, newRepliesExist, newDMsExist) From 2466b8cbb2a1a04e772a3336a4047d0af87ee92d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 17:24:22 +0000 Subject: [PATCH 8/9] Don't show indicators --- desktop_client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/desktop_client.py b/desktop_client.py index b01abdffa..ced2af462 100644 --- a/desktop_client.py +++ b/desktop_client.py @@ -896,11 +896,10 @@ def _desktopShowBox(indent: str, else: boxNameStr = boxName titleStr = _highlightText(boxNameStr.upper()) - - if newDMs: - notificationIcons += ' 📩' - if newReplies: - notificationIcons += ' 📨' + # if newDMs: + # notificationIcons += ' 📩' + # if newReplies: + # notificationIcons += ' 📨' if notificationIcons: while len(titleStr) < 95 - len(notificationIcons): From 54a99b65d3e9ab50dc7bd9831f4f897a7aab456b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 24 Mar 2021 19:48:34 +0000 Subject: [PATCH 9/9] Following and followers commands for desktop client --- README_commandline.md | 2 ++ desktop_client.py | 72 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/README_commandline.md b/README_commandline.md index 14e54a9ee..28206529a 100644 --- a/README_commandline.md +++ b/README_commandline.md @@ -474,6 +474,8 @@ prev Previous page in the timeline read [post number] Read a post from a timeline open [post number] Open web links within a timeline post profile [post number] Show profile for the person who made the given post +following [page number] Show accounts that you are following +followers [page number] Show accounts that are following you ``` If you have a GPG key configured on your local system and are sending a direct message to someone who has a PGP key (the exported key, not just the key ID) set as a tag on their profile then it will try to encrypt the message automatically. So under some conditions end-to-end encryption is possible, such that the instance server only sees ciphertext. Conversely, for arriving direct messages if they are PGP encrypted then the desktop client will try to obtain the relevant public key and decrypt. diff --git a/desktop_client.py b/desktop_client.py index ced2af462..70c1c847f 100644 --- a/desktop_client.py +++ b/desktop_client.py @@ -30,6 +30,8 @@ from speaker import getSpeakerRange from like import sendLikeViaServer from like import sendUndoLikeViaServer from follow import getFollowRequestsViaServer +from follow import getFollowingViaServer +from follow import getFollowersViaServer from follow import sendFollowRequestViaServer from follow import sendUnfollowRequestViaServer from posts import sendBlockViaServer @@ -111,6 +113,10 @@ def _desktopHelp() -> None: 'Open web links within a timeline post') print(indent + 'profile [post number] ' + 'Show profile for the person who made the given post') + print(indent + 'following [page number] ' + + 'Show accounts that you are following') + print(indent + 'followers [page number] ' + + 'Show accounts that are following you') print('') @@ -1198,6 +1204,28 @@ def _desktopShowFollowRequests(followRequestsJson: {}, translate: {}) -> None: handleNickname + '@' + handleDomainFull) +def _desktopShowFollowing(followingJson: {}, translate: {}, + pageNumber: int, indent: str, + followType='following') -> None: + """Shows a page of accounts followed + """ + if not followingJson['orderedItems']: + return + print('') + if followType == 'following': + print(indent + 'Following page ' + str(pageNumber)) + elif followType == 'followers': + print(indent + 'Followers page ' + str(pageNumber)) + print('') + for item in followingJson['orderedItems']: + handleNickname = getNicknameFromActor(item) + handleDomain, handlePort = getDomainFromActor(item) + handleDomainFull = \ + getFullDomain(handleDomain, handlePort) + print(indent + ' 👤 ' + + handleNickname + '@' + handleDomainFull) + + def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, nickname: str, domain: str, port: int, password: str, screenreader: str, @@ -1949,7 +1977,49 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, cachedWebfingers, personCache, debug, __version__) if followRequestsJson: - _desktopShowFollowRequests(followRequestsJson, translate) + if isinstance(followRequestsJson, dict): + _desktopShowFollowRequests(followRequestsJson, + translate) + print('') + elif (commandStr == 'following' or + commandStr.startswith('following ')): + currPage = 1 + if ' ' in commandStr: + pageNum = commandStr.split(' ')[-1].strip() + if pageNum.isdigit(): + currPage = int(pageNum) + followingJson = \ + getFollowingViaServer(baseDir, session, + nickname, password, + domain, port, + httpPrefix, currPage, + cachedWebfingers, personCache, + debug, __version__) + if followingJson: + if isinstance(followingJson, dict): + _desktopShowFollowing(followingJson, translate, + currPage, indent, + 'following') + print('') + elif (commandStr == 'followers' or + commandStr.startswith('followers ')): + currPage = 1 + if ' ' in commandStr: + pageNum = commandStr.split(' ')[-1].strip() + if pageNum.isdigit(): + currPage = int(pageNum) + followersJson = \ + getFollowersViaServer(baseDir, session, + nickname, password, + domain, port, + httpPrefix, currPage, + cachedWebfingers, personCache, + debug, __version__) + if followersJson: + if isinstance(followersJson, dict): + _desktopShowFollowing(followersJson, translate, + currPage, indent, + 'followers') print('') elif (commandStr == 'follow' or commandStr.startswith('follow ')):