tweetkit 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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