wikidata.js 7.5 KB

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