basecamp-sdk 0.9.0 → 0.10.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.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Auto-generated from OpenAPI spec. Do not edit manually.
4
- # Generated: 2026-07-25T04:02:54Z
4
+ # Generated: 2026-07-28T21:34:14Z
5
5
 
6
6
  require "json"
7
7
  require "time"
@@ -288,6 +288,60 @@ module Basecamp
288
288
  end
289
289
  end
290
290
 
291
+ # BucketCardsGroup
292
+ class BucketCardsGroup
293
+ include TypeHelpers
294
+ attr_accessor :bucket, :cards
295
+
296
+ # @return [Array<Symbol>]
297
+ def self.required_fields
298
+ %i[bucket cards].freeze
299
+ end
300
+
301
+ def initialize(data = {})
302
+ @bucket = parse_type(data["bucket"], "RecordingBucket")
303
+ @cards = parse_array(data["cards"], "Card")
304
+ end
305
+
306
+ def to_h
307
+ {
308
+ "bucket" => @bucket,
309
+ "cards" => @cards,
310
+ }.compact
311
+ end
312
+
313
+ def to_json(*args)
314
+ to_h.to_json(*args)
315
+ end
316
+ end
317
+
318
+ # BucketTodosGroup
319
+ class BucketTodosGroup
320
+ include TypeHelpers
321
+ attr_accessor :bucket, :todos
322
+
323
+ # @return [Array<Symbol>]
324
+ def self.required_fields
325
+ %i[bucket todos].freeze
326
+ end
327
+
328
+ def initialize(data = {})
329
+ @bucket = parse_type(data["bucket"], "RecordingBucket")
330
+ @todos = parse_array(data["todos"], "Todo")
331
+ end
332
+
333
+ def to_h
334
+ {
335
+ "bucket" => @bucket,
336
+ "todos" => @todos,
337
+ }.compact
338
+ end
339
+
340
+ def to_json(*args)
341
+ to_h.to_json(*args)
342
+ end
343
+ end
344
+
291
345
  # Campfire
292
346
  class Campfire
293
347
  include TypeHelpers
@@ -1269,6 +1323,34 @@ module Basecamp
1269
1323
  end
1270
1324
  end
1271
1325
 
1326
+ # DoorService
1327
+ class DoorService
1328
+ include TypeHelpers
1329
+ attr_accessor :code, :example_url, :name, :supporting_text, :valid_patterns
1330
+
1331
+ def initialize(data = {})
1332
+ @code = data["code"]
1333
+ @example_url = data["example_url"]
1334
+ @name = data["name"]
1335
+ @supporting_text = data["supporting_text"]
1336
+ @valid_patterns = data["valid_patterns"]
1337
+ end
1338
+
1339
+ def to_h
1340
+ {
1341
+ "code" => @code,
1342
+ "example_url" => @example_url,
1343
+ "name" => @name,
1344
+ "supporting_text" => @supporting_text,
1345
+ "valid_patterns" => @valid_patterns,
1346
+ }.compact
1347
+ end
1348
+
1349
+ def to_json(*args)
1350
+ to_h.to_json(*args)
1351
+ end
1352
+ end
1353
+
1272
1354
  # Event
1273
1355
  class Event
1274
1356
  include TypeHelpers
@@ -1332,6 +1414,121 @@ module Basecamp
1332
1414
  end
1333
1415
  end
1334
1416
 
