duolingo_personal_data 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +20 -0
  3. data/.rubocop_todo.yml +50 -0
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +202 -0
  7. data/README.md +38 -0
  8. data/Rakefile +22 -0
  9. data/duolingo_personal_data.gemspec +26 -0
  10. data/fixtures/TeacherPrivacySettings.csv +2 -0
  11. data/fixtures/auth_data.csv +6 -0
  12. data/fixtures/avatar_images.csv +13 -0
  13. data/fixtures/duolingo-blast-emails.csv +17 -0
  14. data/fixtures/duolingo-notify-data.csv +4 -0
  15. data/fixtures/friends-follow.csv +2 -0
  16. data/fixtures/inventory.csv +5 -0
  17. data/fixtures/languages.csv +4 -0
  18. data/fixtures/leaderboards.csv +4 -0
  19. data/fixtures/profile.csv +13 -0
  20. data/fixtures/stories-story-completions.csv +4 -0
  21. data/fixtures/stories.csv +2 -0
  22. data/lib/duolingo_personal_data/auth_data.rb +39 -0
  23. data/lib/duolingo_personal_data/avatar_images.rb +24 -0
  24. data/lib/duolingo_personal_data/blast_emails.rb +84 -0
  25. data/lib/duolingo_personal_data/friends_follow.rb +47 -0
  26. data/lib/duolingo_personal_data/inventory.rb +48 -0
  27. data/lib/duolingo_personal_data/languages.rb +42 -0
  28. data/lib/duolingo_personal_data/leaderboards.rb +24 -0
  29. data/lib/duolingo_personal_data/notify_data.rb +33 -0
  30. data/lib/duolingo_personal_data/profile.rb +67 -0
  31. data/lib/duolingo_personal_data/stories.rb +29 -0
  32. data/lib/duolingo_personal_data/story_completions.rb +23 -0
  33. data/lib/duolingo_personal_data/teacher_privacy_settings.rb +51 -0
  34. data/lib/duolingo_personal_data/version.rb +3 -0
  35. data/lib/duolingo_personal_data.rb +17 -0
  36. data/manifest.scm +3 -0
  37. data/sig/duolingo_personal_data.rbs +272 -0
  38. metadata +83 -0
