duolingo_personal_data 0.1.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.
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