1417
+ # EverythingBoost
1418
+ class EverythingBoost
1419
+ include TypeHelpers
1420
+ attr_accessor :booster, :content, :created_at, :id, :recording
1421
+
1422
+ # @return [Array<Symbol>]
1423
+ def self.required_fields
1424
+ %i[booster content created_at id recording].freeze
1425
+ end
1426
+
1427
+ def initialize(data = {})
1428
+ @booster = parse_type(data["booster"], "Person")
1429
+ @content = data["content"]
1430
+ @created_at = parse_datetime(data["created_at"])
1431
+ @id = parse_integer(data["id"])
1432
+ @recording = parse_type(data["recording"], "Recording")
1433
+ end
1434
+
1435
+ def to_h
1436
+ {
1437
+ "booster" => @booster,
1438
+ "content" => @content,
1439
+ "created_at" => @created_at,
1440
+ "id" => @id,
1441
+ "recording" => @recording,
1442
+ }.compact
1443
+ end
1444
+
1445
+ def to_json(*args)
1446
+ to_h.to_json(*args)
1447
+ end
1448
+ end
1449
+
1450
+ # EverythingFile
1451
+ class EverythingFile
1452
+ include TypeHelpers
1453
+ attr_accessor :app_download_url, :app_url, :attachable_sgid, :bookmark_url, :boosts_count, :boosts_url, :bucket, :byte_size, :comments_count, :comments_url, :content, :content_attachments, :content_type, :created_at, :creator, :description, :description_attachments, :download_url, :filename, :height, :id, :inherits_status, :parent, :position, :status, :subscription_url, :title, :type, :updated_at, :url, :visible_to_clients, :width
1454
+
1455
+ def initialize(data = {})
1456
+ @app_download_url = data["app_download_url"]
1457
+ @app_url = data["app_url"]
1458
+ @attachable_sgid = data["attachable_sgid"]
1459
+ @bookmark_url = data["bookmark_url"]
1460
+ @boosts_count = parse_integer(data["boosts_count"])
1461
+ @boosts_url = data["boosts_url"]
1462
+ @bucket = parse_type(data["bucket"], "RecordingBucket")
1463
+ @byte_size = parse_integer(data["byte_size"])
1464
+ @comments_count = parse_integer(data["comments_count"])
1465
+ @comments_url = data["comments_url"]
1466
+ @content = data["content"]
1467
+ @content_attachments = parse_array(data["content_attachments"], "RichTextAttachment")
1468
+ @content_type = data["content_type"]
1469
+ @created_at = parse_datetime(data["created_at"])
1470
+ @creator = parse_type(data["creator"], "Person")
1471
+ @description = data["description"]
1472
+ @description_attachments = parse_array(data["description_attachments"], "RichTextAttachment")
1473
+ @download_url = data["download_url"]
1474
+ @filename = data["filename"]
1475
+ @height = parse_integer(data["height"])
1476
+ @id = parse_integer(data["id"])
1477
+ @inherits_status = parse_boolean(data["inherits_status"])
1478
+ @parent = parse_type(data["parent"], "RecordingParent")
1479
+ @position = parse_integer(data["position"])
1480
+ @status = data["status"]
1481
+ @subscription_url = data["subscription_url"]
1482
+ @title = data["title"]
1483
+ @type = data["type"]
1484
+ @updated_at = parse_datetime(data["updated_at"])
1485
+ @url = data["url"]
1486
+ @visible_to_clients = parse_boolean(data["visible_to_clients"])
1487
+ @width = parse_integer(data["width"])
1488
+ end
1489
+
1490
+ def to_h
1491
+ {
1492
+ "app_download_url" => @app_download_url,
1493
+ "app_url" => @app_url,
1494
+ "attachable_sgid" => @attachable_sgid,
1495
+ "bookmark_url" => @bookmark_url,
1496
+ "boosts_count" => @boosts_count,
1497
+ "boosts_url" => @boosts_url,
1498
+ "bucket" => @bucket,
1499
+ "byte_size" => @byte_size,
1500
+ "comments_count" => @comments_count,
1501
+ "comments_url" => @comments_url,
1502
+ "content" => @content,
1503
+ "content_attachments" => @content_attachments,
1504
+ "content_type" => @content_type,
1505
+ "created_at" => @created_at,
1506
+ "creator" => @creator,
1507
+ "description" => @description,
1508
+ "description_attachments" => @description_attachments,
1509
+ "download_url" => @download_url,
1510
+ "filename" => @filename,
1511
+ "height" => @height,
1512
+ "id" => @id,
1513
+ "inherits_status" => @inherits_status,
1514
+ "parent" => @parent,
1515
+ "position" => @position,
1516
+ "status" => @status,
1517
+ "subscription_url" => @subscription_url,
1518
+ "title" => @title,
1519
+ "type" => @type,
1520
+ "updated_at" => @updated_at,
1521
+ "url" => @url,
1522
+ "visible_to_clients" => @visible_to_clients,
1523
+ "width" => @width,
1524
+ }.compact
1525
+ end
1526
+
1527
+ def to_json(*args)
1528
+ to_h.to_json(*args)
1529
+ end
1530
+ end
1531
+
1335
1532
  # Forward