@@ -0,0 +1,47 @@
1
+ module DuolingoPersonalData
2
+ class FriendsFollow
3
+ def initialize(csv_path)
4
+ @csv_path = csv_path
5
+ end
6
+
7
+ def num_following
8
+ @num_following ||= integer_value_from_property("num_following")
9
+ end
10
+
11
+ def num_followers
12
+ @num_followers ||= integer_value_from_property("num_followers")
13
+ end
14
+
15
+ def num_blocking
16
+ @num_blocking ||= integer_value_from_property("num_blocking")
17
+ end
18
+
19
+ def num_blockers
20
+ @num_blockers ||= integer_value_from_property("num_blockers")
21
+ end
22
+
23
+ def timestamp_generated
24
+ @timestamp_generated ||= timestamp_value_from_property("timestamp_generated")
25
+ end
26
+
27
+ private
28
+
29
+ def value_from_property(property_name)
30
+ table.first[property_name]
31
+ end
32
+
33
+ def integer_value_from_property(property_name)
34
+ value = value_from_property(property_name)
35
+ Integer(value)
36
+ end
37
+
38
+ def timestamp_value_from_property(property_name)
39
+ value = integer_value_from_property(property_name)
40
+ Time.at(value)
41
+ end
42
+
43
+ def table
44
+ @table ||= CSV.read(@csv_path, headers: true)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ require "forwardable"
2
+
3
+ module DuolingoPersonalData
4
+ InventoryItem = Struct.new(:item_type, :purchase_datetime, :active, :price_in_virtual_currency, :wager_day,
5
+ :payment_processor, :product, :expected_expiration, keyword_init: true)
6
+
7
+ class Inventory
8
+ def initialize(csv_path)
9
+ @csv_path = csv_path
10
+ end
11
+
12
+ extend Forwardable
13
+ def_delegators :items, *(Array.instance_methods - [:object_id])
14
+
15
+ private
16
+
17
+ def items
18
+ @items ||= table.map do |row|
19
+ item = InventoryItem.new(item_type: row["item_type"],
20
+ purchase_datetime: parse_datetime(row["purchase_datetime"]), active: parse_boolean(row["active"]), payment_processor: row["payment_processor"], product: row["product"])
21
+ price = row["price_in_virtual_currency"]
22
+ item.price_in_virtual_currency = Integer(price) if price
23
+ day = row["wager_day"]
24
+ item.wager_day = Integer(day) if day
25
+ expiration = row["expected_expiration"]
26
+ item.expected_expiration = parse_datetime(expiration) if expiration
27
+ item
28
+ end
29
+ end
30
+
31
+ def parse_datetime(str)
32
+ Time.strptime(str, "%Y-%m-%d %T")
33
+ end
34
+
35
+ def parse_boolean(str)
36
+ case str
37
+ when "false" then false
38
+ when "true" then true
39
+ else
40
+ raise Error, "cannot parse #{str.inspect} as boolean"
41
+ end
42
+ end
43
+
44
+ def table
45
+ @table ||= CSV.read(@csv_path, headers: true)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ require "forwardable"
2
+ require "csv"
3
+
4
+ module DuolingoPersonalData
5
+ Language = Struct.new(:from_language, :points, :skill_learned, :total_lessons, :days_active, :last_active,
6
+ :prior_proficiency, :subscribed, keyword_init: true)
7
+ class Languages
8
+ def initialize(csv_path)
9
+ @csv_path = csv_path
10
+ end
11
+
12
+ extend Forwardable
13
+ def_delegators :languages, *(Hash.instance_methods - [:object_id])
14
+
15
+ private
16
+
17
+ def languages
18
+ @languages ||= CSV.read(@csv_path, headers: true).map do |row|
19
+ language = Language.new(from_language: row["from_language"])
20
+ points = row["points"]
21
+ language.points = Integer(points) if points
22
+ skills = row["skills_learned"]
23
+ language.skill_learned = Integer(skills) if skills
24
+ lessons = row["total_lessons"]
25
+ language.total_lessons = Integer(lessons) if lessons
26
+ days = row["days_active"]
27
+ language.days_active = Integer(days) if days
28
+ active = row["last_active"]
29
+ language.last_active = parse_datetime(active) if active
30
+ proficiency = row["prior_proficiency"]
31
+ language.prior_proficiency = Integer(proficiency) if proficiency
32
+ subscribed = row["subscribed"]
33
+ language.subscribed = parse_datetime(subscribed) if subscribed
34
+ { row["learning_language"] => language }
35
+ end.reduce(&:merge)
36
+ end
37
+
38
+ def parse_datetime(str)
39
+ Time.strptime(str, "%Y-%m-%d %T")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ require "csv"
2
+ require "forwardable"
3
+
4
+ module DuolingoPersonalData
5
+ LeaderboardsEntry = Struct.new(:timestamp, :tier, :score, keyword_init: true)
6
+
7
+ class Leaderboards
8
+ def initialize(csv_path)
9
+ @csv_path = csv_path
10
+ end
11
+
12
+ extend Forwardable
13
+ def_delegators :leaderboards_entries, *(Array.instance_methods - [:object_id])
14
+
15
+ private
16
+
17
+ def leaderboards_entries
18
+ @leaderboards_entries ||= CSV.read(@csv_path, headers: true).map do |row|
19
+ LeaderboardsEntry.new(timestamp: Time.strptime(row["timestamp"], "%FT%TZ"), tier: Integer(row["tier"]),
20
+ score: Float(row["score"]))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ require "json"
2
+
3
+ module DuolingoPersonalData
4
+ class NotifyData
5
+ def initialize(csv_path)
6
+ @csv_path = csv_path
7
+ end
8
+
9
+ def email
10
+ @email ||= value_from_property("email")
11
+ end
12
+
13
+ def device_ids
14
+ @device_ids ||= json_value_from_property("device_ids")
15
+ end
16
+
17
+ private
18
+
19
+ def value_from_property(property_name)
20
+ table.find { |row| row["property"] == property_name }["value"]
21
+ end
22
+
23
+ def json_value_from_property(property_name)
24
+ value = value_from_property(property_name)
25
+ value.gsub!("'", '"')
26
+ JSON.parse(value)
27
+ end
28
+
29
+ def table
30
+ @table ||= CSV.read(@csv_path, headers: true)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,67 @@
1
+ require "csv"
2
+
3
+ module DuolingoPersonalData
4
+ class Profile
5
+ def initialize(csv_path)
6
+ @csv_path = csv_path
7
+ end
8
+
9
+ def username
10
+ @username ||= value_from_property("username")
11
+ end
12
+
13
+ def email
14
+ @email ||= value_from_property("email")
15
+ end
16
+
17
+ def fullname
18
+ @fullname ||= value_from_property("fullname")
19
+ end
20
+
21
+ def joined_at
22
+ @joined_at ||= datetime_value_from_property("joined_at")
23
+ end
24
+
25
+ def ui_language
26
+ @ui_language ||= value_from_property("ui_language")
27
+ end
28
+
29
+ def learning_language
30
+ @learning_language ||= value_from_property("learning_language")
31
+ end
32
+
33
+ def lingots
34
+ @lingots ||= integer_value_from_property("lingots")
35
+ end
36
+
37
+ def daily_goal
38
+ @daily_goal ||= integer_value_from_property("daily_goal")
39
+ end
40
+
41
+ def timezone
42
+ @timezone ||= value_from_property("timezone")
43
+ end
44
+
45
+ def avatar_url
46
+ @avatar_url ||= value_from_property("avatar_url")
47
+ end
48
+
49
+ private
50
+
51
+ def value_from_property(property_name)
52
+ table.find { |row| row["name"] == property_name }["value"]
53
+ end
54
+
55
+ def integer_value_from_property(property_name)
56
+ Integer(value_from_property(property_name))
57
+ end
58
+
59
+ def datetime_value_from_property(property_name)
60
+ Time.strptime(value_from_property(property_name), "%F %T")
61
+ end
62
+
63
+ def table
64
+ @table ||= CSV.read(@csv_path, headers: true)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ module DuolingoPersonalData
2
+ class Stories
3
+ def initialize(csv_path)
4
+ @csv_path = csv_path
5
+ end
6
+
7
+ def user_id
8
+ @user_id ||= value_from_property("userId")
9
+ end
10
+
11
+ def date_of_first_visit_to_stories
12
+ @date_of_first_visit_to_stories ||= datetime_value_from_property("dateOfFirstVisitToStories")
13
+ end
14
+
15
+ private
16
+
17
+ def value_from_property(property_name)
18
+ table.first[property_name]
19
+ end
20
+
21
+ def datetime_value_from_property(property_name)
22
+ Time.strptime(value_from_property(property_name), "%F %T")
23
+ end
24
+
25
+ def table
26
+ @table ||= CSV.read(@csv_path, headers: true)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ require "forwardable"
2
+
3
+ module DuolingoPersonalData
4
+ StoryCompletion = Struct.new(:user_id, :story_id, :score, :time, keyword_init: true)
5
+
6
+ class StoryCompletions
7
+ def initialize(csv_path)
8
+ @csv_path = csv_path
9
+ end
10
+
11
+ extend Forwardable
12
+ def_delegators :completions, *(Array.instance_methods - [:object_id])
13
+
14
+ private
15
+
16
+ def completions
17
+ @completions ||= CSV.read(@csv_path, headers: true).map do |row|
18
+ StoryCompletion.new(user_id: row["userId"], story_id: row["storyId"], score: Integer(row["score"]),
19
+ time: Time.strptime(row["time"], "%F %T"))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ module DuolingoPersonalData
2
+ class TeacherPrivacySettings
3
+ def initialize(csv_path)
4
+ @csv_path = csv_path
5
+ end
6
+
7
+ def disable_clubs
8
+ @disable_clubs ||= boolean_value_from_property("disable_clubs")
9
+ end
10
+
11
+ def disable_discussions
12
+ @disable_discussions ||= boolean_value_from_property("disable_discussions")
13
+ end
14
+
15
+ def disable_events
16
+ @disable_events ||= boolean_value_from_property("disable_events")
17
+ end
18
+
19
+ def disable_stream
20
+ @disable_stream ||= boolean_value_from_property("disable_stream")
21
+ end
22
+
23
+ def disable_immersion
24
+ @disable_immersion ||= boolean_value_from_property("disable_immersion")
25
+ end
26
+
27
+ def disable_mature_words
28
+ @disable_mature_words ||= boolean_value_from_property("disable_mature_words")
29
+ end
30
+
31
+ private
32
+
33
+ def boolean_value_from_property(property_name)
34
+ value = value_from_property(property_name)
35
+ case value
36
+ when "False" then false
37
+ when "True" then true
38
+ else
39
+ raise Error, "cannot interpret #{value.inspect} as boolean"
40
+ end
41
+ end
42
+
43
+ def value_from_property(property_name)
44
+ table.first[property_name]
45
+ end
46
+
47
+ def table
48
+ @table ||= CSV.read(@csv_path, headers: true)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module DuolingoPersonalData
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "duolingo_personal_data/version"
2
+ require_relative "duolingo_personal_data/auth_data"
3
+ require_relative "duolingo_personal_data/avatar_images"
4
+ require_relative "duolingo_personal_data/blast_emails"
5
+ require_relative "duolingo_personal_data/notify_data"
6
+ require_relative "duolingo_personal_data/friends_follow"
7
+ require_relative "duolingo_personal_data/inventory"
8
+ require_relative "duolingo_personal_data/languages"
9
+ require_relative "duolingo_personal_data/leaderboards"
10
+ require_relative "duolingo_personal_data/profile"
11
+ require_relative "duolingo_personal_data/stories"
12
+ require_relative "duolingo_personal_data/story_completions"
13
+ require_relative "duolingo_personal_data/teacher_privacy_settings"
14
+
15
+ module DuolingoPersonalData
16
+ class Error < StandardError; end
17
+ end
data/manifest.scm ADDED
@@ -0,0 +1,3 @@
1
+ (use-modules (gnu packages ruby))
2
+
3
+ (packages->manifest (list ruby-rubocop ruby))
@@ -0,0 +1,272 @@
1
+ # TypeProf 0.21.3
2
+
3
+ # Classes
4
+ module DuolingoPersonalData
5
+ VERSION: String
6
+
7
+ class AuthData
8
+ @csv_path: untyped
9
+ @user_account_name: String?
10
+ @table: Array[Array[String?]]
11
+ @email_address: String?
12
+ @last_update_timestamp: String?
13
+ @last_login_attempt_timestamp: String?
14
+ @last_authentication_key_refresh_timestamp: String?
15
+
16
+ def initialize: (untyped csv_path) -> void
17
+ def user_account_name: -> String?
18
+ def email_address: -> String?
19
+ def last_update_timestamp: -> String?
20
+ def last_login_attempt_timestamp: -> String?
21
+ def last_authentication_key_refresh_timestamp: -> String?
22
+
23
+ private
24
+ def value_from_property: (String property_name) -> String?
25
+ def table: -> Array[Array[String?]]
26
+ end
27
+
28
+ class AvatarImages
29
+ extend Forwardable
30
+ @csv_path: untyped
31
+ @urls: Array[URI::Generic]
32
+ @table: Array[Array[String?]]
33
+
34
+ def initialize: (untyped csv_path) -> void
35
+
36
+ private
37
+ def urls: -> Array[URI::Generic]
38
+ def table: -> Array[Array[String?]]
39
+ end
40
+
41
+ class BlastEmails
42
+ @csv_path: untyped
43
+ @email_address: String?
44
+ @table: Array[Array[String?]]
45
+ @ui_language: String?
46
+ @learning_language: String?
47
+ @enabled: bool
48
+ @announcement: bool
49
+ @creation_datetime: untyped
50
+ @last_session: untyped
51
+ @trial_user: bool
52
+ @country: String?
53
+ @client: String?
54
+ @schools_role: Integer
55
+
56
+ def initialize: (untyped csv_path) -> void
57
+ def email_address: -> String?
58
+ def ui_language: -> String?
59
+ def learning_language: -> String?
60
+ def enabled: -> bool
61
+ def announcement: -> bool
62
+ def creation_datetime: -> untyped
63
+ def last_session: -> untyped
64
+ def trial_user: -> bool
65
+ def country: -> String?
66
+ def client: -> String?
67
+ def schools_role: -> Integer
68
+
69
+ private
70
+ def value_from_property: (String property_name) -> String?
71
+ def boolean_value_from_property: (String property_name) -> bool
72
+ def time_value_from_property: (String property_name) -> untyped
73
+ def integer_value_from_property: (String property_name) -> Integer
74
+ def table: -> Array[Array[String?]]
75
+ end
76
+
77
+ class FriendsFollow
78
+ @csv_path: untyped
79
+ @num_following: Integer
80
+ @table: Array[Array[String?]]
81
+ @num_followers: Integer
82
+ @num_blocking: Integer
83
+ @num_blockers: Integer
84
+ @timestamp_generated: Time
85
+
86
+ def initialize: (untyped csv_path) -> void
87
+ def num_following: -> Integer
88
+ def num_followers: -> Integer
89
+ def num_blocking: -> Integer
90
+ def num_blockers: -> Integer
91
+ def timestamp_generated: -> Time
92
+
93
+ private
94
+ def value_from_property: (String property_name) -> String?
95
+ def integer_value_from_property: (String property_name) -> Integer
96
+ def timestamp_value_from_property: (String property_name) -> Time
97
+ def table: -> Array[Array[String?]]
98
+ end
99
+
100
+ class InventoryItem < Struct[untyped]
101
+ attr_accessor item_type(): String?
102
+ attr_accessor purchase_datetime(): untyped
103
+ attr_accessor active(): bool
104
+ attr_accessor price_in_virtual_currency(): Integer
105
+ attr_accessor wager_day(): Integer
106
+ attr_accessor payment_processor(): String?
107
+ attr_accessor product(): String?
108
+ attr_accessor expected_expiration(): untyped
109
+ end
110
+
111
+ class Inventory
112
+ extend Forwardable
113
+ @csv_path: untyped
114
+ @items: Array[InventoryItem]
115
+ @table: Array[Array[String?]]
116
+
117
+ def initialize: (untyped csv_path) -> void
118
+
119
+ private
120
+ def items: -> Array[InventoryItem]
121
+ def parse_datetime: (String? str) -> untyped
122
+ def parse_boolean: (String? str) -> bool
123
+ def table: -> Array[Array[String?]]
124
+ end
125
+
126
+ class Language < Struct[untyped]
127
+ attr_accessor from_language(): String?
128
+ attr_accessor points(): Integer
129
+ attr_accessor skill_learned(): Integer
130
+ attr_accessor total_lessons(): Integer
131
+ attr_accessor days_active(): Integer
132
+ attr_accessor last_active(): untyped
133
+ attr_accessor prior_proficiency(): Integer
134
+ attr_accessor subscribed(): untyped
135
+ end
136
+
137
+ class Languages
138
+ extend Forwardable
139
+ @csv_path: untyped
140
+ @languages: Hash[String?, Language]
141
+
142
+ def initialize: (untyped csv_path) -> void
143
+
144
+ private
145
+ def languages: -> Hash[String?, Language]
146
+ def parse_datetime: (String str) -> untyped
147
+ end
148
+
149
+ class LeaderboardsEntry < Struct[untyped]
150
+ attr_accessor timestamp(): untyped
151
+ attr_accessor tier(): Integer
152
+ attr_accessor score(): Float
153
+ end
154
+
155
+ class Leaderboards
156
+ extend Forwardable
157
+ @csv_path: untyped
158
+ @entries: Array[LeaderboardsEntry]
159
+
160
+ def initialize: (untyped csv_path) -> void
161
+
162
+ private
163
+ def entries: -> Array[LeaderboardsEntry]
164
+ end
165
+
166
+ class NotifyData
167
+ @csv_path: untyped
168
+ @email: String?
169
+ @table: Array[Array[String?]]
170
+ @device_ids: untyped
171
+
172
+ def initialize: (untyped csv_path) -> void
173
+ def email: -> String?
174
+ def device_ids: -> untyped
175
+
176
+ private
177
+ def value_from_property: (String property_name) -> String?
178
+ def json_value_from_property: (String property_name) -> untyped
179
+ def table: -> Array[Array[String?]]
180
+ end
181
+
182
+ class Profile
183
+ @csv_path: untyped
184
+ @username: String?
185
+ @table: Array[Array[String?]]
186
+ @email: String?
187
+ @fullname: String?
188
+ @joined_at: untyped
189
+ @ui_language: String?
190
+ @learning_language: String?
191
+ @lingots: Integer
192
+ @daily_goal: Integer
193
+ @timezone: String?
194
+ @avatar_url: String?
195
+
196
+ def initialize: (untyped csv_path) -> void
197
+ def username: -> String?
198
+ def email: -> String?
199
+ def fullname: -> String?
200
+ def joined_at: -> untyped
201
+ def ui_language: -> String?
202
+ def learning_language: -> String?
203
+ def lingots: -> Integer
204
+ def daily_goal: -> Integer
205
+ def timezone: -> String?
206
+ def avatar_url: -> String?
207
+
208
+ private
209
+ def value_from_property: (String property_name) -> String?
210
+ def integer_value_from_property: (String property_name) -> Integer
211
+ def datetime_value_from_property: (String property_name) -> untyped
212
+ def table: -> Array[Array[String?]]
213
+ end
214
+
215
+ class Stories
216
+ @csv_path: untyped
217
+ @user_id: String?
218
+ @table: Array[Array[String?]]
219
+ @date_of_first_visit_to_stories: untyped
220
+
221
+ def initialize: (untyped csv_path) -> void
222
+ def user_id: -> String?
223
+ def date_of_first_visit_to_stories: -> untyped
224
+
225
+ private
226
+ def value_from_property: (String property_name) -> String?
227
+ def datetime_value_from_property: (String property_name) -> untyped
228
+ def table: -> Array[Array[String?]]
229
+ end
230
+
231
+ class StoryCompletion < Struct[untyped]
232
+ attr_accessor user_id(): String?
233
+ attr_accessor story_id(): String?
234
+ attr_accessor score(): Integer
235
+ attr_accessor time(): untyped
236
+ end
237
+
238
+ class StoryCompletions
239
+ extend Forwardable
240
+ @csv_path: untyped
241
+ @completions: Array[StoryCompletion]
242
+
243
+ def initialize: (untyped csv_path) -> void
244
+
245
+ private
246
+ def completions: -> Array[StoryCompletion]
247
+ end
248
+
249
+ class TeacherPrivacySettings
250
+ @csv_path: untyped
251
+ @disable_clubs: bool
252
+ @table: Array[Array[String?]]
253
+ @disable_discussions: bool
254
+ @disable_events: bool
255
+ @disable_stream: bool
256
+ @disable_immersion: bool
257
+ @disable_mature_words: bool
258
+
259
+ def initialize: (untyped csv_path) -> void
260
+ def disable_clubs: -> bool
261
+ def disable_discussions: -> bool
262
+ def disable_events: -> bool
263
+ def disable_stream: -> bool
264
+ def disable_immersion: -> bool
265
+ def disable_mature_words: -> bool
266
+
267
+ private
268
+ def boolean_value_from_property: (String property_name) -> bool
269
+ def value_from_property: (String property_name) -> String?
270
+ def table: -> Array[Array[String?]]
271
+ end
272
+ end