apple-news 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/README.md +138 -0
  7. data/Rakefile +6 -0
  8. data/apple-news.gemspec +28 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/lib/apple-news.rb +41 -0
  12. data/lib/apple-news/addition.rb +7 -0
  13. data/lib/apple-news/additions/base.rb +15 -0
  14. data/lib/apple-news/additions/link.rb +9 -0
  15. data/lib/apple-news/animation.rb +7 -0
  16. data/lib/apple-news/animations/appear.rb +7 -0
  17. data/lib/apple-news/animations/base.rb +17 -0
  18. data/lib/apple-news/animations/fade_in.rb +8 -0
  19. data/lib/apple-news/animations/move_in.rb +8 -0
  20. data/lib/apple-news/animations/scale_fade.rb +8 -0
  21. data/lib/apple-news/article.rb +33 -0
  22. data/lib/apple-news/behavior.rb +7 -0
  23. data/lib/apple-news/behaviors/background_motion.rb +7 -0
  24. data/lib/apple-news/behaviors/background_parallax.rb +7 -0
  25. data/lib/apple-news/behaviors/base.rb +15 -0
  26. data/lib/apple-news/behaviors/motion.rb +7 -0
  27. data/lib/apple-news/behaviors/parallax.rb +8 -0
  28. data/lib/apple-news/behaviors/springy.rb +7 -0
  29. data/lib/apple-news/channel.rb +40 -0
  30. data/lib/apple-news/component.rb +28 -0
  31. data/lib/apple-news/component_layout.rb +9 -0
  32. data/lib/apple-news/components/audio.rb +16 -0
  33. data/lib/apple-news/components/author.rb +8 -0
  34. data/lib/apple-news/components/banner_advertisement.rb +14 -0
  35. data/lib/apple-news/components/base.rb +17 -0
  36. data/lib/apple-news/components/body.rb +8 -0
  37. data/lib/apple-news/components/byline.rb +8 -0
  38. data/lib/apple-news/components/caption.rb +8 -0
  39. data/lib/apple-news/components/chapter.rb +10 -0
  40. data/lib/apple-news/components/container.rb +8 -0
  41. data/lib/apple-news/components/divider.rb +8 -0
  42. data/lib/apple-news/components/embed_web_video.rb +12 -0
  43. data/lib/apple-news/components/figure.rb +8 -0
  44. data/lib/apple-news/components/gallery.rb +8 -0
  45. data/lib/apple-news/components/header.rb +7 -0
  46. data/lib/apple-news/components/heading.rb +8 -0
  47. data/lib/apple-news/components/illustrator.rb +8 -0
  48. data/lib/apple-news/components/image.rb +14 -0
  49. data/lib/apple-news/components/instagram.rb +9 -0
  50. data/lib/apple-news/components/intro.rb +8 -0
  51. data/lib/apple-news/components/logo.rb +8 -0
  52. data/lib/apple-news/components/medium_rectangle_advertisement.rb +7 -0
  53. data/lib/apple-news/components/mosaic.rb +8 -0
  54. data/lib/apple-news/components/music.rb +8 -0
  55. data/lib/apple-news/components/photo.rb +8 -0
  56. data/lib/apple-news/components/photographer.rb +8 -0
  57. data/lib/apple-news/components/portrait.rb +8 -0
  58. data/lib/apple-news/components/pullquote.rb +8 -0
  59. data/lib/apple-news/components/quote.rb +8 -0
  60. data/lib/apple-news/components/scalable_image.rb +14 -0
  61. data/lib/apple-news/components/section.rb +8 -0
  62. data/lib/apple-news/components/text.rb +13 -0
  63. data/lib/apple-news/components/title.rb +8 -0
  64. data/lib/apple-news/components/tweet.rb +9 -0
  65. data/lib/apple-news/components/video.rb +12 -0
  66. data/lib/apple-news/configuration.rb +12 -0
  67. data/lib/apple-news/document.rb +22 -0
  68. data/lib/apple-news/document/attachments.rb +24 -0
  69. data/lib/apple-news/document/metadata.rb +43 -0
  70. data/lib/apple-news/document/persistence.rb +42 -0
  71. data/lib/apple-news/layout.rb +8 -0
  72. data/lib/apple-news/links.rb +19 -0
  73. data/lib/apple-news/metadata.rb +15 -0
  74. data/lib/apple-news/multipart_post_patch.rb +29 -0
  75. data/lib/apple-news/properties.rb +87 -0
  76. data/lib/apple-news/properties/advertising_layout.rb +7 -0
  77. data/lib/apple-news/properties/advertising_settings.rb +7 -0
  78. data/lib/apple-news/properties/anchor.rb +15 -0
  79. data/lib/apple-news/properties/base.rb +7 -0
  80. data/lib/apple-news/properties/caption_descriptor.rb +8 -0
  81. data/lib/apple-news/properties/color_stop.rb +8 -0
  82. data/lib/apple-news/properties/content_inset.rb +7 -0
  83. data/lib/apple-news/properties/gallery_item.rb +7 -0
  84. data/lib/apple-news/properties/margin.rb +7 -0
  85. data/lib/apple-news/properties/offset.rb +7 -0
  86. data/lib/apple-news/property.rb +7 -0
  87. data/lib/apple-news/request.rb +6 -0
  88. data/lib/apple-news/requests/get.rb +28 -0
  89. data/lib/apple-news/requests/post.rb +57 -0
  90. data/lib/apple-news/resource.rb +25 -0
  91. data/lib/apple-news/scene.rb +7 -0
  92. data/lib/apple-news/scenes/base.rb +15 -0
  93. data/lib/apple-news/scenes/fading_sticky_header.rb +8 -0
  94. data/lib/apple-news/scenes/parallax_scale_header.rb +7 -0
  95. data/lib/apple-news/section.rb +27 -0
  96. data/lib/apple-news/security.rb +46 -0
  97. data/lib/apple-news/style.rb +10 -0
  98. data/lib/apple-news/styles/base.rb +7 -0
  99. data/lib/apple-news/styles/border.rb +7 -0
  100. data/lib/apple-news/styles/component.rb +7 -0
  101. data/lib/apple-news/styles/component_text.rb +9 -0
  102. data/lib/apple-news/styles/document.rb +7 -0
  103. data/lib/apple-news/styles/drop_cap.rb +9 -0
  104. data/lib/apple-news/styles/fill.rb +11 -0
  105. data/lib/apple-news/styles/gradient_fill.rb +12 -0
  106. data/lib/apple-news/styles/image_fill.rb +11 -0
  107. data/lib/apple-news/styles/inline_text.rb +7 -0
  108. data/lib/apple-news/styles/linear_gradient_fill.rb +9 -0
  109. data/lib/apple-news/styles/shadow.rb +8 -0
  110. data/lib/apple-news/styles/stroke.rb +7 -0
  111. data/lib/apple-news/styles/text.rb +8 -0
  112. data/lib/apple-news/styles/text_stroke.rb +8 -0
  113. data/lib/apple-news/styles/video_fill.rb +12 -0
  114. data/lib/apple-news/version.rb +3 -0
  115. metadata +241 -0
