@ -0,0 +1,9 @@ | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
indent_style = space | |||
indent_size = 2 | |||
end_of_line = lf | |||
insert_final_newline = true | |||
trim_trailing_whitespace = true |
@ -0,0 +1,2 @@ | |||
vendor | |||
.env |
@ -0,0 +1,4 @@ | |||
inherit_from: .rubocop_todo.yml | |||
AllCops: | |||
TargetRubyVersion: 2.5 |
@ -0,0 +1,30 @@ | |||
# This configuration was generated by | |||
# `rubocop --auto-gen-config` | |||
# on 2018-06-01 00:51:49 +0530 using RuboCop version 0.56.0. | |||
# The point is for the user to remove these configuration records | |||
# one by one as the offenses are removed from the code base. | |||
# Note that changes in the inspected code, or installation of new | |||
# versions of RuboCop, may require this file to be generated again. | |||
# Offense count: 1 | |||
Metrics/AbcSize: | |||
Max: 24 | |||
# Offense count: 2 | |||
# Configuration parameters: CountComments. | |||
Metrics/MethodLength: | |||
Max: 14 | |||
# Offense count: 2 | |||
Style/Documentation: | |||
Exclude: | |||
- 'spec/**/*' | |||
- 'test/**/*' | |||
- 'app.rb' | |||
- 'opml.rb' | |||
# Offense count: 2 | |||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. | |||
# URISchemes: http, https | |||
Metrics/LineLength: | |||
Max: 87 |
@ -0,0 +1,10 @@ | |||
# frozen_string_literal: true | |||
source 'https://rubygems.org' | |||
gem 'nokogiri', '~> 1.8' | |||
gem 'octokit', '~> 4.0' | |||
gem 'redis', '~> 4.0' | |||
gem 'sinatra', '~> 2.0' | |||
gem 'sinatra-contrib', '~> 2.0' | |||
gem 'dotenv' |
@ -0,0 +1,52 @@ | |||
GEM | |||
remote: https://rubygems.org/ | |||
specs: | |||
addressable (2.5.2) | |||
public_suffix (>= 2.0.2, < 4.0) | |||
backports (3.11.3) | |||
dotenv (2.4.0) | |||
faraday (0.15.2) | |||
multipart-post (>= 1.2, < 3) | |||
mini_portile2 (2.3.0) | |||
multi_json (1.13.1) | |||
multipart-post (2.0.0) | |||
mustermann (1.0.2) | |||
nokogiri (1.8.2) | |||
mini_portile2 (~> 2.3.0) | |||
octokit (4.9.0) | |||
sawyer (~> 0.8.0, >= 0.5.3) | |||
public_suffix (3.0.2) | |||
rack (2.0.5) | |||
rack-protection (2.0.1) | |||
rack | |||
redis (4.0.1) | |||
sawyer (0.8.1) | |||
addressable (>= 2.3.5, < 2.6) | |||
faraday (~> 0.8, < 1.0) | |||
sinatra (2.0.1) | |||
mustermann (~> 1.0) | |||
rack (~> 2.0) | |||
rack-protection (= 2.0.1) | |||
tilt (~> 2.0) | |||
sinatra-contrib (2.0.1) | |||
backports (>= 2.0) | |||
multi_json | |||
mustermann (~> 1.0) | |||
rack-protection (= 2.0.1) | |||
sinatra (= 2.0.1) | |||
tilt (>= 1.3, < 3) | |||
tilt (2.0.8) | |||
PLATFORMS | |||
ruby | |||
DEPENDENCIES | |||
dotenv | |||
nokogiri (~> 1.8) | |||
octokit (~> 4.0) | |||
redis (~> 4.0) | |||
sinatra (~> 2.0) | |||
sinatra-contrib (~> 2.0) | |||
BUNDLED WITH | |||
1.16.2 |
@ -0,0 +1,75 @@ | |||
# frozen_string_literal: true | |||
require 'dotenv/load' | |||
require 'sinatra/base' | |||
require 'sinatra/reloader' | |||
require 'octokit' | |||
require 'redis' | |||
require 'json' | |||
require './opml' | |||
Octokit.auto_paginate = true | |||
class MyApp < Sinatra::Base | |||
configure :development do | |||
register Sinatra::Reloader | |||
also_reload 'opml.rb' | |||
end | |||
configure do | |||
set :r, Redis.new | |||
set :client, Octokit::Client.new( | |||
client_id: ENV.fetch('GITHUB_CLIENT_ID'), | |||
client_secret: ENV.fetch('GITHUB_CLIENT_SECRET'), | |||
per_page: 100, | |||
auto_traversal: true, | |||
auto_paginate: true | |||
) | |||
end | |||
def get_starred_repos_from_cache(user) | |||
from_cache = settings.r.get "#{user}.repos" | |||
if from_cache | |||
time = settings.r.get "#{user}.repos.time" || Time.now.to_i | |||
return [JSON.parse(from_cache), time] | |||
end | |||
repos = [] | |||
starred = settings.client.starred(user) | |||
starred.each do |repo| | |||
repos.push repo[:full_name] | |||
end | |||
time = Time.now.to_i | |||
settings.r.set "#{user}.repos", repos | |||
settings.r.set "#{user}.repos.time", time | |||
[repos, time] | |||
end | |||
get '/github/:user/starred.opml' do | |||
user = params[:user] | |||
repos, time = get_starred_repos_from_cache(user) | |||
opml = OPML.new do | |||
@title = "Releases: #{user}/starred" | |||
@date_created = Time.new(time.to_i).rfc822 | |||
@date_modified = Time.now.rfc822 | |||
@owner_name = user | |||
end | |||
repos.each do |r| | |||
title = "Release notes from #{r}" | |||
html_url = "https://github.com/#{r}/releases" | |||
rss_url = "#{html_url}.atom" | |||
opml.add_outline( | |||
text: title, description: "#{r}/releases", html_url: html_url, | |||
xml_url: rss_url, title: title, | |||
type: 'rss', version: 'RSS2' | |||
) | |||
end | |||
filename = "#{user}-starred-releases.opml" | |||
response.headers['Content-Disposition'] = "attachment; filename=#{filename};" | |||
response.headers['Content-Type'] = 'application/octet-stream' | |||
response.headers['Content-Transfer-Encoding'] = 'binary' | |||
opml.xml | |||
end | |||
end |
@ -0,0 +1,4 @@ | |||
# frozen_string_literal: true | |||
require './app' | |||
run MyApp |
@ -0,0 +1,53 @@ | |||
# frozen_string_literal: true | |||
require 'nokogiri' | |||
class OPML | |||
PROPERTIES = %w[title date_created date_modified | |||
owner_name owner_email docs].freeze | |||
attr_accessor :title, :date_created, :date_modified, :owner_name, :owner_email, :docs | |||
def initialize(&block) | |||
instance_eval(&block) | |||
@docs = 'http://dev.opml.org/spec2.html' | |||
@outlines = [] | |||
end | |||
def camel_case(str) | |||
str.downcase.split('_').each_with_index.map do |v, i| | |||
i.zero? ? v : v.capitalize | |||
end.join | |||
end | |||
def add_outline(params) | |||
params = Hash[params.map { |k, v| [camel_case(k.to_s), v] }] | |||
@outlines.push params | |||
end | |||
def add_note_with_params(xml, attrs) | |||
attrs.each do |attr| | |||
val = send(attr.to_sym) | |||
key = camel_case(attr).to_sym | |||
xml.send(key, val) if val | |||
end | |||
end | |||
def xml | |||
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| | |||
xml.opml(version: '2.0') do | |||
xml.head do | |||
add_note_with_params xml, PROPERTIES | |||
end | |||
xml.body do | |||
xml.outline text: 'GitHub Releases' do | |||
@outlines.each do |outline| | |||
xml.outline outline | |||
end | |||
end | |||
end | |||
end | |||
end.to_xml | |||
end | |||
end |