tweetkit 0.1.4 → 0.2.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,149 +1,628 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
 
3
5
  module Tweetkit
4
- class Response
5
- attr_accessor :resources, :meta, :original_response, :tweets
6
-
7
- def initialize(response)
8
- @original_response = response.body
9
- parsed_response = JSON.parse(@original_response)
10
- @tweets = Tweetkit::Response::Tweets.new(parsed_response)
11
- @meta = Tweetkit::Response::Meta.new(@tweets.meta)
12
- @resources = Tweetkit::Response::Resources.new(@tweets.resources)
13
- end
14
-
15
- class Resources
6
+ module Response
7
+ class Tweets
16
8
  include Enumerable
17
9
 
18
- VALID_RESOURCES = Set['users', 'tweets', 'media']
10
+ attr_accessor :annotations, :connection, :context_annotations, :entity_annotations, :expansions, :fields, :meta, :options, :original_response, :response, :tweets, :twitter_request
11
+
12
+ def initialize(response, **options)
13
+ parse! response, **options
14
+ end
15
+
16
+ def parse!(response, **options)
17
+ parse_response response
18
+ extract_and_save_tweets
19
+ return unless @tweets
19
20
 
20
- attr_accessor :resources
21
+ extract_and_save_meta
22
+ extract_and_save_expansions
23
+ extract_and_save_options(**options)
24
+ extract_and_save_request
25
+ end
21
26
 
22
- def initialize(resources)
23
- @resources = resources
24
- build_and_normalize_resources(resources) unless resources.nil?
27
+ def parse_response(response)
28
+ @original_response = response.body
29
+ @response = JSON.parse(@original_response)
25
30
  end
26
31
 
27
- def build_and_normalize_resources(resources)
28
- resources.each_key do |resource_type|
29
- normalized_resource = build_and_normalize_resource(@resources[resource_type], resource_type)
30
- instance_variable_set(:"@#{resource_type}", normalized_resource)
31
- self.class.define_method(resource_type) { instance_variable_get("@#{resource_type}") }
32
+ def extract_and_save_tweets
33
+ if (data = @response['data'])
34
+ if data.is_a?(Array)
35
+ @tweets = @response['data'].collect { |tweet| Tweet.new(tweet) }
36
+ else
37
+ @tweets = [Tweet.new(@response['data'])]
38
+ end
39
+ else
40
+ @tweets = nil
32
41
  end
33
42
  end
34
43
 
35
- def build_and_normalize_resource(resource, resource_type)
36
- Tweetkit::Response::Resources::Resource.new(resource, resource_type)
44
+ def extract_and_save_meta
45
+ @meta = Meta.new(@response['meta'])
46
+ end
47
+
48
+ def extract_and_save_expansions
49
+ @expansions = Expansions.new(@response['includes'])
50
+ end
51
+
52
+ def extract_and_save_options(**options)
53
+ @options = options
54
+ end
55
+
56
+ def extract_and_save_request
57
+ @connection = @options[:connection]
58
+ @twitter_request = @options[:twitter_request]
59
+ end
60
+
61
+ def each(*args, &block)
62
+ tweets.each(*args, &block)
63
+ end
64
+
65
+ def last
66
+ tweets.last
37
67
  end
38
68
 
39
- def method_missing(method, **args)
40
- return nil if VALID_RESOURCES.include?(method.to_s)
69
+ def tweet
70
+ tweets.first
71
+ end
41
72
 
42
- super
73
+ def next_page
74
+ connection.params.merge!({ next_token: meta.next_token })
75
+ response = connection.get(twitter_request[:previous_url])
76
+ parse! response,
77
+ connection: connection,
78
+ twitter_request: {
79
+ previous_url: twitter_request[:previous_url],
80
+ previous_query: twitter_request[:previous_query]
81
+ }
82
+ self
43
83
  end
44
84
 
45
- def respond_to_missing?(method, *args)
46
- VALID_RESOURCES.include?(method.to_s) || super
85
+ def prev_page
86
+ connection.params.merge!({ previous: meta.previous_token })
87
+ response = connection.get(twitter_request[:previous_url])
88
+ parse! response,
89
+ connection: connection,
90
+ twitter_request: {
91
+ previous_url: twitter_request[:previous_url],
92
+ previous_query: twitter_request[:previous_query]
93
+ }
94
+ self
47
95
  end
