diff --git a/src/api/tweet.nim b/src/api/tweet.nim index 8f5ff17..f724134 100644 --- a/src/api/tweet.nim +++ b/src/api/tweet.nim @@ -1,4 +1,4 @@ -import asyncdispatch, strutils, uri +import asyncdispatch, strutils, uri, httpclient, json, xmltree, htmlparser import ".."/[types, parser] import utils, consts, media @@ -7,11 +7,21 @@ proc getTweet*(username, id, after, agent: string): Future[Conversation] {.async let headers = genHeaders({ "pragma": "no-cache", - "x-previous-page-name": "profile" + "x-previous-page-name": "profile", + "accept": htmlAccept }, agent, base, xml=true) url = base / username / tweetUrl / id ? {"max_position": after} - html = await fetchHtml(url, headers) + + newClient() + var html: XmlNode + try: + let resp = await client.get($url) + if resp.code == Http403 and "suspended" in (await resp.body): + return Conversation(tweet: Tweet(tombstone: "User has been suspended")) + html = parseHtml(await resp.body) + except: + discard if html == nil: return diff --git a/src/cache.nim b/src/cache.nim index cfc3717..f3a2bb0 100644 --- a/src/cache.nim +++ b/src/cache.nim @@ -15,6 +15,7 @@ withDb: except DbError: discard safeAddColumn Profile.lowername + safeAddColumn Profile.suspended var profileCacheTime = initDuration(minutes=10) diff --git a/src/formatters.nim b/src/formatters.nim index bd11efc..8701fee 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -127,3 +127,6 @@ proc getLocation*(u: Profile | Tweet): (string, string) = let loc = u.location.split(":") let url = if loc.len > 1: "/search?q=place:" & loc[1] else: "" (loc[0], url) + +proc getSuspended*(username: string): string = + &"User \"{username}\" has been suspended" diff --git a/src/parser.nim b/src/parser.nim index 11763cc..929f0aa 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -2,9 +2,19 @@ import xmltree, sequtils, strutils, json, options import types, parserutils, formatters +proc parseJsonData*(node: XmlNode): JsonNode = + let jsonData = node.selectAttr("input.json-data", "value") + if jsonData.len > 0: + return parseJson(jsonData) + proc parseTimelineProfile*(node: XmlNode): Profile = let profile = node.select(".ProfileHeaderCard") - if profile == nil: return + if profile == nil: + let data = parseJsonData(node) + if data != nil and data{"sectionName"}.getStr == "suspended": + let username = data{"internalReferer"}.getStr.strip(chars={'/'}) + return Profile(username: username, suspended: true) + return let pre = ".ProfileHeaderCard-" let username = profile.getUsername(pre & "screenname") diff --git a/src/routes/rss.nim b/src/routes/rss.nim index f6d4f69..481fdf7 100644 --- a/src/routes/rss.nim +++ b/src/routes/rss.nim @@ -29,6 +29,9 @@ proc showRss*(req: Request; hostname: string; query: Query): Future[(string, str userpic: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png" ) + if profile.suspended: + return (profile.username, "suspended") + if timeline != nil: let rss = renderTimelineRss(timeline, profile, hostname, multi=(names.len > 1)) return (rss, timeline.minId) @@ -36,6 +39,8 @@ proc showRss*(req: Request; hostname: string; query: Query): Future[(string, str template respRss*(rss, minId) = if rss.len == 0: resp Http404, showError("User \"" & @"name" & "\" not found", cfg) + elif minId == "suspended": + resp Http404, showError(getSuspended(rss), cfg) let headers = {"Content-Type": "application/rss+xml;charset=utf-8", "Min-Id": minId} resp Http200, headers, rss diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 916d09f..156b062 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -71,6 +71,9 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs; (p, t) = await fetchSingleTimeline(names[0], after, agent, query) r = await rail if p.username.len == 0: return + if p.suspended: + return showError(getSuspended(p.username), cfg) + let pHtml = renderProfile(p, t, r, prefs, getPath()) return renderMain(pHtml, request, cfg, pageTitle(p), pageDesc(p), rss=rss, images = @[p.getUserpic("_200x200")]) diff --git a/src/types.nim b/src/types.nim index b349496..80a8a50 100644 --- a/src/types.nim +++ b/src/types.nim @@ -25,6 +25,7 @@ dbTypes: media*: string verified*: bool protected*: bool + suspended*: bool joinDate* {. dbType: "INTEGER" parseIt: it.i.fromUnix() diff --git a/tests/test_profile.py b/tests/test_profile.py index 15c5240..cf0b4f3 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -71,9 +71,8 @@ class ProfileTest(BaseTestCase): self.assert_text(f'User "{username}" not found') def test_suspended(self): - # TODO: detect suspended self.open_nitter('test') - self.assert_text(f'User "test" not found') + self.assert_text(f'User "test" has been suspended') @parameterized.expand(banner_color) def test_banner_color(self, username, color):