A curated list of libraries & SDKs for the Bluesky API and AT Protocol
1require_relative 'cargo_import'
2require_relative 'import_helpers'
3require_relative 'npm_import'
4require_relative 'requests'
5
6require 'base64'
7require 'json'
8require 'time'
9
10class GithubImport
11 include ImportHelpers
12 include Requests
13
14 def initialize
15 @user_cache = {}
16 end
17
18 def request_headers
19 { 'Authorization' => "Bearer " + auth_config['github_token'] }
20 end
21
22 def url_matches?(url)
23 url =~ %r{^https://github\.com/[\w\-\.]+/[\w\-\.]+}
24 end
25
26 def import_url(url, project)
27 url =~ %r{^https://github\.com/([\w\-\.]+)/([\w\-\.]+)}
28 user, repo = $1, $2
29
30 repo_info = get_repo_info(user, repo)
31 data = extract_repo_data(repo_info)
32
33 if release = get_latest_release(user, repo)
34 data['last_release'] = release
35 end
36
37 if tag_info = get_latest_tag(user, repo)
38 data['last_tag'] = tag_info
39 end
40
41 if ['JavaScript', 'TypeScript'].include?(repo_info['language'])
42 npm = get_npm_releases(user, repo, project)
43 last_update = npm.map { |n| n.last_release_time }.sort.last
44
45 if last_update && (release.nil? || last_update > release['published_at'])
46 data['last_release'] = { 'published_at' => last_update }
47 end
48 elsif repo_info['language'] == 'Rust'
49 crates = get_cargo_releases(user, repo, project)
50 last_update = crates.map { |c| c.last_release_time }.sort.last
51
52 if last_update && (release.nil? || last_update > release['published_at'])
53 data['last_release'] = { 'published_at' => last_update }
54 end
55 end
56
57 data['last_commit'] = get_latest_commit(user, repo)
58
59 if user_info = get_user_info(user)
60 data['user_name'] = user_info['name']
61 end
62
63 data
64 end
65
66 def get_repo_info(user, repo)
67 response = get_response("https://api.github.com/repos/#{user}/#{repo}")
68 raise FetchError.new(response) unless response.code.to_i == 200
69
70 JSON.parse(response.body)
71 end
72
73 def extract_repo_data(json)
74 data = {
75 'name' => json['name'],
76 'description' => json['description'],
77 'user_login' => json['owner']['login'],
78 'user_profile' => "https://github.com/#{json['owner']['login']}",
79 'homepage' => json['homepage'],
80 'stars' => json['stargazers_count']
81 }
82
83 if json['license'] && json['license']['spdx_id'] != 'NOASSERTION'
84 data['license'] = json['license']['spdx_id']
85 end
86
87 data
88 end
89
90 def get_latest_release(user, repo)
91 response = get_response("https://api.github.com/repos/#{user}/#{repo}/releases/latest")
92
93 if response.code.to_i == 200
94 json = JSON.parse(response.body)
95
96 {
97 'tag_name' => json['tag_name'],
98 'name' => json['name'],
99 'draft' => json['draft'],
100 'prerelease' => json['prerelease'],
101 'created_at' => Time.parse(json['created_at']),
102 'published_at' => Time.parse(json['published_at'])
103 }
104 elsif response.code.to_i == 404
105 nil
106 else
107 raise FetchError.new(response)
108 end
109 end
110
111 def get_latest_tag(user, repo)
112 response = get_response("https://api.github.com/repos/#{user}/#{repo}/tags")
113 raise FetchError.new(response) unless response.code.to_i == 200
114
115 json = JSON.parse(response.body)
116
117 if tag = json.first
118 tag_name = tag['name']
119 response = get_response(tag['commit']['url'])
120 raise FetchError.new(response) unless response.code.to_i == 200
121
122 json = JSON.parse(response.body)
123 {
124 'name' => tag_name,
125 'author_date' => Time.parse(json['commit']['author']['date']),
126 'committer_date' => Time.parse(json['commit']['committer']['date'])
127 }
128 else
129 nil
130 end
131 end
132
133 def get_latest_commit(user, repo)
134 response = get_response("https://api.github.com/repos/#{user}/#{repo}/commits")
135 raise FetchError.new(response) unless response.code.to_i == 200
136
137 json = JSON.parse(response.body)
138
139 if commit = json.first
140 {
141 'author_date' => Time.parse(commit['commit']['author']['date']),
142 'committer_date' => Time.parse(commit['commit']['committer']['date'])
143 }
144 else
145 raise FetchError.new(response)
146 end
147 end
148
149 def get_user_info(user)
150 @user_cache[user] ||= begin
151 response = get_response("https://api.github.com/users/#{user}")
152 raise FetchError.new(response) unless response.code.to_i == 200
153
154 JSON.parse(response.body)
155 end
156 end
157
158 def get_npm_releases(user, repo, project)
159 sleep 5
160
161 search_url = URI("https://api.github.com/search/code")
162 search_url.query = URI.encode_www_form(q: "repo:#{user}/#{repo} filename:package.json", per_page: 100)
163 response = get_response(search_url)
164 raise FetchError.new(response) unless response.code.to_i == 200
165
166 json = JSON.parse(response.body)
167 releases = []
168
169 json['items'].each do |file|
170 response = get_response(file['url'])
171 raise FetchError.new(response) unless response.code.to_i == 200
172
173 details = JSON.parse(response.body)
174 contents = Base64.decode64(details['content'])
175 package = NPMImport::PackageJSON.new(contents)
176
177 next if package.version.nil? || package.private?
178
179 if npm = NPMImport.new.get_package_info(package.name)
180 package_repo_url = normalize_repo_url(npm.repository_url)
181 package_homepage = normalize_repo_url(npm.homepage_url)
182
183 if project.urls.map { |u| normalize_repo_url(u) }.any? { |u| u == package_repo_url || u == package_homepage }
184 releases << npm
185 end
186 end
187 end
188
189 releases
190 end
191
192 def get_cargo_releases(user, repo, project)
193 sleep 5
194
195 search_url = URI("https://api.github.com/search/code")
196 search_url.query = URI.encode_www_form(q: "repo:#{user}/#{repo} filename:Cargo.toml", per_page: 100)
197 response = get_response(search_url)
198 raise FetchError.new(response) unless response.code.to_i == 200
199
200 json = JSON.parse(response.body)
201 releases = []
202
203 json['items'].each do |file|
204 response = get_response(file['url'])
205 raise FetchError.new(response) unless response.code.to_i == 200
206
207 details = JSON.parse(response.body)
208 contents = Base64.decode64(details['content'])
209 package = CargoImport::CargoToml.new(contents)
210
211 next if package.name.nil?
212
213 if crate = CargoImport.new.get_crate_info(package.name)
214 crate_repo_url = normalize_repo_url(crate.repository_url)
215 crate_homepage = normalize_repo_url(crate.homepage_url)
216
217 if project.urls.map { |u| normalize_repo_url(u) }.any? { |u| u == crate_repo_url || u == crate_homepage }
218 releases << crate
219 end
220 end
221 end
222
223 releases
224 end
225end