48
96
 
49
- class Resource
50
- include Enumerable
97
+ class Tweet
98
+ attr_accessor :annotations, :attachments, :data
99
+
100
+ def initialize(tweet)
101
+ @data = tweet
102
+ @annotations = Annotations.new(data['context_annotations'], data['entities'])
103
+ @attachments = Attachments.new(data['attachments'])
104
+ end
105
+
106
+ def id
107
+ data['id']
108
+ end
109
+
110
+ def text
111
+ data['text']
112
+ end
113
+
114
+ def author_id
115
+ data['author_id']
116
+ end
117
+
118
+ def conversation_id
119
+ data['conversation_id']
120
+ end
121
+
122
+ def created_at
123
+ data['created_at']
124
+ end
125
+
126
+ def reply_to
127
+ in_reply_to_user_id
128
+ end
129
+
130
+ def in_reply_to_user_id
131
+ data['in_reply_to_user_id']
132
+ end
133
+
134
+ def lang
135
+ data['lang']
136
+ end
137
+
138
+ def nsfw?
139
+ possibly_sensitive
140
+ end
141
+
142
+ def sensitive?
143
+ possibly_sensitive
144
+ end
145
+
146
+ def possibly_sensitive
147
+ data['possibly_sensitive']
148
+ end
149
+
150
+ def permission
151
+ reply_settings
152
+ end
153
+
154
+ def reply_settings
155
+ data['reply_settings']
156
+ end
157
+
158
+ def device
159
+ source
160
+ end
161
+
162
+ def source
163
+ data['source']
164
+ end
51
165
 
52
- attr_accessor :normalized_resource, :original_resource
166
+ def withheld?
167
+ withheld && !withheld.empty?
168
+ end
53
169
 
54
- RESOURCE_NORMALIZATION_KEY = {
55
- 'users': 'id'
56
- }.freeze
170
+ def withheld
171
+ data['withheld']
172
+ end
57
173
 
58
- def initialize(resource, resource_type)
59
- @original_resource = resource
60
- @normalized_resource = {}
61
- normalization_key = RESOURCE_NORMALIZATION_KEY[resource_type.to_sym]
62
- resource.each do |data|
63
- key = data[normalization_key]
64
- @normalized_resource[key.to_i] = data
174
+ def context_annotations
175
+ @annotations.context_annotations || nil
176
+ end
177
+
178
+ def entity_annotations
179
+ entities
180
+ end
181
+
182
+ def entities
183
+ @annotations.entity_annotations || nil
184
+ end
185
+
186
+ class Attachments
187
+ attr_accessor :media_keys, :poll_ids
188
+
189
+ def initialize(attachments)
190
+ return unless attachments
191
+
192
+ @media_keys = attachments['media_keys']
193
+ @poll_ids = attachments['poll_ids']
65
194
  end
66
195
  end
67
196
 