1336
1533
  class Forward
1337
1534
  include TypeHelpers
@@ -2900,11 +3097,11 @@ module Basecamp
2900
3097
  # Recording
2901
3098
  class Recording
2902
3099
  include TypeHelpers
2903
- attr_accessor :app_url, :bucket, :created_at, :creator, :id, :inherits_status, :parent, :status, :title, :type, :updated_at, :url, :visible_to_clients, :bookmark_url, :comments_count, :comments_url, :content, :content_attachments, :description_attachments, :subscription_url
3100
+ attr_accessor :app_url, :bucket, :created_at, :creator, :id, :inherits_status, :status, :title, :type, :updated_at, :url, :visible_to_clients, :bookmark_url, :boosts_count, :boosts_url, :bubble_up_url, :category, :comments_count, :comments_url, :content, :content_attachments, :description, :description_attachments, :from, :group_on, :parent, :position, :replies_count, :replies_url, :service, :subject, :subscription_url
2904
3101
 
2905
3102
  # @return [Array<Symbol>]
2906
3103
  def self.required_fields
2907
- %i[app_url bucket created_at creator id inherits_status parent status title type updated_at url visible_to_clients].freeze
3104
+ %i[app_url bucket created_at creator id inherits_status status title type updated_at url visible_to_clients].freeze
2908
3105
  end
2909
3106
 
2910
3107
  def initialize(data = {})
@@ -2914,7 +3111,6 @@ module Basecamp
2914
3111
  @creator = parse_type(data["creator"], "Person")
2915
3112
  @id = parse_integer(data["id"])
2916
3113
  @inherits_status = parse_boolean(data["inherits_status"])
2917
- @parent = parse_type(data["parent"], "RecordingParent")
2918
3114
  @status = data["status"]
2919
3115
  @title = data["title"]
2920
3116
  @type = data["type"]
@@ -2922,11 +3118,24 @@ module Basecamp
2922
3118
  @url = data["url"]
2923
3119
  @visible_to_clients = parse_boolean(data["visible_to_clients"])
2924
3120
  @bookmark_url = data["bookmark_url"]
3121
+ @boosts_count = parse_integer(data["boosts_count"])
3122
+ @boosts_url = data["boosts_url"]
3123
+ @bubble_up_url = data["bubble_up_url"]
3124
+ @category = parse_type(data["category"], "RecordingCategory")
2925
3125
  @comments_count = parse_integer(data["comments_count"])
2926
3126
  @comments_url = data["comments_url"]
2927
3127
  @content = data["content"]
2928
3128
  @content_attachments = parse_array(data["content_attachments"], "RichTextAttachment")
3129
+ @description = data["description"]
2929
3130
  @description_attachments = parse_array(data["description_attachments"], "RichTextAttachment")
3131
+ @from = data["from"]
3132
+ @group_on = data["group_on"]
3133
+ @parent = parse_type(data["parent"], "RecordingParent")
3134
+ @position = parse_integer(data["position"])
3135
+ @replies_count = parse_integer(data["replies_count"])
3136
+ @replies_url = data["replies_url"]
3137
+ @service = parse_type(data["service"], "DoorService")
3138
+ @subject = data["subject"]
2930
3139
  @subscription_url = data["subscription_url"]
2931
3140
  end
2932
3141
 
@@ -2938,7 +3147,6 @@ module Basecamp
2938
3147
  "creator" => @creator,