@@ -0,0 +1,8 @@
1
+ module AppleNews
2
+ module Component
3
+ class Title < Base
4
+ include Text
5
+ role "title"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module AppleNews
2
+ module Component
3
+ class Tweet < Base
4
+ role "tweet"
5
+ required_property :url
6
+ property_inflection :url, 'URL'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module AppleNews
2
+ module Component
3
+ class Video < Base
4
+ role "video"
5
+ required_property :url
6
+ optional_properties :accessibility_caption, :aspect_ratio, :explicit_content, :still_url
7
+
8
+ property_inflection :url, 'URL'
9
+ property_inflection :still_url, 'stillURL'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module AppleNews
2
+ class Configuration
3
+ attr_accessor :channel_id, :api_key_id, :api_key_secret, :api_base
4
+
5
+ def initialize
6
+ @channel_id = nil
7
+ @api_key_id = nil
8
+ @api_key_secret = nil
9
+ @api_base = 'https://news-api.apple.com'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ require 'stringio'
2
+
3
+ require 'apple-news/document/attachments'
4
+ require 'apple-news/document/metadata'
5
+ require 'apple-news/document/persistence'
6
+
7
+ module AppleNews
8
+ class Document
9
+ include Attachments
10
+ include Metadata
11
+ include Persistence
12
+ include Resource
13
+
14
+ attr_accessor :article, :metadata
15
+
16
+ def initialize(article = nil, metadata = {})
17
+ @article = article
18
+ @metadata = {}
19
+ @files = {}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module AppleNews
2
+ class Document
3
+ module Attachments
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ def add_file(file, mime = nil)
8
+ filename = Pathname.new(file).basename.to_s
9
+ mime ||= MIME::Types.type_for(filename)
10
+
11
+ @files[filename] = UploadIO.new(file, mime, filename)
12
+ end
13
+
14
+ def add_file_at_path(path)
15
+ add_file(File.new(path))
16
+ end
17
+
18
+ def add_string_as_file(name, contents, type)
19
+ @files[name] = UploadIO.new(StringIO.new(contents), type, name)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ module AppleNews
2
+ class Document
3
+ module Metadata
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ def is_sponsored?
8
+ @metadata['isSponsored']
9
+ end
10
+ alias_method :sponsored?, :is_sponsored?
11
+
12
+ def is_sponsored=(val)
13
+ @metadata['isSponsored'] = val
14
+ end
15
+
16
+ def is_preview?
17
+ @metadata['isPreview']
18
+ end
19
+ alias_method :preview?, :is_preview?
20
+
21
+ def is_preview=(val)
22
+ @metadata['isPreview'] = val
23
+ end
24
+
25
+ def accessory_text
26
+ @metadata['accessoryText']
27
+ end
28
+
29
+ def accessory_text=(val)
30
+ @metadata['accessoryText'] = val
31
+ end
32
+
33
+ def links
34
+ @metadata['links']
35
+ end
36
+
37
+ def links=(val)
38
+ @metadata['links'] = val
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ module AppleNews
2
+ class Document
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ def save!
8
+ request = AppleNews::Request::Post.new(endpoint_url)
9
+ request.fields = {
10
+ 'metadata' => metadata_field,
11
+ 'article.json' => article_json
12
+ }.merge(@files)
13
+
14
+ resp = request.call
15
+ article.update_with_data(resp['data'].delete('document'))
16
+ end
17
+
18
+ private
19
+
20
+ def endpoint_url
21
+ if article.persisted?
22
+ "/articles/#{id}"
23
+ else
24
+ "/channels/#{AppleNews.config.channel_id}/articles"
25
+ end
26
+ end
27
+
28
+ def metadata_field
29
+ JSON.dump({ data: @metadata })
30
+ end
31
+
32
+ def article_json
33
+ UploadIO.new(
34
+ StringIO.new(JSON.dump(article.as_json)),
35
+ "application/json",
36
+ "article.json"
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ module AppleNews
2
+ class Layout
3
+ include Properties
4
+
5
+ required_properties :columns, :width
6
+ optional_properties :gutter, :margin
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module AppleNews
2
+ module Links
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def link(name)
7
+ URI::parse(@links[name]).path
8
+ end
9
+
10
+ def section_link_id(name)
11
+ link(name).match(/\/sections\/([A-Za-z0-9\-]+)/)[1]
12
+ end
13
+
14
+ def channel_link_id(name)
15
+ link(name).match(/\/channels\/([A-Za-z0-9\-]+)/)[1]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module AppleNews
2
+ class Metadata
3
+ include Properties
4
+ optional_properties :authors, :campaign_data, :canonical_url, :date_created,
5
+ :date_modified, :date_published, :excerpt, :generator_identifier,
6
+ :keywords, :thumbnail_url
7
+
8
+ optional_property :generator_name, "apple-news-rb"
9
+ optional_property :generator_version, AppleNews::VERSION
10
+ optional_property :transparent_toolbar, false
11
+
12
+ property_inflection :canonical_url, 'canonicalURL'
13
+ property_inflection :thumbnail_url, 'thumbnailURL'
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ # This is really shitty and no one should ever monkey patch, but the Apple News API
2
+ # requires that the size of the form part should be specified in the Content-Disposition
3
+ # header, and not with the Content-Length header. I'm not sure if size is even a valid param
4
+ # in the Content-Disposition header, but here we are.
5
+ module Parts
6
+ class FilePart
7
+ def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
8
+ trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
9
+ content_disposition = opts["Content-Disposition"] || "form-data"
10
+
11
+ part = ''
12
+ part << "--#{boundary}\r\n"
13
+ part << "Content-Disposition: #{content_disposition}; name=#{name}; filename=\"#{filename}\"; size=#{content_len}\r\n"
14
+ part << "Content-Length: #{content_len}\r\n"
15
+ if content_id = opts["Content-ID"]
16
+ part << "Content-ID: #{content_id}\r\n"
17
+ end
18
+
19
+ if headers["Content-Type"] != nil
20
+ part << "Content-Type: " + headers["Content-Type"] + "\r\n"
21
+ else
22
+ part << "Content-Type: #{type}\r\n"
23
+ end
24
+
25
+ part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
26
+ part << "\r\n"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ module AppleNews
2
+ module Properties
3
+ extend ActiveSupport::Concern
4
+
5
+ def initialize(opts = nil)
6
+ if !opts.nil?
7
+ opts = ActiveSupport::HashWithIndifferentAccess.new(opts)
8
+ self.class.properties.each do |prop, default|
9
+ instance_variable_set "@#{prop}", opts.fetch(prop, default)
10
+ end
11
+ end
12
+ end
13
+
14
+ included do
15
+ class_attribute :_required_property_map
16
+ class_attribute :_optional_property_map
17
+ class_attribute :_property_inflection
18
+
19
+ self._required_property_map ||= {}
20
+ self._optional_property_map ||= {}
21
+ self._property_inflection ||= {}
22
+
23
+ def valid?
24
+ _required_property_map.each do |prop, _|
25
+ return false if send(prop).nil?
26
+ end
27
+
28
+ true
29
+ end
30
+
31
+ def properties
32
+ self.class.properties
33
+ end
34
+
35
+ def as_json
36
+ Hash[properties.map { |key, _|
37
+ json_key = _property_inflection[key] || key.to_s.camelize(:lower)
38
+ val = if send(key).respond_to?(:as_json)
39
+ send(key).as_json
40
+ elsif send(key).is_a?(Array)
41
+ send(key).map(&:as_json)
42
+ elsif send(key).is_a?(Hash)
43
+ Hash[send(key).map {|k, v| [k.to_s, v.as_json]}]
44
+ else
45
+ send(key)
46
+ end
47
+
48
+ [json_key, val]
49
+ }.reject { |p| p[1].nil? }]
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ def inherited(base)
55
+ super
56
+ base._required_property_map = _required_property_map.dup
57
+ base._optional_property_map = _optional_property_map.dup
58
+ end
59
+
60
+ def required_properties(*args)
61
+ args.each { |arg| required_property(arg) }
62
+ end
63
+
64
+ def required_property(name, default = nil)
65
+ _required_property_map[name] = default
66
+ attr_accessor name
67
+ end
68
+
69
+ def optional_properties(*args)
70
+ args.each { |arg| optional_property(arg) }
71
+ end
72
+
73
+ def optional_property(name, default = nil)
74
+ _optional_property_map[name] = default
75
+ attr_accessor name
76
+ end
77
+
78
+ def property_inflection(name, inflection)
79
+ _property_inflection[name] = inflection
80
+ end
81
+
82
+ def properties
83
+ _required_property_map.merge(_optional_property_map)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class AdvertisingLayout < Base
4
+ required_property :margin
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class AdvertisingSettings < Base
4
+ optional_properties :frequency, :layout
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module AppleNews
2
+ module Property
3
+ class Anchor < Base
4
+ required_property :target_anchor_position
5
+ optional_properties :origin_anchor_position, :range_length, :range_start,
6
+ :target_component_identifier
7
+
8
+ def valid?
9
+ super && [
10
+ 'top', 'center', 'bottom'
11
+ ].include?(target_anchor_position)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class Base
4
+ include Properties
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module AppleNews
2
+ module Property
3
+ class CaptionDescriptor < Base
4
+ required_property :text
5
+ optional_properties :text_style, :format, :inline_text_styles, :additions
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module AppleNews
2
+ module Property
3
+ class ColorStop < Base
4
+ required_property :color
5
+ optional_properties :location
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class ContentInset < Base
4
+ optional_properties :bottom, :left, :right, :top
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class GalleryItem < Base
4
+ optional_properties :url, :caption, :accessibility_caption, :explicit_content
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class Margin < Base
4
+ optional_properties :bottom, :top
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module AppleNews
2
+ module Property
3
+ class Offset < Base
4
+ required_properties :x, :y
5
+ end
6
+ end
7
+ end