68
- def each(*args, &block)
69
- @normalized_resource.each(*args, &block)
197
+ class Annotations
198
+ attr_accessor :context_annotations, :entity_annotations
199
+
200
+ def initialize(context_annotations, entity_annotations)
201
+ return unless context_annotations || entity_annotations
202
+
203
+ @context_annotations = Context.new(context_annotations)
204
+ @entity_annotations = Entity.new(entity_annotations)
205
+ end
206
+
207
+ class Context
208
+ include Enumerable
209
+
210
+ attr_accessor :annotations
211
+
212
+ def initialize(annotations)
213
+ return unless annotations
214
+
215
+ @annotations = annotations.collect { |annotation| Annotation.new(annotation) }
216
+ end
217
+
218
+ def each(*args, &block)
219
+ annotations.each(*args, &block)
220
+ end
221
+
222
+ class Annotation
223
+ attr_accessor :domain, :entity
224
+
225
+ def initialize(annotation)
226
+ @domain = annotation['domain']
227
+ @entity = annotation['entity']
228
+ end
229
+ end
230
+ end
231
+
232
+ class Entity
233
+ include Enumerable
234
+
235
+ attr_accessor :annotations, :cashtags, :hashtags, :mentions, :urls
236
+
237
+ def initialize(entity_annotations)
238
+ return unless entity_annotations
239
+
240
+ @annotations = Annotations.new(entity_annotations['annotations'])
241
+ @cashtags = Cashtags.new(entity_annotations['cashtags'])
242
+ @hashtags = Hashtags.new(entity_annotations['hashtags'])
243
+ @mentions = Mentions.new(entity_annotations['mentions'])
244
+ @urls = Urls.new(entity_annotations['urls'])
245
+ end
246
+
247
+ def each(*args, &block)
248
+ annotations.each(*args, &block)
249
+ end
250
+
251
+ class Annotations
252
+ attr_accessor :annotations
253
+
254
+ def initialize(annotations)
255
+ return unless annotations
256
+
257
+ @annotations = annotations.collect { |annotation| Annotation.new(annotation) }
258
+ end
259
+
260
+ class Annotation
261
+ attr_accessor :end, :probability, :start, :text, :type
262
+
263
+ def initialize(annotation)
264
+ @end = annotation['end']
265
+ @probability = annotation['probability']
266
+ @start = annotation['start']
267
+ @text = annotation['normalized_text']
268
+ @type = annotation['type']
269
+ end
270
+ end
271
+ end
272
+
273
+ class Cashtags
274
+ attr_accessor :cashtags
275
+
276
+ def initialize(cashtags)
277
+ return unless cashtags
278
+
279
+ @cashtags = cashtags.collect { |cashtag| Cashtag.new(cashtag) }
280
+ end
281
+
282
+ class Cashtag
283
+ attr_accessor :end, :start, :tag
284
+
285
+ def initialize(cashtag)
286
+ @end = cashtag['end']
287
+ @start = cashtag['start']
288
+ @tag = cashtag['tag']
289
+ end
290
+ end
291
+ end
292
+
293
+ class Hashtags
294
+ attr_accessor :hashtags
295
+
296
+ def initialize(hashtags)
297
+ return unless hashtags
298
+
299
+ @hashtags = hashtags.collect { |hashtag| Hashtag.new(hashtag) }
300
+ end
301
+
302
+ class Hashtag
303
+ attr_accessor :end, :start, :tag
304
+
305
+ def initialize(hashtag)
306
+ @end = hashtag['end']
307
+ @start = hashtag['start']
308
+ @tag = hashtag['tag']
309
+ end
310
+ end
311
+ end
312
+
313
+ class Mentions
314
+ attr_accessor :mentions
315
+
316
+ def initialize(mentions)
317
+ return unless mentions
318
+
319
+ @mentions = mentions.collect { |mention| Mention.new(mention) }
320
+ end
321
+
322
+ class Mention
323
+ attr_accessor :end, :id, :start, :username
324
+
325
+ def initialize(mention)
326
+ @end = mention['end']
327
+ @id = mention['id']
328
+ @start = mention['start']
329
+ @username = mention['username']
330
+ end
331
+ end
332
+ end
333
+
334
+ class Urls
335
+ attr_accessor :urls
336
+
337
+ def initialize(urls)
338
+ return unless urls
339
+
340
+ @urls = urls.collect { |url| Url.new(url) }
341
+ end
342
+
343
+ class Url
344
+ attr_accessor :description, :display_url, :end, :expanded_url, :start, :status, :title, :url, :unwound_url
345
+
346
+ def initialize(url)
347
+ @description = url['description']
348
+ @display_url = url['display_url']
349
+ @end = url['end']
350
+ @expanded_url = url['expanded_url']
351
+ @start = url['start']
352
+ @status = url['status']
353
+ @title = url['title']
354
+ @url = url['url']
355
+ @unwound_url = url['unwound_url']
356
+ end
357
+ end
358
+ end
359
+ end
70
360
  end
71
361
 
72
- def each_data(*args, &block)
73
- @normalized_resource.values.each(*args, &block)
362
+ class Geo
363
+ attr_accessor :coordinates, :place_id
364
+
365
+ def initialize(geo)
366
+ return unless geo
367
+
368
+ @coordinates = Coordinates.new(geo['coordinates'])
369
+ @place_id = geo['place_id']
370
+ end
371
+
372
+ class Coordinates
373
+ attr_accessor :coordinates, :type
374
+
375
+ def initialize(coordinates)
376
+ @coordinates = coordinates['coordinates']
377
+ @type = coordinates['point']
378
+ end
379
+
380
+ def x
381
+ coordinates[0]
382
+ end
383
+
384
+ def y
385
+ coordinates[0]
386
+ end
387
+ end
74
388
  end
