soundcloud.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import mongoose from "mongoose";
  2. import async from "async";
  3. import config from "config";
  4. import * as rax from "retry-axios";
  5. import axios from "axios";
  6. import CoreClass from "../core";
  7. let SoundCloudModule;
  8. let CacheModule;
  9. let DBModule;
  10. let MediaModule;
  11. let SongsModule;
  12. let StationsModule;
  13. let PlaylistsModule;
  14. let WSModule;
  15. class RateLimitter {
  16. /**
  17. * Constructor
  18. *
  19. * @param {number} timeBetween - The time between each allowed YouTube request
  20. */
  21. constructor(timeBetween) {
  22. this.dateStarted = Date.now();
  23. this.timeBetween = timeBetween;
  24. }
  25. /**
  26. * Returns a promise that resolves whenever the ratelimit of a YouTube request is done
  27. *
  28. * @returns {Promise} - promise that gets resolved when the rate limit allows it
  29. */
  30. continue() {
  31. return new Promise(resolve => {
  32. if (Date.now() - this.dateStarted >= this.timeBetween) resolve();
  33. else setTimeout(resolve, this.dateStarted + this.timeBetween - Date.now());
  34. });
  35. }
  36. /**
  37. * Restart the rate limit timer
  38. */
  39. restart() {
  40. this.dateStarted = Date.now();
  41. }
  42. }
  43. class _SoundCloudModule extends CoreClass {
  44. // eslint-disable-next-line require-jsdoc
  45. constructor() {
  46. super("soundcloud");
  47. SoundCloudModule = this;
  48. }
  49. /**
  50. * Initialises the soundcloud module
  51. *
  52. * @returns {Promise} - returns promise (reject, resolve)
  53. */
  54. async initialize() {
  55. CacheModule = this.moduleManager.modules.cache;
  56. DBModule = this.moduleManager.modules.db;
  57. MediaModule = this.moduleManager.modules.media;
  58. SongsModule = this.moduleManager.modules.songs;
  59. StationsModule = this.moduleManager.modules.stations;
  60. PlaylistsModule = this.moduleManager.modules.playlists;
  61. WSModule = this.moduleManager.modules.ws;
  62. // this.youtubeApiRequestModel = this.YoutubeApiRequestModel = await DBModule.runJob("GET_MODEL", {
  63. // modelName: "youtubeApiRequest"
  64. // });
  65. this.soundcloudTrackModel = this.SoundCloudTrackModel = await DBModule.runJob("GET_MODEL", {
  66. modelName: "soundcloudTrack"
  67. });
  68. return new Promise(resolve => {
  69. this.rateLimiter = new RateLimitter(config.get("apis.soundcloud.rateLimit"));
  70. this.requestTimeout = config.get("apis.soundcloud.requestTimeout");
  71. this.axios = axios.create();
  72. this.axios.defaults.raxConfig = {
  73. instance: this.axios,
  74. retry: config.get("apis.soundcloud.retryAmount"),
  75. noResponseRetries: config.get("apis.soundcloud.retryAmount")
  76. };
  77. rax.attach(this.axios);
  78. SoundCloudModule.runJob("GET_TRACK", { identifier: 469902882, createMissing: false })
  79. .then(res => {
  80. console.log(57567, res);
  81. })
  82. .catch(err => {
  83. console.log(78768, err);
  84. });
  85. resolve();
  86. });
  87. }
  88. /**
  89. * Perform SoundCloud API get track request
  90. *
  91. * @param {object} payload - object that contains the payload
  92. * @param {object} payload.params - request parameters
  93. * @returns {Promise} - returns promise (reject, resolve)
  94. */
  95. API_GET_TRACK(payload) {
  96. return new Promise((resolve, reject) => {
  97. const { trackId } = payload;
  98. SoundCloudModule.runJob(
  99. "API_CALL",
  100. {
  101. url: `https://api-v2.soundcloud.com/tracks/${trackId}`
  102. },
  103. this
  104. )
  105. .then(response => {
  106. resolve(response);
  107. })
  108. .catch(err => {
  109. reject(err);
  110. });
  111. });
  112. }
  113. /**
  114. * Perform SoundCloud API call
  115. *
  116. * @param {object} payload - object that contains the payload
  117. * @param {object} payload.url - request url
  118. * @param {object} payload.params - request parameters
  119. * @param {object} payload.quotaCost - request quotaCost
  120. * @returns {Promise} - returns promise (reject, resolve)
  121. */
  122. API_CALL(payload) {
  123. return new Promise((resolve, reject) => {
  124. // const { url, params, quotaCost } = payload;
  125. const { url } = payload;
  126. const params = {
  127. client_id: config.get("apis.soundcloud.key")
  128. };
  129. SoundCloudModule.axios
  130. .get(url, {
  131. params,
  132. timeout: SoundCloudModule.requestTimeout
  133. })
  134. .then(response => {
  135. if (response.data.error) {
  136. reject(new Error(response.data.error));
  137. } else {
  138. resolve({ response });
  139. }
  140. })
  141. .catch(err => {
  142. reject(err);
  143. });
  144. // }
  145. });
  146. }
  147. /**
  148. * Create SoundCloud track
  149. *
  150. * @param {object} payload - an object containing the payload
  151. * @param {string} payload.soundcloudTrack - the soundcloudTrack object
  152. * @returns {Promise} - returns a promise (resolve, reject)
  153. */
  154. CREATE_TRACK(payload) {
  155. return new Promise((resolve, reject) => {
  156. async.waterfall(
  157. [
  158. next => {
  159. const { soundcloudTrack } = payload;
  160. if (typeof soundcloudTrack !== "object") next("Invalid soundcloudTrack type");
  161. else {
  162. SoundCloudModule.soundcloudTrackModel.insertMany(soundcloudTrack, next);
  163. }
  164. },
  165. (soundcloudTrack, next) => {
  166. const mediaSource = `soundcloud:${soundcloudTrack.trackId}`;
  167. MediaModule.runJob("RECALCULATE_RATINGS", { mediaSource }, this)
  168. .then(() => next(null, soundcloudTrack))
  169. .catch(next);
  170. }
  171. ],
  172. (err, soundcloudTrack) => {
  173. if (err) reject(new Error(err));
  174. else resolve({ soundcloudTrack });
  175. }
  176. );
  177. });
  178. }
  179. /**
  180. * Get SoundCloud track
  181. *
  182. * @param {object} payload - an object containing the payload
  183. * @param {string} payload.identifier - the soundcloud track ObjectId or track id
  184. * @param {string} payload.createMissing - attempt to fetch and create track if not in db
  185. * @returns {Promise} - returns a promise (resolve, reject)
  186. */
  187. GET_TRACK(payload) {
  188. return new Promise((resolve, reject) => {
  189. async.waterfall(
  190. [
  191. next => {
  192. const query = mongoose.isObjectIdOrHexString(payload.identifier)
  193. ? { _id: payload.identifier }
  194. : { trackId: payload.identifier };
  195. return SoundCloudModule.soundcloudTrackModel.findOne(query, next);
  196. },
  197. (track, next) => {
  198. if (track) return next(null, track, false);
  199. if (mongoose.isObjectIdOrHexString(payload.identifier) || !payload.createMissing)
  200. return next("SoundCloud track not found.");
  201. return SoundCloudModule.runJob("API_GET_TRACK", { trackId: payload.identifier }, this)
  202. .then(({ response }) => {
  203. const { data } = response;
  204. if (!data || !data.id)
  205. return next("The specified track does not exist or cannot be publicly accessed.");
  206. const {
  207. id,
  208. title,
  209. artwork_url: artworkUrl,
  210. created_at: createdAt,
  211. duration,
  212. genre,
  213. kind,
  214. license,
  215. likes_count: likesCount,
  216. playback_count: playbackCount,
  217. public: _public,
  218. tag_list: tagList,
  219. user_id: userId,
  220. user
  221. } = data;
  222. const soundcloudTrack = {
  223. trackId: id,
  224. title,
  225. artworkUrl,
  226. soundcloudCreatedAt: new Date(createdAt),
  227. duration: duration / 1000,
  228. genre,
  229. kind,
  230. license,
  231. likesCount,
  232. playbackCount,
  233. public: _public,
  234. tagList,
  235. userId,
  236. username: user.username
  237. };
  238. return next(null, false, soundcloudTrack);
  239. })
  240. .catch(next);
  241. },
  242. (track, soundcloudTrack, next) => {
  243. if (track) return next(null, track, true);
  244. return SoundCloudModule.runJob("CREATE_TRACK", { soundcloudTrack }, this)
  245. .then(res => {
  246. if (res.soundcloudTrack.length === 1) next(null, res.soundcloudTrack, false);
  247. else next("SoundCloud track not found.");
  248. })
  249. .catch(next);
  250. }
  251. ],
  252. (err, track, existing) => {
  253. if (err) reject(new Error(err));
  254. else resolve({ track, existing });
  255. }
  256. );
  257. });
  258. }
  259. }
  260. export default new _SoundCloudModule();