2939
3148
  "id" => @id,
2940
3149
  "inherits_status" => @inherits_status,
2941
- "parent" => @parent,
2942
3150
  "status" => @status,
2943
3151
  "title" => @title,
2944
3152
  "type" => @type,
@@ -2946,11 +3154,24 @@ module Basecamp
2946
3154
  "url" => @url,
2947
3155
  "visible_to_clients" => @visible_to_clients,
2948
3156
  "bookmark_url" => @bookmark_url,
3157
+ "boosts_count" => @boosts_count,
3158
+ "boosts_url" => @boosts_url,
3159
+ "bubble_up_url" => @bubble_up_url,
3160
+ "category" => @category,
2949
3161
  "comments_count" => @comments_count,
2950
3162
  "comments_url" => @comments_url,
2951
3163
  "content" => @content,
2952
3164
  "content_attachments" => @content_attachments,
3165
+ "description" => @description,
2953
3166
  "description_attachments" => @description_attachments,
3167
+ "from" => @from,
3168
+ "group_on" => @group_on,
3169
+ "parent" => @parent,
3170
+ "position" => @position,
3171
+ "replies_count" => @replies_count,
3172
+ "replies_url" => @replies_url,
3173
+ "service" => @service,
3174
+ "subject" => @subject,
2954
3175
  "subscription_url" => @subscription_url,
2955
3176
  }.compact
2956
3177
  end
@@ -2989,10 +3210,39 @@ module Basecamp
2989
3210
  end
2990
3211
  end
2991
3212
 
3213
+ # RecordingCategory
3214
+ class RecordingCategory
3215
+ include TypeHelpers
3216
+ attr_accessor :id, :name, :icon
3217
+
3218
+ # @return [Array<Symbol>]
3219
+ def self.required_fields
3220
+ %i[id name].freeze
3221
+ end
3222
+
3223
+ def initialize(data = {})
3224
+ @id = parse_integer(data["id"])
3225
+ @name = data["name"]
3226
+ @icon = data["icon"]
3227
+ end
3228
+
3229
+ def to_h
3230
+ {
3231
+ "id" => @id,
3232
+ "name" => @name,
3233
+ "icon" => @icon,
3234
+ }.compact
3235
+ end
3236
+
3237
+ def to_json(*args)
3238
+ to_h.to_json(*args)
3239
+ end
3240
+ end
3241
+
2992
3242
  # RecordingParent
2993
3243
  class RecordingParent
2994
3244
  include TypeHelpers
2995
- attr_accessor :app_url, :id, :title, :type, :url
3245
+ attr_accessor :app_url, :id, :title, :type, :url, :bucket
2996
3246
 
2997
3247
  # @return [Array<Symbol>]
2998
3248
  def self.required_fields
@@ -3005,6 +3255,7 @@ module Basecamp
3005
3255
  @title = data["title"]
3006
3256
  @type = data["type"]
3007
3257
  @url = data["url"]
3258
+ @bucket = parse_type(data["bucket"], "RecordingBucket")
3008
3259
  end
3009
3260
 
3010
3261
  def to_h
@@ -3014,6 +3265,7 @@ module Basecamp
3014
3265
  "title" => @title,
3015
3266
  "type" => @type,
3016
3267
  "url" => @url,
3268
+ "bucket" => @bucket,
3017
3269
  }.compact
3018
3270
  end
3019
3271
 
@@ -3261,29 +3513,32 @@ module Basecamp
3261
3513
  # SearchResult
3262
3514
  class SearchResult
3263
3515
  include TypeHelpers
3264
- attr_accessor :app_url, :id, :title, :type, :url, :bookmark_url, :bucket, :content, :content_attachments, :created_at, :creator, :description, :description_attachments, :inherits_status, :parent, :status, :subject, :updated_at, :visible_to_clients
3516
+ attr_accessor :app_url, :content, :description, :id, :title, :type, :url, :bookmark_url, :bubble_up_url, :bucket, :content_attachments, :created_at, :creator, :description_attachments, :inherits_status, :parent, :plain_text_content, :plain_text_description, :status, :subject, :updated_at, :visible_to_clients
3265
3517
 