75
389
 
76
- def find(key)
77
- @normalized_resource[key.to_i]
390
+ class Metrics
391
+ attr_accessor :public_metrics
392
+
393
+ def initialize(**metrics)
394
+ return unless metrics
395
+
396
+ @public_metrics = Public.new(metrics[:public_metrics])
397
+ end
398
+
399
+ class Public
400
+ attr_accessor :like_count, :quote_count, :reply_count, :retweet_count
401
+
402
+ def initialize(public_metric)
403
+ @like_count = public_metric['like_count']
404
+ @quote_count = public_metric['quote_count']
405
+ @reply_count = public_metric['reply_count']
406
+ @retweet_count = public_metric['retweet_count']
407
+ end
408
+
409
+ def likes
410
+ @like_count
411
+ end
412
+
413
+ def quotes
414
+ @quote_count
415
+ end
416
+
417
+ def replies
418
+ @reply_count
419
+ end
420
+
421
+ def retweets
422
+ @retweet_count
423
+ end
424
+ end
78
425
  end
79
426
  end
80
- end
81
427
 
82
- class Meta
83
- include Enumerable
428
+ class Expansions
429
+ attr_accessor :media, :places, :polls, :tweets, :users
84
430
 
85
- attr_accessor :meta
431
+ def initialize(expansions)
432
+ return unless expansions
86
433
 
87
- def initialize(meta)
88
- @meta = meta
89
- end
434
+ @media = Media.new(expansions['media'])
435
+ @places = expansions['places']
436
+ @polls = expansions['polls']
437
+ @tweets = Tweets.new(expansions['tweets'])
438
+ @users = Users.new(expansions['users'])
439
+ end
90
440
 
91
- def method_missing(attribute, **args)
92
- data = meta[attribute.to_s]
93
- data.empty? ? super : data
94
- end
441
+ class Media
442
+ attr_accessor :media
95
443
 
96
- def respond_to_missing?(method, *args)
97
- meta.respond_to? method
98
- end
99
- end
444
+ def initialize(media)
445
+ return unless media
100
446
 
101
- class Tweets
102
- include Enumerable
447
+ @media = media.collect { |media_object| MediaObject.new(media_object) }
448
+ end
103
449
 
104
- attr_accessor :tweets, :meta, :resources
450
+ class MediaObject
451
+ attr_accessor :media_key, :type
105
452
 
106
- def initialize(response)
107
- @tweets = response['data'] ? response['data'].collect { |tweet| Tweetkit::Response::Tweet.new(tweet) } : []
108
- @meta = response['meta']
109
- @resources = response['includes']
110
- end
453
+ def initialize(media_object)
454
+ @media_key = media_object['media_key']
455
+ @type = media_object['type']
456
+ end
457
+ end
458
+ end
111
459
 
112
- def each(*args, &block)
113
- tweets.each(*args, &block)
114
- end
460
+ class Places
461
+ attr_accessor :places
115
462
 
116
- def last
117
- tweets.last
118
- end
463
+ def initialize(places)
464
+ return unless places
119
465
 
120
- def to_s
121
- @tweets.join(' ')
122
- end
466
+ @places = places.collect { |place| Place.new(place) }
467
+ end
123
468
 
124
- def method_missing(method, **args)
125
- tweets.public_send(method, **args)
126
- end
469
+ class Place
470
+ attr_accessor :full_name, :id
127
471
 
128
- def respond_to_missing?(method, *args)
129
- tweets.respond_to?(method)
130
- end
131
- end
472
+ def initialize(place)
473
+ @full_name = place['full_name']
474
+ @id = place['id']
475
+ end
476
+ end
477
+ end
478
+
479
+ class Polls
480
+ attr_accessor :polls
481
+
482
+ def initialize(polls)
483
+ return unless polls
484
+
485
+ @polls = polls.collect { |poll| Poll.new(poll) }
486
+ end
132
487
 
