bountysource.nim 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # Based on bountysource.cr located at https://github.com/crystal-lang/crystal-website/blob/master/scripts/bountysource.cr
  2. import httpclient, asyncdispatch, json, strutils, os, strtabs, sequtils, future,
  3. algorithm, times
  4. type
  5. BountySource = ref object
  6. client: AsyncHttpClient
  7. team: string
  8. Sponsor = object
  9. name, url, logo: string
  10. amount, allTime: float
  11. since: TimeInfo
  12. const
  13. team = "nim"
  14. apiUrl = "https://api.bountysource.com"
  15. githubApiUrl = "https://api.github.com"
  16. proc newBountySource(team, token: string): BountySource =
  17. result = BountySource(
  18. client: newAsyncHttpClient(userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"),
  19. team: team
  20. )
  21. # Set up headers
  22. result.client.headers["Accept"] = "application/vnd.bountysource+json; version=2"
  23. result.client.headers["Authorization"] = "token " & token
  24. result.client.headers["Referer"] = "https://salt.bountysource.com/teams/nim/admin/supporters"
  25. result.client.headers["Origin"] = "https://salt.bountysource.com/"
  26. proc getSupporters(self: BountySource): Future[JsonNode] {.async.} =
  27. let response = await self.client.get(apiUrl &
  28. "/supporters?order=monthly&per_page=200&team_slug=" & self.team)
  29. doAssert response.status.startsWith($Http200)
  30. return parseJson(await response.body)
  31. proc getGithubUser(username: string): Future[JsonNode] {.async.} =
  32. let client = newAsyncHttpClient()
  33. let response = await client.get(githubApiUrl & "/users/" & username)
  34. if response.status.startsWith($Http200):
  35. return parseJson(await response.body)
  36. else:
  37. echo("Could not get Github user: ", username, ". ", response.status)
  38. return nil
  39. proc processSupporters(supporters: JsonNode) =
  40. var before = supporters.elems.len
  41. supporters.elems.keepIf(
  42. item => item["display_name"].getStr != "Anonymous"
  43. )
  44. echo("Discarded ", before - supporters.elems.len, " anonymous sponsors.")
  45. echo("Found ", supporters.elems.len, " named sponsors.")
  46. supporters.elems.sort(
  47. (x, y) => cmp(y["alltime_amount"].getFNum, x["alltime_amount"].getFNum)
  48. )
  49. proc quote(text: string): string =
  50. if {' ', ','} in text:
  51. return "\"" & text & "\""
  52. else:
  53. return text
  54. proc getLevel(amount: float): int =
  55. result = 0
  56. const levels = [250, 150, 75, 25, 10, 5, 1]
  57. for i in levels:
  58. if amount.int <= i:
  59. result = i
  60. proc writeCsv(sponsors: seq[Sponsor], filename="sponsors.new.csv") =
  61. var csv = ""
  62. csv.add "logo, name, url, this_month, all_time, since, level\n"
  63. for sponsor in sponsors:
  64. csv.add "$#,$#,$#,$#,$#,$#,$#\n" % [
  65. sponsor.logo.quote, sponsor.name.quote,
  66. sponsor.url.quote, $sponsor.amount.int,
  67. $sponsor.allTime.int, sponsor.since.format("MMM d, yyyy").quote,
  68. $sponsor.amount.getLevel
  69. ]
  70. writeFile(filename, csv)
  71. echo("Written csv file to ", filename)
  72. when isMainModule:
  73. if paramCount() == 0:
  74. quit("You need to specify the BountySource access token on the command\n" &
  75. "line, you can find it by going onto https://www.bountysource.com/people/25278-dom96\n" &
  76. "and looking at your browser's network inspector tab to see the token being\n" &
  77. "sent to api.bountysource.com")
  78. let token = paramStr(1)
  79. let bountysource = newBountySource(team, token)
  80. echo("Getting sponsors...")
  81. let supporters = waitFor bountysource.getSupporters()
  82. processSupporters(supporters)
  83. echo("Generating sponsors list... (please be patient)")
  84. var activeSponsors: seq[Sponsor] = @[]
  85. var inactiveSponsors: seq[Sponsor] = @[]
  86. for supporter in supporters:
  87. let name = supporter["display_name"].getStr
  88. var url = ""
  89. let ghUser = waitFor getGithubUser(name)
  90. if not ghUser.isNil:
  91. if ghUser["blog"].kind != JNull:
  92. url = ghUser["blog"].getStr
  93. else:
  94. url = ghUser["html_url"].getStr
  95. if url.len > 0 and not url.startsWith("http"):
  96. url = "http://" & url
  97. let amount = supporter["monthly_amount"].getFNum()
  98. # Only show URL when user donated at least $5.
  99. if amount < 5:
  100. url = ""
  101. #let supporter = getSupporter(supporters,
  102. # supportLevel["owner"]["display_name"].getStr)
  103. #if supporter.isNil: continue
  104. var logo = ""
  105. if amount >= 75:
  106. discard # TODO
  107. let sponsor = Sponsor(name: name, url: url, logo: logo, amount: amount,
  108. allTime: supporter["alltime_amount"].getFNum(),
  109. since: parse(supporter["created_at"].getStr, "yyyy-MM-dd'T'hh:mm:ss")
  110. )
  111. if supporter["monthly_amount"].getFNum > 0.0:
  112. activeSponsors.add(sponsor)
  113. else:
  114. inactiveSponsors.add(sponsor)
  115. echo("Generated ", activeSponsors.len, " active sponsors")
  116. echo("Generated ", inactiveSponsors.len, " inactive sponsors")
  117. writeCsv(activeSponsors)
  118. writeCsv(inactiveSponsors, "inactive_sponsors.new.csv")