3266
3518
  # @return [Array<Symbol>]
3267
3519
  def self.required_fields
3268
- %i[app_url id title type url].freeze
3520
+ %i[app_url content description id title type url].freeze
3269
3521
  end
3270
3522
 
3271
3523
  def initialize(data = {})
3272
3524
  @app_url = data["app_url"]
3525
+ @content = data["content"]
3526
+ @description = data["description"]
3273
3527
  @id = parse_integer(data["id"])
3274
3528
  @title = data["title"]
3275
3529
  @type = data["type"]
3276
3530
  @url = data["url"]
3277
3531
  @bookmark_url = data["bookmark_url"]
3532
+ @bubble_up_url = data["bubble_up_url"]
3278
3533
  @bucket = parse_type(data["bucket"], "RecordingBucket")
3279
- @content = data["content"]
3280
3534
  @content_attachments = parse_array(data["content_attachments"], "RichTextAttachment")
3281
3535
  @created_at = parse_datetime(data["created_at"])
3282
3536
  @creator = parse_type(data["creator"], "Person")
3283
- @description = data["description"]
3284
3537
  @description_attachments = parse_array(data["description_attachments"], "RichTextAttachment")
3285
3538
  @inherits_status = parse_boolean(data["inherits_status"])
3286
3539
  @parent = parse_type(data["parent"], "RecordingParent")
3540
+ @plain_text_content = data["plain_text_content"]
3541
+ @plain_text_description = data["plain_text_description"]
3287
3542
  @status = data["status"]
3288
3543
  @subject = data["subject"]
3289
3544
  @updated_at = parse_datetime(data["updated_at"])
@@ -3293,25 +3548,28 @@ module Basecamp
3293
3548
  def to_h
3294
3549
  {
3295
3550
  "app_url" => @app_url,
3551
+ "content" => @content,
3552
+ "description" => @description,
3296
3553
  "id" => @id,
3297
3554
  "title" => @title,
3298
3555
  "type" => @type,
3299
3556
  "url" => @url,
3300
3557
  "bookmark_url" => @bookmark_url,
3558
+ "bubble_up_url" => @bubble_up_url,
3301
3559
  "bucket" => @bucket,
3302
- "content" => @content,
3303
3560
  "content_attachments" => @content_attachments,
3304
3561
  "created_at" => @created_at,
3305
3562
  "creator" => @creator,
3306
- "description" => @description,
3307
3563
  "description_attachments" => @description_attachments,
3308
3564
  "inherits_status" => @inherits_status,
3309
3565
  "parent" => @parent,
3566
+ "plain_text_content" => @plain_text_content,
3567
+ "plain_text_description" => @plain_text_description,
3310
3568
  "status" => @status,
3311
3569
  "subject" => @subject,
3312
3570
  "updated_at" => @updated_at,
3313
3571
  "visible_to_clients" => @visible_to_clients,
3314
- }.compact
3572
+ }.reject { |k, v| v.nil? && !["content", "description"].include?(k) }
3315
3573
  end
3316
3574
 
3317
3575
  def to_json(*args)
@@ -3418,17 +3676,112 @@ module Basecamp
3418
3676
  end
3419
3677
  end
3420
3678
 
