فهرست منبع

Detect suspended accounts

Zed 6 سال پیش
والد
کامیت
8a6978cf74
8فایلهای تغییر یافته به همراه38 افزوده شده و 6 حذف شده
  1. 13 3
      src/api/tweet.nim
  2. 1 0
      src/cache.nim
  3. 3 0
      src/formatters.nim
  4. 11 1
      src/parser.nim
  5. 5 0
      src/routes/rss.nim
  6. 3 0
      src/routes/timeline.nim
  7. 1 0
      src/types.nim
  8. 1 2
      tests/test_profile.py

+ 13 - 3
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
 

+ 1 - 0
src/cache.nim

@@ -15,6 +15,7 @@ withDb:
   except DbError: discard
 
   safeAddColumn Profile.lowername
+  safeAddColumn Profile.suspended
 
 var profileCacheTime = initDuration(minutes=10)
 

+ 3 - 0
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"

+ 11 - 1
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")

+ 5 - 0
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
 

+ 3 - 0
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")])

+ 1 - 0
src/types.nim

@@ -25,6 +25,7 @@ dbTypes:
       media*: string
       verified*: bool
       protected*: bool
+      suspended*: bool
       joinDate* {.
           dbType: "INTEGER"
           parseIt: it.i.fromUnix()

+ 1 - 2
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):