├── .gitignore ├── LICENSE ├── README.md ├── github_api.nimble └── github_api ├── client.nim ├── gist.nim ├── nim.cfg ├── repository.nim └── search.nim /.gitignore: -------------------------------------------------------------------------------- 1 | *nimcache 2 | bin 3 | test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2017 Christopher Watson 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nim GitHub API Wrapper 2 | 3 | This is a wrapper for the GitHub API written in Nim. To get started add this to your `.nimble` file: 4 | 5 | ```nim 6 | requires "github_api >= 0.1.0" 7 | ``` 8 | 9 | The import it into your project and create an instance of the `GithubApiClient` 10 | 11 | ```nim 12 | import github_api 13 | 14 | var client = newGithubApiClient() 15 | 16 | # Or, with a auth token 17 | 18 | var client = newGithubApiClient("b24123832b745c3fe5e4e6606het7co73e31f21") 19 | ``` 20 | 21 | ## Usage 22 | 23 | After importing `github_api` and creating an instance of the `GithubApiClient` you can use the client to easily send requests, authenticated or not, to the GitHub API. Note: some requests do require authentication. 24 | 25 | ```nim 26 | import json, streams 27 | import github_api 28 | 29 | var client = newGithubApiClient("b24123832b745c3fe5e4e6606het7co73e31f21") 30 | 31 | var res = client.listUserRepos("watzon") 32 | 33 | if res.status.startsWith("200"): 34 | var repos = parseJson(res.bodyStream.readAll()) 35 | echo(repos.pretty()) 36 | ``` 37 | 38 | ## Roadmap 39 | 40 | I have gotten a lot done on this in a very short period of time, but there is still a lot left to do. Here are the planned features: 41 | 42 | - [x] Base Client 43 | - [ ] Data Types 44 | - [ ] Activity 45 | - [x] Gists 46 | - [ ] Gist Data 47 | - [ ] Apps 48 | - [ ] Migration 49 | - [ ] Organizations 50 | - [ ] Projects 51 | - [ ] Pull Requests 52 | - [ ] Reactions 53 | - [x] Repositories 54 | - [x] Search 55 | - [ ] Miscellaneous 56 | - [ ] Codes of Conduct 57 | - [ ] Emojis 58 | - [ ] Gitignore 59 | - [ ] Licenses 60 | - [ ] Markdown 61 | - [ ] Meta 62 | - [ ] Rate Limit 63 | - [ ] OAuth Authorizations 64 | - [ ] Basic Authentication -------------------------------------------------------------------------------- /github_api.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Chris Watson" 5 | description = "Connector for the GitHub API v3" 6 | license = "WTFPL" 7 | 8 | skipDirs = @["test"] 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 0.17.2" 13 | -------------------------------------------------------------------------------- /github_api/client.nim: -------------------------------------------------------------------------------- 1 | ## This module is the backbone of the github_api wrapper. 2 | ## It provides a layer of abstraction on top of Nim's ``HttpClient`` 3 | ## and allows you to persist an access token. 4 | ## 5 | ## Getting started 6 | ## =============== 7 | ## 8 | ## To get started using `github_api` you will need to get an 9 | ## instance of the ``GithubApiClient`` object. To do so, 10 | ## just call the ``newGithubApiClient`` proc. 11 | ## 12 | ## .. code-block:: nim 13 | ## 14 | ## import github_api 15 | ## 16 | ## var client = newGithubApiClient() 17 | ## 18 | ## Alternately, you can instantiate a ``GithubApiClient`` instance 19 | ## with an access token. 20 | ## 21 | ## .. code-block:: nim 22 | ## 23 | ## import github_api 24 | ## 25 | ## var client = newGithubApiClient("b24123832b745c3fe5e4e6606het7co73e31f21") 26 | 27 | import httpclient, tables, ospaths, strutils, json, cgi 28 | 29 | type 30 | GithubApiClient* = ref object 31 | ## The client object is responsible for connecting everything else to the GitHub API. It 32 | ## wraps `HttpClient` and provides a layer of abstraction on top. 33 | httpClient: HttpClient 34 | baseUrl*: string 35 | accessToken*: string 36 | 37 | proc toQueryString*(json: JsonNode): string = 38 | ## Take a json node and turn it into a query string to be sent to GitHub 39 | if json == nil: return "" 40 | result = "" 41 | var 42 | sep = '?' 43 | strVal = "" 44 | for key, val in json: 45 | case val.kind 46 | of JString: 47 | strVal = val.getStr() 48 | of JNull: continue 49 | else: 50 | strVal = $val 51 | result.add(sep) 52 | result.add(key) 53 | result.add('=') 54 | result.add(encodeUrl(strVal)) 55 | sep = '&' 56 | 57 | proc newGithubApiClient*( 58 | accessToken: string = nil, 59 | userAgent = defUserAgent, 60 | maxRedirects = 5, 61 | proxy: Proxy = nil, 62 | timeout = -1): GithubApiClient = 63 | ## Create a new instance of the ``GithubApiClient`` object 64 | 65 | var httpClient = newHttpClient(userAgent, maxRedirects, nil, proxy, timeout) 66 | httpClient.headers = newHttpHeaders({ 67 | # "Accept": "application/vnd.github.v3+json", 68 | "Accept": "application/vnd.github.mercy-preview+json", # Allows use of preview APIs 69 | "Content-Type": "application/json" 70 | }) 71 | 72 | GithubApiClient( 73 | httpClient: httpClient, 74 | baseUrl: "https://api.github.com", 75 | accessToken: accessToken 76 | ) 77 | 78 | proc seqTo[T](data: string): seq[T] = 79 | var json = parseJson(data) 80 | result = @[] 81 | for repo in json: 82 | result.add(repo.to(T)) 83 | 84 | proc request*(client: GithubApiClient, path: string, body: string = "", query: JsonNode = nil, httpMethod: string = $HttpGet): Response = 85 | var url = client.baseUrl / path & toQueryString(query) 86 | var headers: HttpHeaders = nil 87 | if client.accessToken != nil: 88 | headers = newHttpHeaders({ "Authorization": "token " & client.accessToken }) 89 | client.httpClient.request(url, httpMethod, body, headers) -------------------------------------------------------------------------------- /github_api/gist.nim: -------------------------------------------------------------------------------- 1 | import httpclient, ospaths, json 2 | import ./client 3 | 4 | type 5 | GistFileArray = JsonNode 6 | 7 | proc listGists*( 8 | client: GithubApiClient, 9 | since: string = nil, 10 | limit: int = 0, 11 | page: int = 1): Response = 12 | 13 | var data = %*{ 14 | "since": since, 15 | "per_page": limit, 16 | "page": page 17 | } 18 | var path = "/gists" 19 | client.request(path, query = data) 20 | 21 | proc listUserGists*( 22 | client: GithubApiClient, 23 | username: string, 24 | since: string = nil, 25 | limit: int = 0, 26 | page: int = 1): Response = 27 | 28 | var data = %*{ 29 | "since": since, 30 | "per_page": limit, 31 | "page": page 32 | } 33 | var path = "/users" / username / "gists" 34 | client.request(path, query = data) 35 | 36 | proc listAllGists*( 37 | client: GithubApiClient, 38 | since: string = nil, 39 | limit: int = 0, 40 | page: int = 1): Response = 41 | 42 | var data = %*{ 43 | "since": since, 44 | "per_page": limit, 45 | "page": page 46 | } 47 | var path = "/gists/public" 48 | client.request(path, query = data) 49 | 50 | proc getStarredGists*( 51 | client: GithubApiClient, 52 | since: string = nil, 53 | limit: int = 0, 54 | page: int = 1): Response = 55 | 56 | var data = %*{ 57 | "since": since, 58 | "per_page": limit, 59 | "page": page 60 | } 61 | var path = "/gists/starred" 62 | client.request(path, query = data) 63 | 64 | proc getGist*( 65 | client: GithubApiClient, 66 | id: string): Response = 67 | 68 | var path = "/gists" / id 69 | client.request(path) 70 | 71 | proc getGist*( 72 | client: GithubApiClient, 73 | id: string, 74 | sha: string): Response = 75 | 76 | var path = "/gists" / id / sha 77 | client.request(path) 78 | 79 | proc createGist*( 80 | client: GithubApiClient, 81 | files: GistFileArray, 82 | description: string = nil, 83 | public: bool = false): Response = 84 | 85 | var data = %*{ 86 | "files": files, 87 | "description": description, 88 | "public": public 89 | } 90 | var path = "/gists" 91 | client.request(path, body = $data, httpMethod = $HttpPost) 92 | 93 | proc editGist*( 94 | client: GithubApiClient, 95 | id: string, 96 | files: GistFileArray, 97 | description: string = nil): Response = 98 | 99 | var data = %*{ 100 | "files": files, 101 | "description": description 102 | } 103 | var path = "/gists" / id 104 | client.request(path, body = $data, httpMethod = $HttpPatch) 105 | 106 | proc getGistCommits*( 107 | client: GithubApiClient, 108 | id: string, 109 | limit: int = 0, 110 | page: int = 1): Response = 111 | 112 | var data = %*{ 113 | "per_page": limit, 114 | "page": page 115 | } 116 | var path = "/gists" / id / "commits" 117 | client.request(path) 118 | 119 | proc starGist*( 120 | client: GithubApiClient, 121 | id: string): Response = 122 | 123 | var path = "/gists" / id / "star" 124 | client.request(path, httpMethod = $HttpPut) 125 | 126 | proc unstarGist*( 127 | client: GithubApiClient, 128 | id: string): Response = 129 | 130 | var path = "/gists" / id / "star" 131 | client.request(path, httpMethod = $HttpDelete) 132 | 133 | proc getGistStarStatus*( 134 | client: GithubApiClient, 135 | id: string): Response = 136 | 137 | var path = "/gists" / id / "star" 138 | client.request(path) 139 | 140 | proc forkGist*( 141 | client: GithubApiClient, 142 | id: string): Response = 143 | 144 | var path = "/gists" / id / "forks" 145 | client.request(path, httpMethod = $HttpPost) 146 | 147 | proc getGistForks*( 148 | client: GithubApiClient, 149 | id: string, 150 | limit: int = 0, 151 | page: int = 1): Response = 152 | 153 | var data = %*{ 154 | "per_page": limit, 155 | "page": page 156 | } 157 | var path = "/gists" / id / "forks" 158 | client.request(path) 159 | 160 | proc deleteGist*( 161 | client: GithubApiClient, 162 | id: string): Response = 163 | 164 | var path = "/gists" / id 165 | client.request(path, httpMethod = $HttpDelete) -------------------------------------------------------------------------------- /github_api/nim.cfg: -------------------------------------------------------------------------------- 1 | path: ".." 2 | 3 | # SSL 4 | define: ssl 5 | 6 | define: nimSetUtf8CodePage 7 | hint[User]:off 8 | hint[XDeclaredButNotUsed]:off 9 | warning[ProveField]:off 10 | warning[ShadowIdent]:off 11 | warning[GcUnsafe]:off 12 | warning[GcUnsafe2]:off 13 | 14 | @if crosswin: 15 | gcc.linkerexe = "x86_64-w64-mingw32-gcc" 16 | gcc.exe = "x86_64-w64-mingw32-gcc" 17 | gcc.path = "/usr/lib64/ccache" 18 | gcc.options.linker = "" 19 | os = "windows" 20 | define: windows 21 | @end -------------------------------------------------------------------------------- /github_api/repository.nim: -------------------------------------------------------------------------------- 1 | ## This module contains procs related to Repository management 2 | ## in GitHub. Information on the procs in this file can be 3 | ## found in the GitHub API documentation. 4 | ## https://developer.github.com/v3/repos/ 5 | 6 | import httpclient, ospaths, strutils, json, marshal 7 | import ./client 8 | 9 | type 10 | User* = ref object 11 | login*: string 12 | id*: int 13 | avatar_url*: string 14 | gravatar_id*: string 15 | url*: string 16 | html_url*: string 17 | followers_url*: string 18 | following_url*: string 19 | gists_url*: string 20 | starred_url*: string 21 | subscriptions_url*: string 22 | organizations_url*: string 23 | repos_url*: string 24 | events_url*: string 25 | received_events_url*: string 26 | # type*: string 27 | site_admin*: bool 28 | Repository* = ref object 29 | id*: int 30 | name*: string 31 | full_name*: string 32 | owner*: User 33 | private*: bool 34 | html_url*: string 35 | description*: string 36 | fork*: bool 37 | url*: string 38 | forks_url*: string 39 | keys_url*: string 40 | collaborators_url*: string 41 | teams_url*: string 42 | hooks_url*: string 43 | issue_events_url*: string 44 | events_url*: string 45 | assignees_url*: string 46 | branches_url*: string 47 | tags_url*: string 48 | blobs_url*: string 49 | git_tags_url*: string 50 | git_refs_url*: string 51 | trees_url*: string 52 | statuses_url*: string 53 | languages_url*: string 54 | stargazers_url*: string 55 | contributors_url*: string 56 | subscribers_url*: string 57 | subscription_url*: string 58 | commits_url*: string 59 | git_commits_url*: string 60 | comments_url*: string 61 | issue_comment_url*: string 62 | contents_url*: string 63 | compare_url*: string 64 | merges_url*: string 65 | archive_url*: string 66 | downloads_url*: string 67 | issues_url*: string 68 | pulls_url*: string 69 | milestones_url*: string 70 | notifications_url*: string 71 | labels_url*: string 72 | releases_url*: string 73 | deployments_url*: string 74 | created_at*: string 75 | updated_at*: string 76 | pushed_at*: string 77 | git_url*: string 78 | ssh_url*: string 79 | clone_url*: string 80 | svn_url*: string 81 | homepage*: string 82 | size*: int 83 | stargazers_count*: int 84 | watchers_count*: int 85 | language*: string 86 | has_issues*: bool 87 | has_projects*: bool 88 | has_downloads*: bool 89 | has_wiki*: bool 90 | has_pages*: bool 91 | forks_count*: int 92 | mirror_url*: string 93 | archived*: bool 94 | open_issues_count*: int 95 | forks*: int 96 | open_issues*: int 97 | watchers*: int 98 | default_branch*: string 99 | 100 | proc listRepos*( 101 | client: GithubApiClient, 102 | visibility: string = nil, 103 | affiliation: string = nil, 104 | repoType: string = nil, 105 | sort: string = nil, 106 | direction: string = nil, 107 | limit: int = 100, 108 | page: int = 1): Response = 109 | ## https://developer.github.com/v3/repos/#list-your-repositories 110 | 111 | var data = %*{ 112 | "visibility": visibility, 113 | "affiliation": affiliation, 114 | "type": repoType, 115 | "sort": sort, 116 | "direction": direction, 117 | "per_page": limit, 118 | "page": page 119 | } 120 | var path = "/user/repos" 121 | client.request(path, query = data) 122 | 123 | proc listUserRepos*( 124 | client: GithubApiClient, 125 | username: string, 126 | repoType: string = nil, 127 | sort: string = nil, 128 | direction: string = nil, 129 | limit: int = 100, 130 | page: int = 1): Response = 131 | ## https://developer.github.com/v3/repos/#list-user-repositories 132 | 133 | var data = %*{ 134 | "type": repoType, 135 | "sort": sort, 136 | "direction": direction, 137 | "per_page": limit, 138 | "page": page 139 | } 140 | var path = "/users" / username / "repos" 141 | client.request(path, query = data) 142 | 143 | proc listOrgRepos*( 144 | client: GithubApiClient, 145 | orgName: string, 146 | repoType: string = nil, 147 | limit: int = 100, 148 | page: int = 1): Response = 149 | ## https://developer.github.com/v3/repos/#list-organization-repositories 150 | 151 | var data = %*{ 152 | "type": repoType, 153 | "per_page": limit, 154 | "page": page 155 | } 156 | var path = "/orgs" / orgName / "repos" 157 | client.request(path, query = data) 158 | 159 | proc listAllRepos*( 160 | client: GithubApiClient, 161 | since: int = 0): Response = 162 | ## https://developer.github.com/v3/repos/#list-all-public-repositories 163 | 164 | var data = %*{ 165 | "since": since 166 | } 167 | var path = "/repositories" 168 | client.request(path, query = data) 169 | 170 | proc createRepo*( 171 | client: GithubApiClient, 172 | name: string, 173 | orgName: string = nil, 174 | description: string = nil, 175 | homepage: string = nil, 176 | private: bool = false, 177 | has_issues: bool = true, 178 | has_projects: bool = false, 179 | has_wiki: bool = true, 180 | team_id: int = -1, 181 | auto_init: bool = false, 182 | gitignore_template: string = nil, 183 | license_template: string = nil, 184 | allow_squash_merge: bool = true, 185 | allow_merge_commit: bool = true, 186 | allow_rebase_merge: bool = true): Response = 187 | ## https://developer.github.com/v3/repos/#create 188 | 189 | var data = %*{ 190 | "name": name, 191 | "description": description, 192 | "homepage": homepage, 193 | "private": private, 194 | "has_issues": has_issues, 195 | "has_projects": has_projects, 196 | "has_wiki": has_wiki, 197 | "team_id": team_id, 198 | "auto_init": auto_init, 199 | "gitignore_template": gitignore_template, 200 | "license_template": license_template, 201 | "allow_squash_merge": allow_squash_merge, 202 | "allow_merge_commit": allow_merge_commit, 203 | "allow_rebase_merge": allow_rebase_merge 204 | } 205 | var path = if orgName != nil: "/orgs" / orgName / "repos" else: "/user/repos" 206 | client.request(path, body = $data, httpMethod = $HttpPost) 207 | 208 | proc getRepo*( 209 | client: GithubApiClient, 210 | owner: string, 211 | repo: string): Response = 212 | ## https://developer.github.com/v3/repos/#get 213 | 214 | var path = "/repos" / owner / repo 215 | client.request(path) 216 | 217 | proc editRepo*( 218 | client: GithubApiClient, 219 | owner: string, 220 | repo: string, 221 | name: string, 222 | description: string = nil, 223 | homepage: string = nil, 224 | private: bool = false, 225 | has_issues: bool = true, 226 | has_projects: bool = false, 227 | has_wiki: bool = true, 228 | default_branch: string = nil, 229 | allow_squash_merge: bool = true, 230 | allow_merge_commit: bool = true, 231 | allow_rebase_merge: bool = true): Response = 232 | ## https://developer.github.com/v3/repos/#edit 233 | 234 | var data = %*{ 235 | "name": if name != nil: name else: repo, 236 | "description": description, 237 | "homepage": homepage, 238 | "private": private, 239 | "has_issues": has_issues, 240 | "has_projects": has_projects, 241 | "has_wiki": has_wiki, 242 | "default_branch": default_branch, 243 | "allow_squash_merge": allow_squash_merge, 244 | "allow_merge_commit": allow_merge_commit, 245 | "allow_rebase_merge": allow_rebase_merge 246 | } 247 | var path = "/repos" / owner / repo 248 | client.request(path, body = $data, httpMethod = $HttpPatch) 249 | 250 | proc listRepoTopics*( 251 | client: GithubApiClient, 252 | owner: string, 253 | repo: string): Response = 254 | ## https://developer.github.com/v3/repos/#list-all-topics-for-a-repository 255 | 256 | var path = "/repos" / owner / repo / "topics" 257 | client.request(path) 258 | 259 | proc replaceRepoTopics*( 260 | client: GithubApiClient, 261 | owner: string, 262 | repo: string, 263 | names: seq[string]): Response = 264 | ## https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository 265 | 266 | var data = %*{ 267 | "names": names 268 | } 269 | var path = "/repos" / owner / repo / "topics" 270 | client.request(path, body = $data, httpMethod = $HttpPut) 271 | 272 | proc listContributors*( 273 | client: GithubApiClient, 274 | owner: string, 275 | repo: string, 276 | anon: bool = false): Response = 277 | ## https://developer.github.com/v3/repos/#list-contributors 278 | 279 | var data = %*{ 280 | "anon": anon 281 | } 282 | var path = "/repos" / owner / repo / "contributors" 283 | client.request(path, query = data) 284 | 285 | proc listLanguages*( 286 | client: GithubApiClient, 287 | owner: string, 288 | repo: string): Response = 289 | ## https://developer.github.com/v3/repos/#list-languages 290 | 291 | var path = "/repos" / owner / repo / "languages" 292 | client.request(path) 293 | 294 | proc listTeams*( 295 | client: GithubApiClient, 296 | owner: string, 297 | repo: string, 298 | limit: int = 0, 299 | page: int = 1): Response = 300 | ## https://developer.github.com/v3/repos/#list-teams 301 | 302 | var data = %*{ 303 | "per_page": limit, 304 | "page": page 305 | } 306 | var path = "/repos" / owner / repo / "teams" 307 | client.request(path, query = data) 308 | 309 | proc listTags*( 310 | client: GithubApiClient, 311 | owner: string, 312 | repo: string, 313 | limit: int = 0, 314 | page: int = 1): Response = 315 | ## https://developer.github.com/v3/repos/#list-tags 316 | 317 | var data = %*{ 318 | "per_page": limit, 319 | "page": page 320 | } 321 | var path = "/repos" / owner / repo / "tags" 322 | client.request(path, query = data) 323 | 324 | proc deleteRepo*( 325 | client: GithubApiClient, 326 | owner: string, 327 | repo: string): Response = 328 | ## https://developer.github.com/v3/repos/#delete-a-repository 329 | 330 | var path = "/repos" / owner / repo 331 | client.request(path, httpMethod = $HttpDelete) -------------------------------------------------------------------------------- /github_api/search.nim: -------------------------------------------------------------------------------- 1 | import httpclient, ospaths, json 2 | import ./client 3 | 4 | proc search*( 5 | client: GithubApiClient, 6 | searchType: string, 7 | query: string, 8 | sort: string = nil, 9 | order: string = nil, 10 | limit: int = 100, 11 | page: int = 1): Response = 12 | 13 | var data = %*{ 14 | "q": query, 15 | "sort": sort, 16 | "order": order, 17 | "per_page": limit, 18 | "page": page 19 | } 20 | var path = "/search" / searchType 21 | client.request(path, query = data) 22 | 23 | proc searchRepositories*( 24 | client: GithubApiClient, 25 | query: string, 26 | sort: string = nil, 27 | order: string = nil, 28 | limit: int = 100, 29 | page: int = 1): Response = 30 | 31 | search(client, "repositories", query, sort, order, limit, page) 32 | 33 | proc searchCommits*( 34 | client: GithubApiClient, 35 | query: string, 36 | sort: string = nil, 37 | order: string = nil, 38 | limit: int = 100, 39 | page: int = 1): Response = 40 | 41 | search(client, "commits", query, sort, order, limit, page) 42 | 43 | proc searchCode*( 44 | client: GithubApiClient, 45 | query: string, 46 | sort: string = nil, 47 | order: string = nil, 48 | limit: int = 100, 49 | page: int = 1): Response = 50 | 51 | search(client, "code", query, sort, order, limit, page) 52 | 53 | proc searchIssues*( 54 | client: GithubApiClient, 55 | query: string, 56 | sort: string = nil, 57 | order: string = nil, 58 | limit: int = 100, 59 | page: int = 1): Response = 60 | 61 | search(client, "issues", query, sort, order, limit, page) 62 | 63 | proc searchUsers*( 64 | client: GithubApiClient, 65 | query: string, 66 | sort: string = nil, 67 | order: string = nil, 68 | limit: int = 100, 69 | page: int = 1): Response = 70 | 71 | search(client, "users", query, sort, order, limit, page) --------------------------------------------------------------------------------