3679
+ # TimelineAttachment
3680
+ class TimelineAttachment
3681
+ include TypeHelpers
3682
+ attr_accessor :app_download_url, :app_url, :attachable_sgid, :bookmark_url, :boosts_count, :boosts_url, :bucket, :byte_size, :caption, :comments_count, :comments_url, :content_type, :created_at, :creator, :description, :description_attachments, :download_url, :filename, :height, :id, :inherits_status, :key, :parent, :position, :preview_url, :previewable, :sgid, :status, :status_url, :subscription_url, :thumbnail_url, :title, :type, :updated_at, :url, :visible_to_clients, :width
3683
+
3684
+ def initialize(data = {})
3685
+ @app_download_url = data["app_download_url"]
3686
+ @app_url = data["app_url"]
3687
+ @attachable_sgid = data["attachable_sgid"]
3688
+ @bookmark_url = data["bookmark_url"]
3689
+ @boosts_count = parse_integer(data["boosts_count"])
3690
+ @boosts_url = data["boosts_url"]
3691
+ @bucket = parse_type(data["bucket"], "TodoBucket")
3692
+ @byte_size = parse_integer(data["byte_size"])
3693
+ @caption = data["caption"]
3694
+ @comments_count = parse_integer(data["comments_count"])
3695
+ @comments_url = data["comments_url"]
3696
+ @content_type = data["content_type"]
3697
+ @created_at = parse_datetime(data["created_at"])
3698
+ @creator = parse_type(data["creator"], "Person")
3699
+ @description = data["description"]
3700
+ @description_attachments = parse_array(data["description_attachments"], "RichTextAttachment")
3701
+ @download_url = data["download_url"]
3702
+ @filename = data["filename"]
3703
+ @height = parse_integer(data["height"])
3704
+ @id = parse_integer(data["id"])
3705
+ @inherits_status = parse_boolean(data["inherits_status"])
3706
+ @key = data["key"]
3707
+ @parent = parse_type(data["parent"], "RecordingParent")
3708
+ @position = parse_integer(data["position"])
3709
+ @preview_url = data["preview_url"]
3710
+ @previewable = parse_boolean(data["previewable"])
3711
+ @sgid = data["sgid"]
3712
+ @status = data["status"]
3713
+ @status_url = data["status_url"]
3714
+ @subscription_url = data["subscription_url"]
3715
+ @thumbnail_url = data["thumbnail_url"]
3716
+ @title = data["title"]
3717
+ @type = data["type"]
3718
+ @updated_at = parse_datetime(data["updated_at"])
3719
+ @url = data["url"]
3720
+ @visible_to_clients = parse_boolean(data["visible_to_clients"])
3721
+ @width = parse_integer(data["width"])
3722
+ end
3723
+
3724
+ def to_h
3725
+ {
3726
+ "app_download_url" => @app_download_url,
3727
+ "app_url" => @app_url,
3728
+ "attachable_sgid" => @attachable_sgid,
3729
+ "bookmark_url" => @bookmark_url,
3730
+ "boosts_count" => @boosts_count,
3731
+ "boosts_url" => @boosts_url,
3732
+ "bucket" => @bucket,
3733
+ "byte_size" => @byte_size,
3734
+ "caption" => @caption,
3735
+ "comments_count" => @comments_count,
3736
+ "comments_url" => @comments_url,
3737
+ "content_type" => @content_type,
3738
+ "created_at" => @created_at,
3739
+ "creator" => @creator,
3740
+ "description" => @description,
3741
+ "description_attachments" => @description_attachments,
3742
+ "download_url" => @download_url,
3743
+ "filename" => @filename,
3744
+ "height" => @height,
3745
+ "id" => @id,
3746
+ "inherits_status" => @inherits_status,
3747
+ "key" => @key,
3748
+ "parent" => @parent,
3749
+ "position" => @position,
3750
+ "preview_url" => @preview_url,
3751
+ "previewable" => @previewable,
3752
+ "sgid" => @sgid,
3753
+ "status" => @status,
3754
+ "status_url" => @status_url,
3755
+ "subscription_url" => @subscription_url,
3756
+ "thumbnail_url" => @thumbnail_url,
3757
+ "title" => @title,
3758
+ "type" => @type,
3759
+ "updated_at" => @updated_at,
3760
+ "url" => @url,
3761
+ "visible_to_clients" => @visible_to_clients,
3762
+ "width" => @width,
3763
+ }.compact
3764
+ end
3765
+
3766
+ def to_json(*args)
3767
+ to_h.to_json(*args)
3768
+ end
3769
+ end
3770
+
3421
3771
  # TimelineEvent