133
- class Tweet
134
- attr_accessor :tweet
488
+ class Poll
489
+ attr_accessor :id, :options
135
490
 
136
- def initialize(tweet)
137
- @tweet = tweet
491
+ def initialize(poll)
492
+ @id = poll['id']
493
+ @options = Options.new(poll['options'])
494
+ end
495
+
496
+ class Options
497
+ attr_accessor :options
498
+
499
+ def initialize(options)
500
+ @options = options.collect { |option| Option.new(option) }
501
+ end
502
+
503
+ class Option
504
+ attr_accessor :label, :position, :votes
505
+
506
+ def initialize(option)
507
+ @label = option['label']
508
+ @position = option['position']
509
+ @votes = option['votes']
510
+ end
511
+ end
512
+ end
513
+ end
514
+ end
515
+
516
+ class Tweets
517
+ attr_accessor :tweets
518
+
519
+ def initialize(tweets)
520
+ return unless tweets
521
+
522
+ @tweets = tweets.collect { |tweet| Tweet.new(tweet) }
523
+ end
524
+ end
525
+
526
+ class Users
527
+ attr_accessor :users
528
+
529
+ def initialize(users)
530
+ return unless users
531
+
532
+ @users = users.collect { |user| User.new(user) }
533
+ end
534
+
535
+ class User
536
+ attr_accessor :id, :name, :username
537
+
538
+ def initialize(user)
539
+ @id = user['id']
540
+ @name = user['name']
541
+ @username = user['username']
542
+ end
543
+ end
544
+ end
138
545
  end
139
546
 
140
- def method_missing(attribute)
141
- data = tweet[attribute.to_s]
142
- data.empty? ? super : data
547
+ class Fields
548
+ attr_accessor :fields, :media_fields, :place_fields, :poll_fields, :tweet_fields, :user_fields
549
+
550
+ def initialize(fields)
551
+ @fields = fields
552
+ build_and_normalize_fields(fields) unless fields.nil?
553
+ end
554
+
555
+ def build_and_normalize_fields(fields)
556
+ fields.each_key do |field_type|
557
+ normalized_field = build_and_normalize_field(@fields[field_type], field_type)
558
+ instance_variable_set(:"@#{field_type}", normalized_field)
559
+ self.class.define_method(field_type) { instance_variable_get("@#{field_type}") }
560
+ end
561
+ end
562
+
563
+ def build_and_normalize_field(field, field_type)
564
+ Field.new(field, field_type)
565
+ end
566
+
567
+ def method_missing(method, **args)
568
+ return nil if VALID_FIELDS.include?(method.to_s)
569
+
570
+ super
571
+ end
572
+
573
+ def respond_to_missing?(method, *args)
574
+ VALID_FIELDS.include?(method.to_s) || super
575
+ end
576
+
577
+ class Field
578
+ include Enumerable
579
+
580
+ attr_accessor :normalized_field, :original_field
581
+
582
+ FIELD_NORMALIZATION_KEY = {
583
+ 'users': 'id'
584
+ }.freeze
585
+
586
+ def initialize(field, field_type)
587
+ @original_field = field
588
+ @normalized_field = {}
589
+ normalization_key = FIELD_NORMALIZATION_KEY[field_type.to_sym]
590
+ field.each do |data|
591
+ key = data[normalization_key]
592
+ @normalized_field[key.to_i] = data
593
+ end
594
+ end
595
+
596
+ def each(*args, &block)
597
+ @normalized_field.each(*args, &block)
598
+ end
599
+
600
+ def each_data(*args, &block)
601
+ @normalized_field.values.each(*args, &block)
602
+ end
603
+
604
+ def find(key)
605
+ @normalized_field[key.to_i]
606
+ end
607
+ end
143
608
  end
144
609
 
145
- def respond_to_missing?(method, *args)
146
- tweet.respond_to?(method) || super
610
+ class Meta
611
+ attr_accessor :data
612
+
613
+ def initialize(meta)
614
+ return unless meta
615
+
616
+ @data = meta
617
+ end
618
+
619
+ def next_token
620
+ @data['next_token']
621
+ end
622
+
623
+ def previous_token
624
+ @data['previous_token']
625
+ end
147
626
  end
148
627
  end
149
628
  end