@@ -29,5 +29,194 @@ struct Invidious::User
2929
3030 return subscriptions
3131 end
32- end
32+
33+ # -------------------
34+ # Invidious
35+ # -------------------
36+
37+ # Import from another invidious account
38+ def from_invidious (user : User , body : String )
39+ data = JSON .parse(body)
40+
41+ if data[" subscriptions" ]?
42+ user.subscriptions += data[" subscriptions" ].as_a.map(& .as_s)
43+ user.subscriptions.uniq!
44+ user.subscriptions = get_batch_channels(user.subscriptions)
45+
46+ Invidious ::Database ::Users .update_subscriptions(user)
47+ end
48+
49+ if data[" watch_history" ]?
50+ user.watched += data[" watch_history" ].as_a.map(& .as_s)
51+ user.watched.uniq!
52+ Invidious ::Database ::Users .update_watch_history(user)
53+ end
54+
55+ if data[" preferences" ]?
56+ user.preferences = Preferences .from_json(data[" preferences" ].to_json)
57+ Invidious ::Database ::Users .update_preferences(user)
58+ end
59+
60+ if playlists = data[" playlists" ]?.try & .as_a?
61+ playlists.each do |item |
62+ title = item[" title" ]?.try & .as_s?.try & .delete(" <>" )
63+ description = item[" description" ]?.try & .as_s?.try & .delete(" \r " )
64+ privacy = item[" privacy" ]?.try & .as_s?.try { |privacy | PlaylistPrivacy .parse? privacy }
65+
66+ next if ! title
67+ next if ! description
68+ next if ! privacy
69+
70+ playlist = create_playlist(title, privacy, user)
71+ Invidious ::Database ::Playlists .update_description(playlist.id, description)
72+
73+ videos = item[" videos" ]?.try & .as_a?.try & .each_with_index do |video_id , idx |
74+ raise InfoException .new(" Playlist cannot have more than 500 videos" ) if idx > 500
75+
76+ video_id = video_id.try & .as_s?
77+ next if ! video_id
78+
79+ begin
80+ video = get_video(video_id)
81+ rescue ex
82+ next
83+ end
84+
85+ playlist_video = PlaylistVideo .new({
86+ title: video.title,
87+ id: video.id,
88+ author: video.author,
89+ ucid: video.ucid,
90+ length_seconds: video.length_seconds,
91+ published: video.published,
92+ plid: playlist.id,
93+ live_now: video.live_now,
94+ index: Random ::Secure .rand(0 _i64 ..Int64 ::MAX ),
95+ })
96+
97+ Invidious ::Database ::PlaylistVideos .insert(playlist_video)
98+ Invidious ::Database ::Playlists .update_video_added(playlist.id, playlist_video.index)
99+ end
100+ end
101+ end
102+ end
103+
104+ # -------------------
105+ # Youtube
106+ # -------------------
107+
108+ # Import subscribed channels from Youtube
109+ # Returns success status
110+ def from_youtube (user : User , body : String , filename : String , type : String ) : Bool
111+ extension = filename.split(" ." ).last
112+
113+ if extension == " xml" || type == " application/xml" || type == " text/xml"
114+ subscriptions = XML .parse(body)
115+ user.subscriptions += subscriptions.xpath_nodes(%q( //outline[@type="rss"]) ).map do |channel |
116+ channel[" xmlUrl" ].match(/UC[a-zA-Z0-9_-] {22} / ).not_nil![0 ]
117+ end
118+ elsif extension == " json" || type == " application/json"
119+ subscriptions = JSON .parse(body)
120+ user.subscriptions += subscriptions.as_a.compact_map do |entry |
121+ entry[" snippet" ][" resourceId" ][" channelId" ].as_s
122+ end
123+ elsif extension == " csv" || type == " text/csv"
124+ subscriptions = parse_subscription_export_csv(body)
125+ user.subscriptions += subscriptions
126+ else
127+ return false
128+ end
129+
130+ user.subscriptions.uniq!
131+ user.subscriptions = get_batch_channels(user.subscriptions)
132+
133+ Invidious ::Database ::Users .update_subscriptions(user)
134+ return true
135+ end
136+
137+ # -------------------
138+ # Freetube
139+ # -------------------
140+
141+ def from_freetube (user : User , body : String )
142+ matches = body.scan(/"channelId":"(?<channel_id>[a-zA-Z0-9_-] {24} ) "/ )
143+
144+ user.subscriptions += matches.map(& .[" channel_id" ])
145+ user.subscriptions.uniq!
146+ user.subscriptions = get_batch_channels(user.subscriptions)
147+
148+ Invidious ::Database ::Users .update_subscriptions(user)
149+ end
150+
151+ # -------------------
152+ # Newpipe
153+ # -------------------
154+
155+ def from_newpipe_subs (user : User , body : String )
156+ data = JSON .parse(body)
157+
158+ user.subscriptions += data[" subscriptions" ].as_a.compact_map do |channel |
159+ if match = channel[" url" ].as_s.match(/\/ channel\/ (?<channel>UC[a-zA-Z0-9_-] {22} ) / )
160+ next match[" channel" ]
161+ elsif match = channel[" url" ].as_s.match(/\/ user\/ (?<user>.+) / )
162+ # Resolve URL using the API
163+ resolved_url = YoutubeAPI .resolve_url(" https://www.youtube.com/user/#{ match[" user" ] } " )
164+ ucid = resolved_url.dig?(" endpoint" , " browseEndpoint" , " browseId" )
165+ next ucid.as_s if ucid
166+ end
167+
168+ nil
169+ end
170+
171+ user.subscriptions.uniq!
172+ user.subscriptions = get_batch_channels(user.subscriptions)
173+
174+ Invidious ::Database ::Users .update_subscriptions(user)
175+ end
176+
177+ def from_newpipe (user : User , body : String ) : Bool
178+ io = IO ::Memory .new(body)
179+
180+ Compress ::Zip ::File .open(io) do |file |
181+ file.entries.each do |entry |
182+ entry.open do |file_io |
183+ # Ensure max size of 4MB
184+ io_sized = IO ::Sized .new(file_io, 0x400000 )
185+
186+ next if entry.filename != " newpipe.db"
187+
188+ tempfile = File .tempfile(" .db" )
189+
190+ begin
191+ File .write(tempfile.path, io_sized.gets_to_end)
192+ rescue
193+ return false
194+ end
195+
196+ db = DB .open(" sqlite3://" + tempfile.path)
197+
198+ user.watched += db.query_all(" SELECT url FROM streams" , as: String )
199+ .map(& .lchop(" https://www.youtube.com/watch?v=" ))
200+
201+ user.watched.uniq!
202+ Invidious ::Database ::Users .update_watch_history(user)
203+
204+ user.subscriptions += db.query_all(" SELECT url FROM subscriptions" , as: String )
205+ .map(& .lchop(" https://www.youtube.com/channel/" ))
206+
207+ user.subscriptions.uniq!
208+ user.subscriptions = get_batch_channels(user.subscriptions)
209+
210+ Invidious ::Database ::Users .update_subscriptions(user)
211+
212+ db.close
213+ tempfile.delete
214+ end
215+ end
216+ end
217+
218+ # Success!
219+ return true
220+ end
221+ end # module
33222end
0 commit comments