3422
3772
  class TimelineEvent
3423
3773
  include TypeHelpers
3424
- attr_accessor :action, :app_url, :bucket, :created_at, :creator, :id, :kind, :parent_recording_id, :summary_excerpt, :target, :title, :url
3774
+ attr_accessor :action, :app_url, :attachments, :avatars_sample, :bucket, :created_at, :creator, :data, :id, :kind, :parent_recording_id, :summary_excerpt, :target, :title, :url
3425
3775
 
3426
3776
  def initialize(data = {})
3427
3777
  @action = data["action"]
3428
3778
  @app_url = data["app_url"]
3779
+ @attachments = parse_array(data["attachments"], "TimelineAttachment")
3780
+ @avatars_sample = data["avatars_sample"]
3429
3781
  @bucket = parse_type(data["bucket"], "TodoBucket")
3430
3782
  @created_at = parse_datetime(data["created_at"])
3431
3783
  @creator = parse_type(data["creator"], "Person")
3784
+ @data = parse_type(data["data"], "TimelineEventData")
3432
3785
  @id = parse_integer(data["id"])
3433
3786
  @kind = data["kind"]
3434
3787
  @parent_recording_id = parse_integer(data["parent_recording_id"])
@@ -3442,9 +3795,12 @@ module Basecamp
3442
3795
  {
3443
3796
  "action" => @action,
3444
3797
  "app_url" => @app_url,
3798
+ "attachments" => @attachments,
3799
+ "avatars_sample" => @avatars_sample,
3445
3800
  "bucket" => @bucket,
3446
3801
  "created_at" => @created_at,
3447
3802
  "creator" => @creator,
3803
+ "data" => @data,
3448
3804
  "id" => @id,
3449
3805
  "kind" => @kind,
3450
3806
  "parent_recording_id" => @parent_recording_id,
@@ -3460,6 +3816,35 @@ module Basecamp
3460
3816
  end
3461
3817
  end
3462
3818
 
3819
+ # TimelineEventData
3820
+ class TimelineEventData
3821
+ include TypeHelpers
3822
+ attr_accessor :all_day, :ends_at, :starts_at
3823
+
3824
+ # @return [Array<Symbol>]
3825
+ def self.required_fields
3826
+ %i[all_day ends_at starts_at].freeze
3827
+ end
3828
+
3829
+ def initialize(data = {})
3830
+ @all_day = parse_boolean(data["all_day"])
3831
+ @ends_at = data["ends_at"]
3832
+ @starts_at = data["starts_at"]
3833
+ end
3834
+
3835
+ def to_h
3836
+ {
3837
+ "all_day" => @all_day,
3838
+ "ends_at" => @ends_at,
3839
+ "starts_at" => @starts_at,
3840
+ }.reject { |k, v| v.nil? && !["ends_at", "starts_at"].include?(k) }
3841
+ end
3842
+
3843
+ def to_json(*args)
3844
+ to_h.to_json(*args)
3845
+ end
3846
+ end
3847
+
3463
3848
  # TimesheetEntry
3464
3849
  class TimesheetEntry
3465
3850
  include TypeHelpers
@@ -3667,15 +4052,16 @@ module Basecamp
3667
4052
  # Todolist
3668
4053
  class Todolist
3669
4054
  include TypeHelpers
3670
- attr_accessor :app_url, :bucket, :created_at, :creator, :description_attachments, :id, :inherits_status, :name, :parent, :status, :title, :type, :updated_at, :url, :visible_to_clients, :app_todos_url, :bookmark_url, :boosts_count, :boosts_url, :comments_count, :comments_url, :completed, :completed_ratio, :description, :groups_url, :position, :subscription_url, :todos_url
4055
+ attr_accessor :app_url, :bubble_up_url, :bucket, :created_at, :creator, :description_attachments, :id, :inherits_status, :name, :parent, :status, :title, :type, :updated_at, :url, :visible_to_clients, :app_todos_url, :bookmark_url, :boosts_count, :boosts_url, :comments_count, :comments_url, :completed, :completed_ratio, :description, :groups_url, :position, :subscription_url, :todos_url
3671
4056
 
3672
4057
  # @return [Array<Symbol>]
3673
4058
  def self.required_fields
3674
- %i[app_url bucket created_at creator description_attachments id inherits_status name parent status title type updated_at url visible_to_clients].freeze
4059
+ %i[app_url bubble_up_url bucket created_at creator description_attachments id inherits_status name parent status title type updated_at url visible_to_clients].freeze
3675
4060
  end
3676
4061
 
3677
4062
  def initialize(data = {})
3678
4063
  @app_url = data["app_url"]
4064
+ @bubble_up_url = data["bubble_up_url"]
3679
4065
  @bucket = parse_type(data["bucket"], "TodoBucket")
3680
4066
  @created_at = parse_datetime(data["created_at"])
3681
4067
  @creator = parse_type(data["creator"], "Person")
@@ -3708,6 +4094,7 @@ module Basecamp
3708
4094
  def to_h
3709
4095
  {
3710
4096
  "app_url" => @app_url,
4097
+ "bubble_up_url" => @bubble_up_url,
3711
4098
  "bucket" => @bucket,
3712
4099
  "created_at" => @created_at,
3713
4100
  "creator" => @creator,
@@ -3746,15 +4133,16 @@ module Basecamp
3746
4133
  # TodolistGroup
3747
4134
  class TodolistGroup
3748
4135
  include TypeHelpers
3749
- attr_accessor :app_url, :bucket, :created_at, :creator, :id, :inherits_status, :name, :parent, :status, :title, :type, :updated_at, :url, :visible_to_clients, :app_todos_url, :bookmark_url, :comments_count, :comments_url, :completed, :completed_ratio, :position, :subscription_url, :todos_url
4136
+ attr_accessor :app_url, :bubble_up_url, :bucket, :created_at, :creator, :id, :inherits_status, :name, :parent, :status, :title, :type, :updated_at, :url, :visible_to_clients, :app_todos_url, :bookmark_url, :comments_count, :comments_url, :completed, :completed_ratio, :position, :subscription_url, :todos_url
3750
4137
 
3751
4138
  # @return [Array<Symbol>]
3752
4139
  def self.required_fields
3753
- %i[app_url bucket created_at creator id inherits_status name parent status title type updated_at url visible_to_clients].freeze
4140
+ %i[app_url bubble_up_url bucket created_at creator id inherits_status name parent status title type updated_at url visible_to_clients].freeze
3754
4141
  end
3755
4142
 
3756
4143
  def initialize(data = {})
3757
4144
  @app_url = data["app_url"]
4145
+ @bubble_up_url = data["bubble_up_url"]
3758
4146
  @bucket = parse_type(data["bucket"], "TodoBucket")
3759
4147
  @created_at = parse_datetime(data["created_at"])
3760
4148
  @creator = parse_type(data["creator"], "Person")
@@ -3782,6 +4170,7 @@ module Basecamp
3782
4170
  def to_h
3783
4171
  {
3784
4172
  "app_url" => @app_url,
4173
+ "bubble_up_url" => @bubble_up_url,
3785
4174
  "bucket" => @bucket,
3786
4175
  "created_at" => @created_at,
3787
4176
  "creator" => @creator,
data/lib/basecamp/http.rb CHANGED
@@ -377,7 +377,8 @@ module Basecamp
377
377
  end
378
378
  end
379
379
 
380
- raise last_error || Basecamp::ApiError.new("Request failed after #{@config.max_retries} retries")
380
+ noun = @config.max_retries == 1 ? "attempt" : "attempts"
381
+ raise last_error || Basecamp::ApiError.new("Request failed after #{@config.max_retries} #{noun}")
381
382
  end
382
383
 
383
384
  def single_request(method, url, params:, body:, attempt:, retry_count: 0, allow_cross_origin: false)