A curated list of libraries & SDKs for the Bluesky API and AT Protocol

starting to add Tangled repo support (wip)

+116 -7
+1
.gitignore
··· 5 5 .jekyll-metadata 6 6 config/auth.yml 7 7 public 8 + tmp 8 9 vendor
+3
Gemfile
··· 2 2 3 3 gem "jekyll", "~> 4.3" 4 4 5 + gem 'minisky', '~> 0.5' 6 + gem 'didkit', '~> 0.2' 7 + 5 8 group :jekyll_plugins do 6 9 end 7 10
+5
Gemfile.lock
··· 16 16 colorator (1.1.0) 17 17 concurrent-ruby (1.3.5) 18 18 csv (3.3.5) 19 + didkit (0.2.3) 19 20 ed25519 (1.4.0) 20 21 em-websocket (0.5.3) 21 22 eventmachine (>= 0.12.9) ··· 100 101 rb-inotify (~> 0.9, >= 0.9.10) 101 102 logger (1.7.0) 102 103 mercenary (0.4.0) 104 + minisky (0.5.0) 105 + base64 (~> 0.1) 103 106 net-scp (4.1.0) 104 107 net-ssh (>= 2.6.5, < 8.0.0) 105 108 net-sftp (4.0.0) ··· 164 167 bcrypt_pbkdf (>= 1.0, < 2.0) 165 168 benchmark 166 169 capistrano (~> 2.0) 170 + didkit (~> 0.2) 167 171 ed25519 (>= 1.2, < 2.0) 168 172 jekyll (~> 4.3) 169 173 logger 174 + minisky (~> 0.5) 170 175 171 176 BUNDLED WITH 172 177 2.6.3
+1 -1
_includes/project_card.html
··· 19 19 <p class="title"> 20 20 <a class="project-name" href="{{ include.project.url }}" target="_blank">{{ info.name }}</a> 21 21 <span class="author"><span class="dot">•</span> 22 - <a href="https://github.com/{{ info.user_login }}" target="_blank">{{ user }}</a></span> 22 + <a href="{{ info.user_profile }}" target="_blank">{{ user }}</a></span> 23 23 {% if info.user_login == "bluesky-social" %} 24 24 <img class="butterfly" src="/assets/images/logo.png" width="16"> 25 25 {% endif %}
+6 -5
lib/github_import.rb
··· 59 59 60 60 def extract_repo_data(json) 61 61 data = { 62 - 'name' => json['name'], 63 - 'description' => json['description'], 64 - 'user_login' => json['owner']['login'], 65 - 'homepage' => json['homepage'], 66 - 'stars' => json['stargazers_count'] 62 + 'name' => json['name'], 63 + 'description' => json['description'], 64 + 'user_login' => json['owner']['login'], 65 + 'user_profile' => "https://github.com/#{json['owner']['login']}", 66 + 'homepage' => json['homepage'], 67 + 'stars' => json['stargazers_count'] 67 68 } 68 69 69 70 if json['license'] && json['license']['spdx_id'] != 'NOASSERTION'
+2 -1
lib/metadata_import.rb
··· 1 1 require_relative 'github_import' 2 + require_relative 'tangled_import' 2 3 3 4 class MetadataImport 4 5 OUTPUT_FILE = '_data/metadata.yml' ··· 14 15 end 15 16 16 17 urls = get_repo_urls(language) 17 - importers = [GithubImport.new] 18 + importers = [GithubImport.new, TangledImport.new] 18 19 19 20 urls.each do |url| 20 21 if imp = importers.detect { |i| i.url_matches?(url) }
+98
lib/tangled_import.rb
··· 1 + require_relative 'npm_import' 2 + require_relative 'requests' 3 + 4 + require 'didkit' 5 + require 'fileutils' 6 + require 'minisky' 7 + 8 + class TangledImport 9 + include Requests 10 + 11 + def initialize 12 + @user_cache = {} 13 + end 14 + 15 + def url_matches?(url) 16 + url =~ %r{^https://tangled\.sh/@[\w\-\.]+/[\w\-\.]+} 17 + end 18 + 19 + def import_url(url) 20 + url =~ %r{^https://tangled\.sh/@([\w\-\.]+)/([\w\-\.]+)} 21 + user, repo = $1, $2 22 + 23 + did = DID.resolve_handle(user) 24 + sky = Minisky.new(did.get_document.pds_endpoint, nil) 25 + 26 + repos = sky.fetch_all('com.atproto.repo.listRecords', 27 + { repo: did, collection: 'sh.tangled.repo', limit: 100 }, 28 + field: 'records' 29 + ) 30 + 31 + repo_record = repos.detect { |x| x['value']['name'] == repo } 32 + 33 + repo_folder = clone_repo(user, repo) 34 + 35 + data = repo_data_from_record(repo_record['value']) 36 + data['user_login'] = user 37 + data['user_profile'] = "https://tangled.sh/@#{user}" 38 + 39 + if tag_info = get_latest_tag(repo_folder) 40 + data['last_tag'] = tag_info 41 + end 42 + 43 + data['last_commit'] = get_latest_commit(repo_folder) 44 + 45 + data 46 + end 47 + 48 + def clone_repo(user, repo) 49 + repos_cache = File.expand_path(File.join(__dir__, '..', 'tmp', 'repos')) 50 + FileUtils.mkdir_p(repos_cache) 51 + 52 + dirname = "#{user}_#{repo}" 53 + repo_folder = File.join(repos_cache, dirname) 54 + 55 + if Dir.exist?(repo_folder) 56 + Dir.chdir(repo_folder) do 57 + system('git pull') 58 + end 59 + else 60 + Dir.chdir(repos_cache) do 61 + system("git clone https://tangled.sh/@#{user}/#{repo} #{dirname}") 62 + end 63 + end 64 + 65 + repo_folder 66 + end 67 + 68 + def repo_data_from_record(record) 69 + { 70 + 'name' => record['name'], 71 + 'description' => record['description'], 72 + } 73 + end 74 + 75 + def get_latest_tag(repo_folder) 76 + Dir.chdir(repo_folder) do 77 + newest_tag_commit = %x(git rev-list --tags --max-count=1).strip 78 + return nil if newest_tag_commit.empty? 79 + 80 + newest_tag = %x(git tag --points-at "#{newest_tag_commit}" | head -1).strip 81 + timestamp = %x(git show -s --format=%cI "#{newest_tag_commit}").strip 82 + 83 + { 84 + 'name' => newest_tag, 85 + 'committer_date' => Time.parse(timestamp) 86 + } 87 + end 88 + end 89 + 90 + def get_latest_commit(repo_folder) 91 + Dir.chdir(repo_folder) do 92 + { 93 + 'committer_date' => %x(git show -s --format=%cI).strip.then { |x| Time.parse(x) }, 94 + 'author_date' => %x(git show -s --format=%aI).strip.then { |x| Time.parse(x) } 95 + } 96 + end 97 + end 98 + end