Created
December 26, 2017 20:54
-
-
Save alem0lars/dbc4f92fa7af250b72db579473ab2928 to your computer and use it in GitHub Desktop.
Trello two-way sync (WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require "net/http" | |
require "json" | |
require "pathname" | |
require "date" | |
module TRSync | |
module Model | |
class Board | |
include Enumerable | |
def initialize | |
@lists = [] | |
end | |
def <<(list) | |
if list.is_a? List | |
@lists << list | |
else | |
@lists << List.parse(list) | |
end | |
end | |
def [](list_name) | |
@lists.find { |list| list.name == list_name } | |
end | |
def each | |
@lists.each { |list| yield list } | |
end | |
def last_list | |
@lists.last | |
end | |
def to_pretty_s(indent = 1) | |
indent_str = "\n#{"\t" * indent}" | |
lists = @lists.empty? ? "<none>" : "#{indent_str}#{@lists.map { |l| l.to_pretty_s(indent + 1)}.join(indent_str)}" | |
"Board{lists=#{lists}}" | |
end | |
end | |
class List | |
include Enumerable | |
attr_accessor :id, :name | |
def initialize(id, name) | |
@id = id | |
@name = name | |
@cards = [] | |
end | |
def <<(card) | |
if card.is_a? Card | |
@cards << card | |
else | |
@cards << Card.parse(card) | |
end | |
end | |
def [](card_name) | |
@cards.find { |card| card.name == card_name } | |
end | |
def each | |
@cards.each { |card| yield card } | |
end | |
def last_card | |
@cards.last | |
end | |
def to_pretty_s(indent = 1) | |
indent_str = "\n#{"\t" * indent}" | |
cards = @cards.empty? ? "<none>" : "#{indent_str}#{@cards.map { |c| c.to_pretty_s(indent + 1)}.join(indent_str)}" | |
"List{id=#{id}, name=#{name}, cards=#{cards}}" | |
end | |
def self.parse(data) | |
List.new(data["id"], data["name"]) | |
end | |
end | |
class Card | |
attr_accessor :id, :name, :desc, :closed, :date, :short_link, :url | |
def initialize(id, name, desc, closed, date, short_link, url) | |
@id = id | |
@name = name | |
@desc = desc | |
@closed = closed | |
@date = date | |
@short_link = short_link | |
@url = url | |
@attachments = [] | |
@checklists = [] | |
end | |
def add_attachment(attachment) | |
if attachment.is_a? Attachment | |
@attachments << attachment | |
else | |
@attachments << Attachment.parse(attachment) | |
end | |
end | |
def attachments | |
@attachments.to_enum(:each) | |
end | |
def last_attachment | |
@attachments.last | |
end | |
def add_checklist(checklist) | |
if checklist.is_a? Attachment | |
@checklists << checklist | |
else | |
@checklists << Checklist.parse(checklist) | |
end | |
end | |
def checklists | |
@checklists.to_enum(:each) | |
end | |
def last_checklist | |
@checklists.last | |
end | |
def to_pretty_s(indent = 1) | |
indent_str = "\n#{"\t" * indent}" | |
attachments = @attachments.empty? ? "<none>" : "#{indent_str}#{@attachments.map { |a| a.to_pretty_s(indent + 1)}.join(indent_str)}" | |
checklists = @checklists.empty? ? "<none>" : "#{indent_str}#{@checklists.map { |c| c.to_pretty_s(indent + 1)}.join(indent_str)}" | |
"Card{#{indent_str}id=#{id}, name=#{name}, desc=#{desc.empty? ? "<none>" : "..."},#{indent_str}closed=#{closed}, date=#{date}, short_link=#{short_link},#{indent_str}url=#{url}, attachments=#{attachments}, checklists=#{checklists}}" | |
end | |
def self.parse(data) | |
Card.new( | |
data["id"], | |
data["name"], | |
data["desc"], | |
data["closed"], | |
DateTime.parse(data["dateLastActivity"]), | |
data["shortLink"], | |
data["shortUrl"] | |
) | |
end | |
end | |
class Attachment | |
attr_accessor :id, :name, :url, :date | |
def initialize(id, name, url, date) | |
@id = id | |
@name = name | |
@url = url | |
@date = date | |
end | |
def to_pretty_s(indent = 1) | |
"Attachment{id=#{id}, name=#{name}, url=#{url}, date=#{date}}" | |
end | |
def self.parse(data) | |
Attachment.new( | |
data["id"], | |
data["name"], | |
data["url"], | |
DateTime.parse(data["date"]) | |
) | |
end | |
end | |
class Checklist | |
include Enumerable | |
attr_accessor :id, :name | |
def initialize(id, name) | |
@id = id | |
@name = name | |
@items = [] | |
end | |
def <<(item) | |
@items << item | |
end | |
def each | |
@items.each { |item| yield item } | |
end | |
def last_item | |
@items.last | |
end | |
def to_pretty_s(indent = 1) | |
indent_str = "\n#{"\t" * indent}" | |
items = @items.empty? ? "<none>" : "#{indent_str}#{@items.map(&:to_s).join(indent_str)}" | |
"Checklist{id=#{id}, name=#{name}, items=#{items}}" | |
end | |
def self.parse(data) | |
cl = Checklist.new(data["id"], data["name"]) | |
data["checkItems"].each do |item| | |
cl << { id: item["id"], name: item["name"], state: item["state"] } | |
end | |
cl | |
end | |
end | |
end | |
module Request | |
class Base | |
attr_reader :params, :path, :method, :use_ssl | |
def initialize(path: [], params: {}, method: :get, use_ssl: true, | |
key: nil, token: nil) | |
@base_url = "https://api.trello.com/1" | |
@params = { key: key, token: token }.merge(params).delete_if do |k, v| | |
v.nil? || v.empty? | |
end | |
def @params.to_s | |
self.map{ |k, v| "#{k}=#{v}" }.join("&") | |
end | |
@path = path | |
def @path.to_s | |
self.join("/") | |
end | |
@method = method | |
@use_ssl = use_ssl | |
end | |
def url | |
"#@base_url/#{path}" | |
end | |
def perform | |
uri, req = case method | |
when :get | |
uri = URI("#{url}?#{params}") | |
req = Net::HTTP::Get.new(uri, "Content-Type" => "application/json") | |
[uri, req] | |
when :post | |
uri = URI(url) | |
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") | |
req.body = params.to_json | |
[uri, req] | |
end | |
puts ">>> Performing request: #{uri}" | |
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http| | |
http.request(req) | |
end | |
JSON.parse(res.body) | |
rescue StandardError => exc | |
puts "!!! Got error while performing request: #{exc}" | |
end | |
end | |
class Board < Base | |
def initialize(board_id, **kwargs) | |
super(**kwargs.merge({ | |
path: ["boards", board_id], | |
params: { fields: (kwargs[:fields] || []).join(","), lists: "all" } | |
})) | |
end | |
end | |
class Cards < Base | |
def initialize(board_id, **kwargs) | |
super(**kwargs.merge({ | |
path: ["boards", board_id, "cards"], | |
params: { fields: (kwargs[:fields] || []).join(",") } | |
})) | |
end | |
end | |
class Attachments < Base | |
def initialize(card_id, **kwargs) | |
super(**kwargs.merge({ | |
path: ["cards", card_id, "attachments"], | |
params: { fields: (kwargs[:fields] || []).join(",") } | |
})) | |
end | |
end | |
class Checklists < Base | |
def initialize(card_id, **kwargs) | |
super(**kwargs.merge({ | |
path: ["cards", card_id, "checklists"], | |
params: { fields: (kwargs[:fields] || []).join(",") } | |
})) | |
end | |
end | |
end | |
module Retriever | |
module Local | |
class Board | |
def initialize(root_dir) | |
@root_dir = root_dir | |
end | |
def retrieve | |
Pathname.glob(@root_dir.join("*")).select { |list_dir| list_dir.directory? }.collect_concat do |list_dir| | |
list = {} | |
list[:name] = list_dir.basename | |
list | |
end | |
end | |
end | |
end | |
module Remote | |
class Base | |
def initialize(trello_key, trello_token) | |
@trello_key = trello_key | |
@trello_token = trello_token | |
end | |
end | |
class Board < Base | |
def initialize(board_id, trello_key, trello_token) | |
super(trello_key, trello_token) | |
@board_id = board_id | |
end | |
def retrieve | |
board_data = TRSync::Request::Board.new(@board_id, key: @trello_key, token: @trello_token).perform | |
cards_data = TRSync::Request::Cards.new(@board_id, key: @trello_key, token: @trello_token).perform | |
board = TRSync::Model::Board.new | |
board_data["lists"].each do |list_data| | |
board << list_data | |
cards_data.select { |c| c["idList"] == board.last_list.id } | |
.each do |card_data| | |
board.last_list << card_data | |
attachments = TRSync::Request::Attachments.new(board.last_list.last_card.id, key: @trello_key, token: @trello_token).perform | |
attachments.each do |attachment_data| | |
board.last_list.last_card.add_attachment(attachment_data) | |
end | |
checklists = TRSync::Request::Checklists.new(board.last_list.last_card.id, key: @trello_key, token: @trello_token).perform | |
checklists.each do |checklist_data| | |
board.last_list.last_card.add_checklist(checklist_data) | |
end | |
end | |
end | |
board | |
end | |
end | |
end | |
end | |
class Synchronizer | |
def initialize(root_dir, board_id, trello_key, trello_token, check_delay) | |
@remote_board_retriever = TRSync::Retriever::Remote::Board.new(board_id, trello_key, trello_token) | |
@local_board_retriever = TRSync::Retriever::Local::Board.new(root_dir) | |
@check_delay = check_delay | |
end | |
def start | |
threads = [] | |
# Perform sync | |
loop do | |
perform_sync | |
sleep @check_delay | |
end | |
threads.map(&:join) | |
end | |
private def perform_sync | |
puts ">>> Performing sync.." | |
remote_board = @remote_board_retriever.retrieve | |
local_board = @local_board_retriever.retrieve | |
# remote_lists.each do |remote_list| | |
# name = remote_list[:name] | |
# local_list = local_lists.find { |local_list| local_list[:name] == name } | |
# if local_list | |
# update_local_list(local_list, remote_list) | |
# else | |
# create_local_list(remote_list) | |
# end | |
# remote_list[:cards].each do |card| | |
# # TODO | |
# end | |
# end | |
end | |
# | |
# Update an existing local list | |
# | |
private def update_local_list(local_list, remote_list) | |
# TODO | |
end | |
# | |
# Create a new local list | |
# | |
private def create_local_list(remote_list) | |
# TODO | |
end | |
end | |
end | |
# Entry-Point | |
begin | |
ENV["ROOT_DIR"] = "." | |
ENV["BOARD_ID"] = "A16esx8M" | |
ENV["TRELLO_API_KEY"] = "52dcbfb53f378bf37b3296e0e2ece4a3" | |
ENV["TRELLO_TOKEN"] = "4dcf3ff950b2a80c48e1e708aa9cc82cfa8507658b54234781c847d70339fb47" | |
synchronizer = TRSync::Synchronizer.new(ENV["ROOT_DIR"], ENV["BOARD_ID"], ENV["TRELLO_API_KEY"], ENV["TRELLO_TOKEN"], 1) | |
synchronizer.start | |
rescue SystemExit, Interrupt | |
puts "!!! Shutting down !!!" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment