wikidata.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import axios from "axios";
  2. import CoreClass from "../core";
  3. class RateLimitter {
  4. /**
  5. * Constructor
  6. * @param {number} timeBetween - The time between each allowed WikiData request
  7. */
  8. constructor(timeBetween) {
  9. this.dateStarted = Date.now();
  10. this.timeBetween = timeBetween;
  11. }
  12. /**
  13. * Returns a promise that resolves whenever the ratelimit of a WikiData request is done
  14. * @returns {Promise} - promise that gets resolved when the rate limit allows it
  15. */
  16. continue() {
  17. return new Promise(resolve => {
  18. if (Date.now() - this.dateStarted >= this.timeBetween) resolve();
  19. else setTimeout(resolve, this.dateStarted + this.timeBetween - Date.now());
  20. });
  21. }
  22. /**
  23. * Restart the rate limit timer
  24. */
  25. restart() {
  26. this.dateStarted = Date.now();
  27. }
  28. }
  29. let WikiDataModule;
  30. let DBModule;
  31. class _WikiDataModule extends CoreClass {
  32. // eslint-disable-next-line require-jsdoc
  33. constructor() {
  34. super("wikidata", {
  35. concurrency: 10
  36. });
  37. WikiDataModule = this;
  38. }
  39. /**
  40. * Initialises the activities module
  41. * @returns {Promise} - returns promise (reject, resolve)
  42. */
  43. async initialize() {
  44. DBModule = this.moduleManager.modules.db;
  45. this.genericApiRequestModel = this.GenericApiRequestModel = await DBModule.runJob("GET_MODEL", {
  46. modelName: "genericApiRequest"
  47. });
  48. this.rateLimiter = new RateLimitter(1100);
  49. this.requestTimeout = 5000;
  50. this.axios = axios.create();
  51. }
  52. /**
  53. * Get WikiData data from entity url
  54. * @param {object} payload - object that contains the payload
  55. * @param {string} payload.entityUrl - entity url
  56. * @returns {Promise} - returns promise (reject, resolve)
  57. */
  58. async API_GET_DATA_FROM_ENTITY_URL(payload) {
  59. const { entityUrl } = payload;
  60. const sparqlQuery = `PREFIX entity_url: <${entityUrl}>
  61. SELECT DISTINCT ?YouTube_video_ID ?SoundCloud_track_ID WHERE {
  62. OPTIONAL { entity_url: wdt:P1651 ?YouTube_video_ID. }
  63. OPTIONAL { entity_url: wdt:P3040 ?SoundCloud_track_ID. }
  64. }`
  65. .replaceAll("\n", "")
  66. .replaceAll("\t", "");
  67. return WikiDataModule.runJob(
  68. "API_CALL",
  69. {
  70. url: "https://query.wikidata.org/sparql",
  71. params: {
  72. query: sparqlQuery
  73. }
  74. },
  75. this
  76. );
  77. }
  78. /**
  79. * Get WikiData data from work id
  80. * @param {object} payload - object that contains the payload
  81. * @param {string} payload.workId - work id
  82. * @returns {Promise} - returns promise (reject, resolve)
  83. */
  84. async API_GET_DATA_FROM_MUSICBRAINZ_WORK(payload) {
  85. const { workId } = payload;
  86. const sparqlQuery =
  87. `SELECT DISTINCT ?item ?itemLabel ?YouTube_video_ID ?SoundCloud_ID ?Music_video_entity_URL WHERE {
  88. SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
  89. {
  90. SELECT DISTINCT ?item WHERE {
  91. ?item p:P435 ?statement0.
  92. ?statement0 ps:P435 "${workId}".
  93. }
  94. LIMIT 100
  95. }
  96. OPTIONAL { ?item wdt:P1651 ?YouTube_video_ID. }
  97. OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
  98. OPTIONAL { ?item wdt:P6718 ?Music_video_entity_URL. }
  99. }`
  100. .replaceAll("\n", "")
  101. .replaceAll("\t", "");
  102. return WikiDataModule.runJob(
  103. "API_CALL",
  104. {
  105. url: "https://query.wikidata.org/sparql",
  106. params: {
  107. query: sparqlQuery
  108. }
  109. },
  110. this
  111. );
  112. }
  113. /**
  114. * Get WikiData data from release group id
  115. * @param {object} payload - object that contains the payload
  116. * @param {string} payload.releaseGroupId - release group id
  117. * @returns {Promise} - returns promise (reject, resolve)
  118. */
  119. async API_GET_DATA_FROM_MUSICBRAINZ_RELEASE_GROUP(payload) {
  120. const { releaseGroupId } = payload;
  121. const sparqlQuery =
  122. `SELECT DISTINCT ?item ?itemLabel ?YouTube_video_ID ?SoundCloud_ID ?Music_video_entity_URL WHERE {
  123. SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
  124. {
  125. SELECT DISTINCT ?item WHERE {
  126. ?item p:P436 ?statement0.
  127. ?statement0 ps:P436 "${releaseGroupId}".
  128. }
  129. LIMIT 100
  130. }
  131. OPTIONAL { ?item wdt:P1651 ?YouTube_video_ID. }
  132. OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
  133. OPTIONAL { ?item wdt:P6718 ?Music_video_entity_URL. }
  134. }`
  135. .replaceAll("\n", "")
  136. .replaceAll("\t", "");
  137. return WikiDataModule.runJob(
  138. "API_CALL",
  139. {
  140. url: "https://query.wikidata.org/sparql",
  141. params: {
  142. query: sparqlQuery
  143. }
  144. },
  145. this
  146. );
  147. }
  148. /**
  149. * Get WikiData data from Spotify album id
  150. * @param {object} payload - object that contains the payload
  151. * @param {string} payload.spotifyAlbumId - Spotify album id
  152. * @returns {Promise} - returns promise (reject, resolve)
  153. */
  154. async API_GET_DATA_FROM_SPOTIFY_ALBUM(payload) {
  155. const { spotifyAlbumId } = payload;
  156. if (!spotifyAlbumId) throw new Error("Invalid Spotify album ID provided.");
  157. const sparqlQuery = `SELECT DISTINCT ?item ?itemLabel ?YouTube_playlist_ID ?SoundCloud_ID WHERE {
  158. SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
  159. {
  160. SELECT DISTINCT ?item WHERE {
  161. ?item p:P2205 ?statement0.
  162. ?statement0 ps:P2205 "${spotifyAlbumId}".
  163. }
  164. LIMIT 100
  165. }
  166. OPTIONAL { ?item wdt:P4300 ?YouTube_playlist_ID. }
  167. OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
  168. }`
  169. .replaceAll("\n", "")
  170. .replaceAll("\t", "");
  171. return WikiDataModule.runJob(
  172. "API_CALL",
  173. {
  174. url: "https://query.wikidata.org/sparql",
  175. params: {
  176. query: sparqlQuery
  177. }
  178. },
  179. this
  180. );
  181. }
  182. /**
  183. * Get WikiData data from Spotify artist id
  184. * @param {object} payload - object that contains the payload
  185. * @param {string} payload.spotifyArtistId - Spotify artist id
  186. * @returns {Promise} - returns promise (reject, resolve)
  187. */
  188. async API_GET_DATA_FROM_SPOTIFY_ARTIST(payload) {
  189. const { spotifyArtistId } = payload;
  190. if (!spotifyArtistId) throw new Error("Invalid Spotify artist ID provided.");
  191. const sparqlQuery =
  192. `SELECT DISTINCT ?item ?itemLabel ?YouTube_channel_ID ?SoundCloud_ID ?MusicBrainz_artist_ID WHERE {
  193. SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
  194. {
  195. SELECT DISTINCT ?item WHERE {
  196. ?item p:P1902 ?statement0.
  197. ?statement0 ps:P1902 "${spotifyArtistId}".
  198. }
  199. LIMIT 100
  200. }
  201. OPTIONAL { ?item wdt:P2397 ?YouTube_channel_ID. }
  202. OPTIONAL { ?item wdt:P3040 ?SoundCloud_ID. }
  203. OPTIONAL { ?item wdt:P434 ?MusicBrainz_artist_ID. }
  204. }`
  205. .replaceAll("\n", "")
  206. .replaceAll("\t", "");
  207. return WikiDataModule.runJob(
  208. "API_CALL",
  209. {
  210. url: "https://query.wikidata.org/sparql",
  211. params: {
  212. query: sparqlQuery
  213. }
  214. },
  215. this
  216. );
  217. }
  218. /**
  219. * Perform WikiData API call
  220. * @param {object} payload - object that contains the payload
  221. * @param {string} payload.url - request url
  222. * @param {object} payload.params - request parameters
  223. * @returns {Promise} - returns promise (reject, resolve)
  224. */
  225. async API_CALL(payload) {
  226. const { url, params } = payload;
  227. let genericApiRequest = await WikiDataModule.GenericApiRequestModel.findOne({
  228. url,
  229. params
  230. });
  231. if (genericApiRequest) return genericApiRequest._doc.responseData;
  232. await WikiDataModule.rateLimiter.continue();
  233. WikiDataModule.rateLimiter.restart();
  234. const { data: responseData } = await WikiDataModule.axios.get(url, {
  235. params,
  236. headers: {
  237. Accept: "application/sparql-results+json"
  238. },
  239. timeout: WikiDataModule.requestTimeout
  240. });
  241. if (responseData.error) throw new Error(responseData.error);
  242. genericApiRequest = new WikiDataModule.GenericApiRequestModel({
  243. url,
  244. params,
  245. responseData,
  246. date: Date.now()
  247. });
  248. genericApiRequest.save();
  249. return responseData;
  250. }
  251. }
  252. export default new _WikiDataModule();