flycal-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +139 -0
- data/exe/flycal +6 -0
- data/lib/flycal_cli/auth.rb +132 -0
- data/lib/flycal_cli/calendar_service.rb +60 -0
- data/lib/flycal_cli/cli.rb +236 -0
- data/lib/flycal_cli/config.rb +56 -0
- data/lib/flycal_cli/version.rb +5 -0
- data/lib/flycal_cli.rb +11 -0
- metadata +138 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8efca78d1d82f9e3310cbd90a29a95ad52fbf9fff714d3aada8321c999bfaa71
|
|
4
|
+
data.tar.gz: 749f5417dfd3cc780c78e55524aa0acabea9b8fc28f07614d8faa25e7920253b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: be1d24bae7776ca94842c9edd5116544b750846ccc169bd57ba6720dc1b63904eaad92fb2f33ace8f668a8580352ea2ff530532c5262bbc83d44f4f92521b324
|
|
7
|
+
data.tar.gz: 2d89f80c2b486f102b8ad6edf178dd71126fd78549a49caecc17a5bc0254827f2d0ed07cb727e2cbc18c0b3bf19cc64bbca04846f8a72a00b82d7cb77495273c
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 flycal-cli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# flycal-cli
|
|
2
|
+
|
|
3
|
+
CLI per accedere ai calendari Google da riga di comando.
|
|
4
|
+
|
|
5
|
+
## Installazione
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem install flycal-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Oppure aggiungi al tuo Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "flycal-cli"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configurazione iniziale
|
|
18
|
+
|
|
19
|
+
Prima di usare flycal, devi creare credenziali OAuth nel Google Cloud Console:
|
|
20
|
+
|
|
21
|
+
1. Vai su [Google Cloud Console - Credenziali](https://console.cloud.google.com/apis/credentials)
|
|
22
|
+
2. Crea un progetto (o usa uno esistente)
|
|
23
|
+
3. Abilita l'**API Google Calendar** (API e servizi → Libreria → Cerca "Google Calendar API")
|
|
24
|
+
4. Crea credenziali **Applicazione desktop** (OAuth 2.0 Client IDs)
|
|
25
|
+
5. Aggiungi questo URI come redirect autorizzato:
|
|
26
|
+
```
|
|
27
|
+
http://127.0.0.1:9292/oauth2callback
|
|
28
|
+
```
|
|
29
|
+
6. Scarica il file JSON e salvalo come `~/.flycal/credentials.json`
|
|
30
|
+
|
|
31
|
+
## Comandi
|
|
32
|
+
|
|
33
|
+
### `flycal login`
|
|
34
|
+
|
|
35
|
+
Connetti al tuo account Google. Se non sei connesso, verrà generato un link da aprire nel browser per completare l'autenticazione OAuth.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
flycal login
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `flycal calendars`
|
|
42
|
+
|
|
43
|
+
Mostra la lista dei calendari disponibili e permette di selezionare il calendario di default. La selezione è scorrevole (usa le frecce per navigare).
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
flycal calendars
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `flycal logout`
|
|
50
|
+
|
|
51
|
+
Disconnetti dall'account Google.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
flycal logout
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `flycal search`
|
|
58
|
+
|
|
59
|
+
Cerca eventi nei calendari.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
flycal search --from 2025-02-01 --to 2025-02-28
|
|
63
|
+
flycal search -f 2025-02-01 -t 2025-02-28 --description "riunione"
|
|
64
|
+
flycal search -f 2025-02-01T09:00 -t 2025-02-28T18:00 -c "Lavoro"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Opzioni:
|
|
68
|
+
- `-f, --from`: Data/ora inizio (es. `2025-01-01` o `2025-01-01T09:00`)
|
|
69
|
+
- `-t, --to`: Data/ora fine
|
|
70
|
+
- `-c, --calendar`: Nome o ID del calendario (default: calendario impostato con `flycal calendars`)
|
|
71
|
+
- `-d, --description`: Filtra eventi per testo nella descrizione
|
|
72
|
+
|
|
73
|
+
## File di configurazione
|
|
74
|
+
|
|
75
|
+
I dati vengono salvati in `~/.flycal/`:
|
|
76
|
+
|
|
77
|
+
- `config.yml` - calendario di default (`calendar_default`)
|
|
78
|
+
- `credentials.json` - credenziali OAuth (da creare manualmente)
|
|
79
|
+
- `tokens.yml` - token di accesso (gestito automaticamente)
|
|
80
|
+
|
|
81
|
+
## Pubblicazione su RubyGems
|
|
82
|
+
|
|
83
|
+
### Primo caricamento
|
|
84
|
+
|
|
85
|
+
1. Crea un account su [rubygems.org](https://rubygems.org) se non ce l'hai
|
|
86
|
+
2. Aggiorna `flycal-cli.gemspec` con i tuoi dati (autori, email, homepage)
|
|
87
|
+
3. Build e push:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Build della gem
|
|
91
|
+
gem build flycal-cli.gemspec
|
|
92
|
+
|
|
93
|
+
# Push (ti chiederà email e password RubyGems)
|
|
94
|
+
gem push flycal-cli-0.1.0.gem
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Versioni successive
|
|
98
|
+
|
|
99
|
+
1. Aggiorna la versione in `lib/flycal_cli/version.rb`
|
|
100
|
+
2. Commit e tag:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git add .
|
|
104
|
+
git commit -m "Release v0.2.0"
|
|
105
|
+
git tag v0.2.0
|
|
106
|
+
git push origin main
|
|
107
|
+
git push origin v0.2.0
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
3. Build e push:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
gem build flycal-cli.gemspec
|
|
114
|
+
gem push flycal-cli-0.2.0.gem
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Usando `rake release` (raccomandato)
|
|
118
|
+
|
|
119
|
+
Aggiungi al Rakefile:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
require "bundler/gem_tasks"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Poi:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Per la prima release
|
|
129
|
+
bundle exec rake release
|
|
130
|
+
|
|
131
|
+
# Per release successive: aggiorna VERSION e poi
|
|
132
|
+
bundle exec rake release
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`rake release` esegue: build, push su RubyGems, commit, tag e push su git.
|
|
136
|
+
|
|
137
|
+
## Licenza
|
|
138
|
+
|
|
139
|
+
MIT
|
data/exe/flycal
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webrick"
|
|
4
|
+
require "googleauth"
|
|
5
|
+
require "googleauth/stores/file_token_store"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
module FlycalCli
|
|
9
|
+
class Auth
|
|
10
|
+
SCOPE = "https://www.googleapis.com/auth/calendar.readonly"
|
|
11
|
+
REDIRECT_PORT = 9292
|
|
12
|
+
REDIRECT_URI = "http://127.0.0.1:#{REDIRECT_PORT}/oauth2callback"
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def credentials
|
|
16
|
+
return nil unless Config.credentials_exist?
|
|
17
|
+
|
|
18
|
+
token_store = Google::Auth::Stores::FileTokenStore.new(file: Config.tokens_path)
|
|
19
|
+
client_id = Google::Auth::ClientId.from_file(Config.credentials_path)
|
|
20
|
+
authorizer = Google::Auth::UserAuthorizer.new(
|
|
21
|
+
client_id,
|
|
22
|
+
SCOPE,
|
|
23
|
+
token_store,
|
|
24
|
+
"/oauth2callback"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
creds = authorizer.get_credentials("flycal_user")
|
|
28
|
+
return creds if creds
|
|
29
|
+
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def logged_in?
|
|
34
|
+
creds = credentials
|
|
35
|
+
return false unless creds
|
|
36
|
+
|
|
37
|
+
creds.fetch_access_token!
|
|
38
|
+
true
|
|
39
|
+
rescue Signet::AuthorizationError
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def login
|
|
44
|
+
unless Config.credentials_exist?
|
|
45
|
+
raise FlycalCli::Error,
|
|
46
|
+
"File credentials non trovato. Crea #{Config.credentials_path} con le credenziali OAuth dal Google Cloud Console.\n" \
|
|
47
|
+
"Vai su: https://console.cloud.google.com/apis/credentials\n" \
|
|
48
|
+
"Crea credenziali 'Desktop app' e scarica il JSON come credentials.json"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
token_store = Google::Auth::Stores::FileTokenStore.new(file: Config.tokens_path)
|
|
52
|
+
client_id = Google::Auth::ClientId.from_file(Config.credentials_path)
|
|
53
|
+
authorizer = Google::Auth::UserAuthorizer.new(
|
|
54
|
+
client_id,
|
|
55
|
+
SCOPE,
|
|
56
|
+
token_store,
|
|
57
|
+
"/oauth2callback"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
creds = authorizer.get_credentials("flycal_user")
|
|
61
|
+
if creds
|
|
62
|
+
creds.fetch_access_token!
|
|
63
|
+
return creds
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Avvia server locale per ricevere il codice
|
|
67
|
+
code = start_redirect_server(authorizer)
|
|
68
|
+
raise FlycalCli::Error, "Autenticazione annullata o fallita" if code.nil? || code.empty?
|
|
69
|
+
|
|
70
|
+
authorizer.get_and_store_credentials_from_code(
|
|
71
|
+
user_id: "flycal_user",
|
|
72
|
+
code: code,
|
|
73
|
+
base_url: "http://127.0.0.1:#{REDIRECT_PORT}"
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def logout
|
|
78
|
+
return true unless File.exist?(Config.tokens_path)
|
|
79
|
+
|
|
80
|
+
token_store = Google::Auth::Stores::FileTokenStore.new(file: Config.tokens_path)
|
|
81
|
+
token_store.delete("flycal_user")
|
|
82
|
+
File.delete(Config.tokens_path) if File.exist?(Config.tokens_path)
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def start_redirect_server(authorizer)
|
|
89
|
+
auth_code = nil
|
|
90
|
+
state = SecureRandom.hex(16)
|
|
91
|
+
code_verifier = Google::Auth::UserAuthorizer.generate_code_verifier
|
|
92
|
+
authorizer.code_verifier = code_verifier
|
|
93
|
+
|
|
94
|
+
server = WEBrick::HTTPServer.new(
|
|
95
|
+
Port: REDIRECT_PORT,
|
|
96
|
+
BindAddress: "127.0.0.1",
|
|
97
|
+
Logger: WEBrick::Log.new($stderr, WEBrick::Log::FATAL),
|
|
98
|
+
AccessLog: []
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
server.mount_proc("/oauth2callback") do |req, res|
|
|
102
|
+
query = req.request_uri.query
|
|
103
|
+
params = query ? URI.decode_www_form(query).to_h : {}
|
|
104
|
+
|
|
105
|
+
if params["error"]
|
|
106
|
+
res.status = 400
|
|
107
|
+
res.body = "<h1>Errore</h1><p>#{params['error']}: #{params['error_description']}</p>"
|
|
108
|
+
auth_code = nil
|
|
109
|
+
elsif params["code"]
|
|
110
|
+
auth_code = params["code"]
|
|
111
|
+
res.status = 200
|
|
112
|
+
res.body = "<h1>Autenticazione completata!</h1><p>Puoi chiudere questa finestra e tornare al terminale.</p>"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
Thread.new { server.shutdown }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
url = authorizer.get_authorization_url(
|
|
119
|
+
base_url: REDIRECT_URI.sub("/oauth2callback", ""),
|
|
120
|
+
state: state
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
puts "\nApri questo link nel browser per autenticarti:\n\n"
|
|
124
|
+
puts " #{url}\n\n"
|
|
125
|
+
puts "Dopo l'autenticazione, tornerai qui automaticamente.\n\n"
|
|
126
|
+
|
|
127
|
+
server.start
|
|
128
|
+
auth_code
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "google/apis/calendar_v3"
|
|
4
|
+
|
|
5
|
+
module FlycalCli
|
|
6
|
+
class CalendarService
|
|
7
|
+
def initialize(credentials)
|
|
8
|
+
@service = Google::Apis::CalendarV3::CalendarService.new
|
|
9
|
+
@service.authorization = credentials
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def list_calendars
|
|
13
|
+
result = @service.list_calendar_lists
|
|
14
|
+
result.items || []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_calendar(calendar_id)
|
|
18
|
+
@service.get_calendar(calendar_id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def list_events(calendar_id, time_min:, time_max:, query: nil)
|
|
22
|
+
events = []
|
|
23
|
+
page_token = nil
|
|
24
|
+
|
|
25
|
+
loop do
|
|
26
|
+
result = @service.list_events(
|
|
27
|
+
calendar_id,
|
|
28
|
+
time_min: time_min.iso8601,
|
|
29
|
+
time_max: time_max.iso8601,
|
|
30
|
+
q: query,
|
|
31
|
+
single_events: true,
|
|
32
|
+
order_by: "startTime",
|
|
33
|
+
page_token: page_token
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
events.concat(result.items || [])
|
|
37
|
+
|
|
38
|
+
page_token = result.next_page_token
|
|
39
|
+
break if page_token.nil? || page_token.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
events
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def list_all_events(calendar_ids, time_min:, time_max:, query: nil)
|
|
46
|
+
all_events = []
|
|
47
|
+
|
|
48
|
+
calendar_ids.each do |cal_id|
|
|
49
|
+
begin
|
|
50
|
+
events = list_events(cal_id, time_min: time_min, time_max: time_max, query: query)
|
|
51
|
+
events.each { |e| all_events << { calendar_id: cal_id, event: e } }
|
|
52
|
+
rescue Google::Apis::Errors::Error => e
|
|
53
|
+
warn "Errore nel calendario #{cal_id}: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
all_events
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "time"
|
|
5
|
+
require "tty-prompt"
|
|
6
|
+
require "tty-spinner"
|
|
7
|
+
|
|
8
|
+
module FlycalCli
|
|
9
|
+
class Cli < Thor
|
|
10
|
+
package_name "flycal"
|
|
11
|
+
|
|
12
|
+
def self.exit_on_failure?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "login", "Connetti al tuo account Google"
|
|
17
|
+
def login
|
|
18
|
+
if Auth.logged_in?
|
|
19
|
+
puts "✓ Sei già connesso al tuo account Google."
|
|
20
|
+
puts "\nEsegui 'flycal calendars' per impostare il calendario di default."
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
unless Config.credentials_exist?
|
|
25
|
+
puts "Errore: File credentials non trovato."
|
|
26
|
+
puts "\nPer configurare flycal:"
|
|
27
|
+
puts "1. Vai su https://console.cloud.google.com/apis/credentials"
|
|
28
|
+
puts "2. Crea credenziali 'Applicazione desktop' (Desktop app)"
|
|
29
|
+
puts "3. Scarica il JSON e salvalo come: #{Config.credentials_path}"
|
|
30
|
+
puts "\nAggiungi anche questo URI come redirect autorizzato:"
|
|
31
|
+
puts " http://127.0.0.1:9292/oauth2callback"
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
Auth.login
|
|
37
|
+
puts "\n✓ Autenticazione completata con successo!"
|
|
38
|
+
puts "\nEsegui 'flycal calendars' per impostare il calendario di default."
|
|
39
|
+
rescue FlycalCli::Error => e
|
|
40
|
+
puts "Errore: #{e.message}"
|
|
41
|
+
exit 1
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "logout", "Disconnetti dall'account Google"
|
|
46
|
+
def logout
|
|
47
|
+
unless Auth.logged_in?
|
|
48
|
+
puts "Non sei connesso a nessun account Google."
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Auth.logout
|
|
53
|
+
puts "✓ Disconnesso con successo."
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
desc "calendars", "Mostra i calendari disponibili e imposta quello di default"
|
|
57
|
+
def calendars
|
|
58
|
+
unless Auth.logged_in?
|
|
59
|
+
puts "Non sei connesso. Esegui prima 'flycal login'."
|
|
60
|
+
exit 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
creds = Auth.credentials
|
|
64
|
+
service = CalendarService.new(creds)
|
|
65
|
+
|
|
66
|
+
spinner = TTY::Spinner.new("Caricamento calendari... ", format: :dots)
|
|
67
|
+
spinner.auto_spin
|
|
68
|
+
calendars = service.list_calendars
|
|
69
|
+
spinner.stop("✓")
|
|
70
|
+
|
|
71
|
+
if calendars.empty?
|
|
72
|
+
puts "Nessun calendario trovato."
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Costruisci lista per selezione (display => calendar_id)
|
|
77
|
+
default_id = Config.calendar_default
|
|
78
|
+
choices = calendars.to_h do |cal|
|
|
79
|
+
primary = cal.primary ? " (principale)" : ""
|
|
80
|
+
default = (cal.id == default_id) ? " [default]" : ""
|
|
81
|
+
summary = cal.summary || cal.id
|
|
82
|
+
label = "#{summary}#{primary}#{default}"
|
|
83
|
+
[label, cal.id]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
prompt = TTY::Prompt.new
|
|
87
|
+
selected_id = prompt.select(
|
|
88
|
+
"Scegli il calendario di default:",
|
|
89
|
+
choices,
|
|
90
|
+
per_page: 15,
|
|
91
|
+
filter: true
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
calendar = calendars.find { |c| c.id == selected_id }
|
|
95
|
+
if calendar
|
|
96
|
+
Config.calendar_default = calendar.id
|
|
97
|
+
puts "\n✓ Calendario di default impostato: #{calendar.summary}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
desc "search", "Cerca eventi nei calendari"
|
|
102
|
+
option :calendar, type: :string, aliases: "-c", desc: "Nome o ID del calendario (default: calendario default)"
|
|
103
|
+
option :from, type: :string, aliases: "-f", required: true, desc: "Data/ora inizio (es. 2025-01-01 o 2025-01-01T09:00)"
|
|
104
|
+
option :to, type: :string, aliases: "-t", required: true, desc: "Data/ora fine (es. 2025-01-31 o 2025-01-31T18:00)"
|
|
105
|
+
option :description, type: :string, aliases: "-d", desc: "Filtra per descrizione (testo)"
|
|
106
|
+
def search
|
|
107
|
+
unless Auth.logged_in?
|
|
108
|
+
puts "Non sei connesso. Esegui prima 'flycal login'."
|
|
109
|
+
exit 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
time_min = parse_datetime(options[:from])
|
|
113
|
+
time_max = parse_datetime(options[:to], end_of_day: true)
|
|
114
|
+
|
|
115
|
+
if time_min > time_max
|
|
116
|
+
puts "Errore: la data 'from' deve essere prima della data 'to'."
|
|
117
|
+
exit 1
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
creds = Auth.credentials
|
|
121
|
+
service = CalendarService.new(creds)
|
|
122
|
+
|
|
123
|
+
calendar_ids = resolve_calendar_ids(service, options[:calendar])
|
|
124
|
+
if calendar_ids.empty?
|
|
125
|
+
puts "Nessun calendario trovato."
|
|
126
|
+
exit 1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
events = service.list_all_events(
|
|
130
|
+
calendar_ids,
|
|
131
|
+
time_min: time_min,
|
|
132
|
+
time_max: time_max,
|
|
133
|
+
query: options[:description]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
print_events(service, events)
|
|
137
|
+
print_summary(events)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
default_task :help
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def parse_datetime(str, end_of_day: false)
|
|
145
|
+
return nil if str.nil? || str.empty?
|
|
146
|
+
|
|
147
|
+
if str.include?("T")
|
|
148
|
+
Time.parse(str)
|
|
149
|
+
else
|
|
150
|
+
d = Date.parse(str)
|
|
151
|
+
t = d.to_time
|
|
152
|
+
end_of_day ? t + 86400 - 1 : t # 23:59:59 per --to quando è solo data
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def resolve_calendar_ids(service, calendar_name)
|
|
157
|
+
calendar_lists = service.list_calendars
|
|
158
|
+
|
|
159
|
+
if calendar_name.nil? || calendar_name.empty?
|
|
160
|
+
default_id = Config.calendar_default
|
|
161
|
+
if default_id
|
|
162
|
+
return [default_id]
|
|
163
|
+
end
|
|
164
|
+
# Usa calendario principale se disponibile
|
|
165
|
+
primary = calendar_lists.find(&:primary)
|
|
166
|
+
return [primary.id] if primary
|
|
167
|
+
# Altrimenti primo calendario
|
|
168
|
+
return [calendar_lists.first.id] if calendar_lists.any?
|
|
169
|
+
return []
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Cerca per nome o ID
|
|
173
|
+
matches = calendar_lists.select do |cal|
|
|
174
|
+
cal.id == calendar_name ||
|
|
175
|
+
(cal.summary && cal.summary.downcase.include?(calendar_name.downcase))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
matches.map(&:id)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def print_events(service, events)
|
|
182
|
+
# Usa lista calendari per i nomi (evita chiamate API extra)
|
|
183
|
+
calendar_list = service.list_calendars
|
|
184
|
+
calendar_names = calendar_list.to_h { |c| [c.id, c.summary || c.id] }
|
|
185
|
+
|
|
186
|
+
events.each do |item|
|
|
187
|
+
cal_id = item[:calendar_id]
|
|
188
|
+
event = item[:event]
|
|
189
|
+
cal_name = calendar_names[cal_id] || cal_id
|
|
190
|
+
|
|
191
|
+
start_time = event.start&.date_time || event.start&.date
|
|
192
|
+
end_time = event.end&.date_time || event.end&.date
|
|
193
|
+
|
|
194
|
+
start_str = format_datetime(start_time)
|
|
195
|
+
end_str = format_datetime(end_time)
|
|
196
|
+
desc = event.summary || "(senza titolo)"
|
|
197
|
+
desc = event.description&.slice(0, 80) if event.summary.nil? && event.description
|
|
198
|
+
|
|
199
|
+
puts "#{cal_name} | #{start_str} | #{end_str} | #{desc}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def format_datetime(dt)
|
|
204
|
+
return "-" if dt.nil?
|
|
205
|
+
|
|
206
|
+
if dt.is_a?(String)
|
|
207
|
+
dt
|
|
208
|
+
else
|
|
209
|
+
dt.strftime("%Y-%m-%d %H:%M")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def print_summary(events)
|
|
214
|
+
total_minutes = 0
|
|
215
|
+
|
|
216
|
+
events.each do |item|
|
|
217
|
+
event = item[:event]
|
|
218
|
+
start = event.start&.date_time || event.start&.date
|
|
219
|
+
end_dt = event.end&.date_time || event.end&.date
|
|
220
|
+
|
|
221
|
+
next if start.nil? || end_dt.nil?
|
|
222
|
+
|
|
223
|
+
start = start.to_time if start.respond_to?(:to_time)
|
|
224
|
+
end_dt = end_dt.to_time if end_dt.respond_to?(:to_time)
|
|
225
|
+
total_minutes += (end_dt - start) / 60
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
hours = (total_minutes / 60).floor
|
|
229
|
+
mins = (total_minutes % 60).round
|
|
230
|
+
|
|
231
|
+
puts "\n---"
|
|
232
|
+
puts "Eventi trovati: #{events.size}"
|
|
233
|
+
puts "Tempo totale occupato: #{hours}h #{mins}min"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module FlycalCli
|
|
7
|
+
class Config
|
|
8
|
+
CONFIG_DIR = File.expand_path("~/.flycal")
|
|
9
|
+
CONFIG_FILE = File.join(CONFIG_DIR, "config.yml")
|
|
10
|
+
CREDENTIALS_FILE = File.join(CONFIG_DIR, "credentials.json")
|
|
11
|
+
TOKENS_FILE = File.join(CONFIG_DIR, "tokens.yml")
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def load
|
|
15
|
+
return {} unless File.exist?(CONFIG_FILE)
|
|
16
|
+
|
|
17
|
+
YAML.load_file(CONFIG_FILE) || {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save(data)
|
|
21
|
+
FileUtils.mkdir_p(CONFIG_DIR)
|
|
22
|
+
File.write(CONFIG_FILE, data.to_yaml)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def calendar_default
|
|
26
|
+
load["calendar_default"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def calendar_default=(calendar_id)
|
|
30
|
+
data = load
|
|
31
|
+
data["calendar_default"] = calendar_id
|
|
32
|
+
save(data)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def credentials_path
|
|
36
|
+
CREDENTIALS_FILE
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def credentials_exist?
|
|
40
|
+
File.exist?(CREDENTIALS_FILE)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tokens_path
|
|
44
|
+
TOKENS_FILE
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def config_dir
|
|
48
|
+
CONFIG_DIR
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clear_all
|
|
52
|
+
FileUtils.rm_rf(CONFIG_DIR)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/flycal_cli.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: flycal-cli
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- flycal-cli
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: google-apis-calendar_v3
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.51'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.51'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: googleauth
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.8'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: thor
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.3'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.3'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: tty-prompt
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.23'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.23'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: tty-spinner
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.9'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.9'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webrick
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.8'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.8'
|
|
96
|
+
description: flycal-cli permette di connettersi al proprio account Google e gestire
|
|
97
|
+
i calendari da riga di comando
|
|
98
|
+
email:
|
|
99
|
+
- ''
|
|
100
|
+
executables:
|
|
101
|
+
- flycal
|
|
102
|
+
extensions: []
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- LICENSE
|
|
106
|
+
- README.md
|
|
107
|
+
- exe/flycal
|
|
108
|
+
- lib/flycal_cli.rb
|
|
109
|
+
- lib/flycal_cli/auth.rb
|
|
110
|
+
- lib/flycal_cli/calendar_service.rb
|
|
111
|
+
- lib/flycal_cli/cli.rb
|
|
112
|
+
- lib/flycal_cli/config.rb
|
|
113
|
+
- lib/flycal_cli/version.rb
|
|
114
|
+
homepage: https://github.com/your-username/flycal-cli
|
|
115
|
+
licenses:
|
|
116
|
+
- MIT
|
|
117
|
+
metadata:
|
|
118
|
+
homepage_uri: https://github.com/your-username/flycal-cli
|
|
119
|
+
source_code_uri: https://github.com/your-username/flycal-cli
|
|
120
|
+
changelog_uri: https://github.com/your-username/flycal-cli/blob/main/CHANGELOG.md
|
|
121
|
+
rdoc_options: []
|
|
122
|
+
require_paths:
|
|
123
|
+
- lib
|
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: 3.1.0
|
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: '0'
|
|
134
|
+
requirements: []
|
|
135
|
+
rubygems_version: 4.0.6
|
|
136
|
+
specification_version: 4
|
|
137
|
+
summary: CLI per accedere ai calendari Google
|
|
138
|
+
test_files: []
|