soup.io: EmbedFix

Repairs embeds in your own and your friend soup. Fixes embeds everywhere if there is a link in the description

  1. // ==UserScript==
  2. // @name soup.io: EmbedFix
  3. // @namespace http://xcvbnm.org/
  4. // @author Nordern
  5. // @description Repairs embeds in your own and your friend soup. Fixes embeds everywhere if there is a link in the description
  6. // @version 0.2.1
  7. // @match http://*.soup.io/*
  8. // @match https://*.soup.io/*
  9. // @exclude http://www.soup.io/frames/*
  10. // @exclude http://www.soup.io/remote/*
  11. // @license public domain, MediaEmbed has MIT Licence
  12. // @run-at document-end
  13. // ==/UserScript==
  14. // Available on github under: https://github.com/edave64/souplements/blob/master/youtube-fix/
  15. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  16. /**
  17. * This code snippet allows you to intercept the loading of new elements and modify the loaded posts.
  18. * The basic idea is to allow filtering out posts before they are inserted into the dom, and thus before their assets
  19. * are loaded. This reduces stress on both the client and the asset servers.
  20. *
  21. * To use the filter, register the event "processBatch" in SOUP.Endless.
  22. * Example:
  23. *
  24. * SOUP.Endless.on("processBatch", function (doc) {
  25. * // your code here
  26. * });
  27. *
  28. * The doc argument represents a temporary HTMLDocument node, storing the new loaded posts. You can work with it
  29. * like you can work with ''document''.
  30. *
  31. * Be careful to never remove all posts, otherwise Soup will assume you reached the end.
  32. *
  33. * Please keep in mind that soup already has some filters build in: http://faq.soup.io/post/4328678
  34. * These are probably easier on the servers.
  35. *
  36. * Licence: Public domain
  37. *
  38. * UPDATE 1.1:
  39. * The soup event-api was used! I just used it wrong. It is supposed to be a template. The event is now fired on
  40. * SOUP.Endless instead of SOUP.Events
  41. *
  42. * UPDATE 1.2:
  43. * Also trigger for loaded in reactions
  44. */
  45. (function () {
  46. // Add events to SOUP.Endless
  47. if (!SOUP.Endless.trigger) {
  48. SOUP.tools.extend(SOUP.Endless, SOUP.Events);
  49. }
  50.  
  51. if (Ajax.Request._EndlessFilter) return;
  52.  
  53. var oldRequest = Ajax.Request;
  54.  
  55. function getLoadAboveURL() {
  56. var url = $("endless_top_post").href;
  57. return url.match(/[&?]newer=1/) ? url : url + (url.indexOf("?") >= 0 ? "&" : "?") + "newer=1";
  58. }
  59.  
  60. function getLoadBelowURL() {
  61. return SOUP.Endless.next_url.replace(/&?newer=1&?/g, "");
  62. }
  63.  
  64. function catchBatchLoad (path, options) {
  65. var oldSuccess = options.onSuccess;
  66. options.onSuccess = function (response) {
  67. var text = response.responseText,
  68. pipePosition = text.indexOf("|"),
  69. nextPath = text.substring(0, pipePosition),
  70. content = text.substring(pipePosition + 1),
  71. parser = new DOMParser(),
  72. xmlDoc = parser.parseFromString(content, "text/html"),
  73. root = xmlDoc.body;
  74.  
  75. root.setAttribute("id", "posts");
  76. SOUP.Endless.trigger("processBatch", xmlDoc);
  77.  
  78. response.responseText = nextPath + "|" + root.innerHTML;
  79.  
  80. return oldSuccess.apply(this, arguments);
  81. };
  82.  
  83. return oldRequest.apply(this, arguments);
  84. }
  85.  
  86. function catchPreviewLoad (path, options) {
  87. var oldSuccess = options.onSuccess;
  88. options.onSuccess = function (response) {
  89. var content = response.responseText,
  90. parser = new DOMParser(),
  91. xmlDoc = parser.parseFromString(content, "text/html"),
  92. root = xmlDoc.body;
  93.  
  94. root.setAttribute("id", "posts");
  95. SOUP.Endless.trigger("processBatch", xmlDoc);
  96.  
  97. response.responseText = root.innerHTML;
  98.  
  99. return oldSuccess.apply(this, arguments);
  100. };
  101.  
  102. return oldRequest.apply(this, arguments);
  103. }
  104.  
  105. Ajax.Request = function (path, options) {
  106. var aboveURL = getLoadAboveURL();
  107. var belowURL = getLoadBelowURL();
  108.  
  109. if (path === aboveURL || path === belowURL) {
  110. return catchBatchLoad.apply(this, arguments);
  111. }
  112. if (path.startsWith("http://" + document.location.host + "/preview/")) {
  113. return catchPreviewLoad.apply(this, arguments);
  114. }
  115. return oldRequest.apply(this, arguments);
  116. };
  117. Ajax.Request._EndlessFilter = true;
  118. Ajax.Request.Events = oldRequest.Events;
  119. Ajax.Request.prototype = oldRequest.prototype;
  120. }());
  121.  
  122. },{}],2:[function(require,module,exports){
  123. require("../endlessFilter");
  124. const MediaEmbedder = require("media-embedder");
  125.  
  126. if (!SOUP.EmbedFix) {
  127. SOUP.EmbedFix = true;
  128.  
  129. function fixAll (doc) {
  130. var video_posts = [].slice.call(doc.getElementsByClassName("post_video"));
  131. const firstPost = document.querySelector(".post .content");
  132. let width = 500;
  133.  
  134. if (firstPost) {
  135. width = parseInt(window.getComputedStyle(firstPost).width);
  136. }
  137.  
  138. const height = (width / 16 * 9)|0;
  139. for (const video_post of video_posts) {
  140. const embed = video_post.getElementsByClassName("embed")[0];
  141. if (embed.children.length === 0) {
  142. let mediaData;
  143.  
  144. const textarea = video_post.querySelector("[name='post[embedcode_or_url]']");
  145. if (textarea) {
  146. mediaData = MediaEmbedder.detect(textarea.childNodes[0] ? textarea.childNodes[0].nodeValue : "");
  147. }
  148.  
  149. if (!mediaData) {
  150. const description = video_post.getElementsByClassName("body")[0];
  151. if (description) {
  152. mediaData = MediaEmbedder.detect(description.innerHTML);
  153. }
  154. }
  155.  
  156. if (mediaData) {
  157. mediaData.width = width;
  158. mediaData.height = height;
  159. embed.innerHTML = MediaEmbedder.buildIframe(mediaData);
  160. }
  161. }
  162. }
  163. }
  164.  
  165. SOUP.Endless.on("processBatch", function (doc) {
  166. fixAll(doc);
  167. });
  168.  
  169. fixAll(document);
  170. }
  171.  
  172. },{"../endlessFilter":1,"media-embedder":4}],3:[function(require,module,exports){
  173. module.exports = {
  174. parse: function (text) {
  175. const a = document.createElement("a");
  176. a.href = text;
  177. a.query = a.search.substring(1);
  178. return a;
  179. }
  180. };
  181.  
  182. },{}],4:[function(require,module,exports){
  183. (function (global){
  184. "use strict";
  185.  
  186. /**
  187. * @typedef MediaInfo
  188. * @name MediaInfo
  189. * @type {object}
  190. * @property {string} platform - The name of the media platfrom.
  191. * @property {string} mediaid - A string uniquely identifying on piece of media on the platform
  192. * @property {number} [height] - The height of the embeded player
  193. * @property {number} [width] - The width of the embeded player
  194. * @property {boolean} [allowFullscreen] - True if the player can enter fullscreen
  195. * @property {boolean} [loop] - True if the player will start over at the end
  196. * @property {number} [timestamp] - The number of seconds at the begining that will be skiped
  197. */
  198.  
  199. /**
  200. * @typedef MediaPlatform
  201. * @name MediaPlatform
  202. */
  203.  
  204. /**
  205. * Parses a text can either be a url to a video on a platform, or an html
  206. * snipplet containing an embedding code for one of these platforms.
  207. *
  208. * It attemps to extract as much information as possible from this text.
  209. *
  210. * @method MediaPlatform~detect
  211. * @param {string} test - A URL or an html snipplet containing an embed code
  212. * @returns {MediaInfo|undefined} Information found in the string, undefined if none where found.
  213. */
  214.  
  215. /**
  216. * Generates a iframe embed html snipplet from a descriptor
  217. *
  218. * @method MediaPlatform~buildIframe
  219. * @param {MediaInfo} descriptor
  220. * @returns {string} An html snipplet
  221. */
  222.  
  223. /**
  224. * Generates a link url from a descriptor
  225. *
  226. * @method MediaPlatform~buildLink
  227. * @param {MediaInfo} descriptor
  228. * @returns {string} A url
  229. */
  230.  
  231. /** @type {Object.<string, MediaPlatform>} */
  232. const platforms = {
  233. youtube: require("./platforms/youtube"),
  234. dailymotion: require("./platforms/dailymotion"),
  235. vimeo: require("./platforms/vimeo")
  236. };
  237.  
  238. /** @type {MediaPlatform} */
  239. global.test = module.exports = {
  240. detect: function(text) {
  241. for (const platform in platforms) {
  242. const ret = platforms[platform].detect(text);
  243. if (ret) {
  244. ret.platform = platform;
  245. return ret;
  246. }
  247. }
  248. },
  249. buildIframe: function (descriptor) {
  250. const platform = platforms[descriptor.platform];
  251. return platform.buildIframe(descriptor);
  252. },
  253. buildLink: function (descriptor) {
  254. const platform = platforms[descriptor.platform];
  255. return platform.buildLink(descriptor);
  256. }
  257. };
  258.  
  259. }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  260. },{"./platforms/dailymotion":6,"./platforms/vimeo":7,"./platforms/youtube":8}],5:[function(require,module,exports){
  261. "use strict";
  262.  
  263. const querystring = require("querystring");
  264. const UrlHelper = require("./utils/url_helper");
  265. const DocHelper = require("./utils/doc_helper");
  266.  
  267. /**
  268. * @callback processor
  269. * @param {UrlObject} url
  270. * @param {Object} queryObject
  271. * @return {MediaInfo}
  272. */
  273.  
  274. /**
  275. * @callback generator
  276. * @param {MediaInfo} mediaInfo
  277. * @param {boolean} embedded
  278. * @param {string[]} queryParts
  279. * @returns {string} Url
  280. */
  281.  
  282. /**
  283. * @param {processor} urlProcessor
  284. * @param {generator} urlGenerator
  285. * @returns {MediaPlatform}
  286. */
  287. module.exports = function (urlProcessor, urlGenerator) {
  288. function wrappedProcessor (text) {
  289. try {
  290. const urlData = UrlHelper.parse(text);
  291. const qs = querystring.parse(urlData.query);
  292.  
  293. return urlProcessor(urlData, qs);
  294. } catch (e) {
  295. console.error(e);
  296. }
  297. }
  298.  
  299. function wrappedGenerator (data, embed) {
  300. const queryData = [];
  301. queryData.when = function (condition, val) { if (condition) this.push(val); };
  302. let url = urlGenerator(data, embed, queryData);
  303. if (queryData.length > 0) {
  304. url += "?" + queryData.join("&");
  305. }
  306. return url;
  307. }
  308. return {
  309. detect: (text) => {
  310. // is entire text a url?
  311. let entire = wrappedProcessor(text),
  312. ret;
  313. if (entire) {
  314. return entire;
  315. }
  316. for (const tag of DocHelper.get_nodes(text, "iframe", "embed", "a")) {
  317. switch (tag.nodeName.toLowerCase()) {
  318. case "iframe":
  319. case "embed":
  320. ret = DocHelper.processIframe(tag, wrappedProcessor);
  321. if (ret) {
  322. return ret;
  323. }
  324. break;
  325. case "a":
  326. ret = wrappedProcessor(tag.getAttribute("href"));
  327. if (ret) {
  328. return ret;
  329. }
  330. break;
  331. }
  332. }
  333. },
  334. buildIframe: (data) => {
  335. return DocHelper.buildIframe(wrappedGenerator(data, true), data.height, data.width, data.allowFullscreen);
  336. },
  337. buildLink: (data) => {
  338. return wrappedGenerator(data, false);
  339. }
  340. };
  341. };
  342.  
  343. },{"./utils/doc_helper":9,"./utils/url_helper":10,"querystring":13}],6:[function(require,module,exports){
  344. "use strict";
  345. const urlParse = /^\/(embed\/video|video|swf)\/([0-9a-zA-Z]*)/
  346.  
  347. module.exports = require("../platform_base")(
  348. function (urlData, qs) {
  349. if (urlData.normalizedHost === "dailymotion.com") {
  350. const urlMatch = urlData.pathname.match(urlParse);
  351. if (urlMatch) {
  352. return {
  353. mediaid: urlMatch[2],
  354. height: null,
  355. width: null,
  356. allowFullscreen: null,
  357. loop: null,
  358. timestamp: qs.start || null,
  359. autoplay: qs.autoplay === "1" || qs.autoPlay === "1"
  360. }
  361. }
  362. }
  363. },
  364. function (data, embed, query) {
  365. let url = embed ? "https://www.dailymotion.com/embed/video/" : "https://www.dailymotion.com/video/";
  366. url += data.mediaid.replace(/[^0-9a-zA-Z]/g, ""); // sanitize mediaid
  367. query.when(data.allowFullscreen === false, "fullscreen=1");
  368. query.when(data.autoplay , "autoplay=1");
  369. query.when(data.timestamp , "start=" + parseInt(data.timestamp));
  370. return url;
  371. }
  372. );
  373.  
  374. },{"../platform_base":5}],7:[function(require,module,exports){
  375. "use strict";
  376. const urlParse = /^\/(video\/)?([0-9]*)$/;
  377.  
  378. /**
  379. * @param {string} videoId
  380. * @param {object} param
  381. * @returns {MediaInfo}
  382. */
  383. function generate (videoId, param) {
  384. return {
  385. mediaid: videoId,
  386. height: null, width: null, timestamp: null,
  387. allowFullscreen: null,
  388. loop: param.loop === "1",
  389. autoplay: param.autoplay === "1"
  390. }
  391. }
  392.  
  393. module.exports = require("../platform_base")(
  394. function (urlData, qs) {
  395. if (urlData.normalizedHost === "vimeo.com" || urlData.normalizedHost === "player.vimeo.com") {
  396. if (urlData.pathname === "/moogaloop.swf") {
  397. return generate(qs.clip_id, qs);
  398. }
  399. const urlMatch = urlData.pathname.match(urlParse);
  400. if (urlMatch) {
  401. return generate(urlMatch[2], qs);
  402. }
  403. }
  404. },
  405. function (data, embed, query) {
  406. let url = embed ? "https://player.vimeo.com/video/" : "https://vimeo.com/";
  407. url += data.mediaid.replace(/[^0-9]/g, ""); // sanitize mediaid
  408. query.when(data.loop, "loop=1");
  409. query.when(data.autoplay, "autoplay=1");
  410.  
  411. return url;
  412. }
  413. );
  414.  
  415. },{"../platform_base":5}],8:[function(require,module,exports){
  416. "use strict";
  417. const querystring = require("querystring");
  418. const embedUrlParse = /^\/(embed|v)\/([0-9a-zA-Z\-_]*)(.*)/;
  419.  
  420. /**
  421. * @param {string} videoId
  422. * @param {object} param
  423. * @returns {MediaInfo}
  424. */
  425. function generate (videoId, param) {
  426. return {
  427. mediaid: videoId,
  428. height: null, width: null,
  429. allowFullscreen: param.fs !== "0",
  430. timestamp: param.t || param.time || param.start || null,
  431. loop: param.loop === "1",
  432. autoplay: param.autoplay === "1"
  433. }
  434. }
  435.  
  436. module.exports = require("../platform_base")(
  437. function (urlData, qs) {
  438. if (urlData.normalizedHost === "youtube.com") {
  439. if (qs.v) {
  440. return generate(qs.v, qs);
  441. }
  442. const embedUrlMatch = urlData.pathname.match(embedUrlParse);
  443. if (embedUrlMatch) {
  444. // youtube /v/ urls can be kind of odd and and append the query with & to the path
  445. const vUrlQs = querystring.parse(embedUrlMatch[3]);
  446. return generate(embedUrlMatch[2], Object.assign({}, qs, vUrlQs));
  447. }
  448. } else if (urlData.normalizedHost === "youtu.be") {
  449. const qs = querystring.parse(urlData.query);
  450. return generate(urlData.pathname.slice(1), qs);
  451. }
  452. },
  453. function (data, embed, query) {
  454. let url = embed ? "https://www.youtube.com/embed/" : "https://www.youtube.com/watch";
  455. const mediaid = data.mediaid.replace(/[^0-9a-zA-Z\-_]/g, ""); // sanitize mediaid
  456. query.when(data.allowFullscreen === false, "fs=1");
  457. query.when(data.loop, "loop=1");
  458. query.when(data.autoplay, "autoplay=1");
  459. if (data.timestamp) {
  460. query.push("start=" + data.timestamp.replace(/[^0-9hms]/g, ""));
  461. }
  462.  
  463. if (embed) {
  464. url += mediaid;
  465. } else {
  466. query.push("v=" + mediaid)
  467. }
  468.  
  469. return url;
  470. }
  471. );
  472.  
  473. },{"../platform_base":5,"querystring":13}],9:[function(require,module,exports){
  474. const DOMParser = (window.window).DOMParser;
  475.  
  476. module.exports = {
  477. get_nodes: function (text, ...node_types) {
  478. let parser = (new DOMParser ()).parseFromString("<html><body>" + text + "</body></html>", "text/html");
  479. var tags = [];
  480. for (const node_type of node_types) {
  481. tags.push.apply(tags, parser.getElementsByTagName(node_type));
  482. }
  483. return tags;
  484. },
  485.  
  486. processIframe: function (iframe, urlProcessor) {
  487. const ret = urlProcessor(iframe.getAttribute("src"));
  488. if (ret) {
  489. ret.allowFullscreen = iframe.getAttribute("allowfullscreen") != null;
  490. ret.height = iframe.getAttribute("height");
  491. ret.width = iframe.getAttribute("width");
  492. return ret;
  493. }
  494. },
  495.  
  496. buildIframe: function (src, height, width, allowFullscreen) {
  497. let ret = "<iframe "
  498. if (height != null) {
  499. ret += 'height="' + parseInt(height) + '" '
  500. }
  501. if (width != null) {
  502. ret += 'width="' + parseInt(width) + '" '
  503. }
  504. if (allowFullscreen === true) {
  505. ret += 'allowfullscreen webkitallowfullscreen mozallowfullscreen '
  506. }
  507. ret += 'frameborder="0" src="' + src + '"></iframe>'
  508. return ret;
  509. }
  510. };
  511.  
  512. },{}],10:[function(require,module,exports){
  513. const url = require("url");
  514. const querystring = require("querystring");
  515.  
  516. module.exports = {
  517. parse: function (text) {
  518. let fullUrl = url.parse(text);
  519. if (fullUrl.protocol === null) {
  520. fullUrl = url.parse("test:" + (text.startsWith("//") ? "" : "//") + text);
  521. }
  522.  
  523. if (fullUrl.host) {
  524. fullUrl.normalizedHost = fullUrl.host.startsWith("www.") ? fullUrl.host.substring(4) : fullUrl.host;
  525. }
  526.  
  527. return fullUrl;
  528. }
  529. };
  530.  
  531. },{"querystring":13,"url":3}],11:[function(require,module,exports){
  532. // Copyright Joyent, Inc. and other Node contributors.
  533. //
  534. // Permission is hereby granted, free of charge, to any person obtaining a
  535. // copy of this software and associated documentation files (the
  536. // "Software"), to deal in the Software without restriction, including
  537. // without limitation the rights to use, copy, modify, merge, publish,
  538. // distribute, sublicense, and/or sell copies of the Software, and to permit
  539. // persons to whom the Software is furnished to do so, subject to the
  540. // following conditions:
  541. //
  542. // The above copyright notice and this permission notice shall be included
  543. // in all copies or substantial portions of the Software.
  544. //
  545. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  546. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  547. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  548. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  549. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  550. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  551. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  552.  
  553. 'use strict';
  554.  
  555. // If obj.hasOwnProperty has been overridden, then calling
  556. // obj.hasOwnProperty(prop) will break.
  557. // See: https://github.com/joyent/node/issues/1707
  558. function hasOwnProperty(obj, prop) {
  559. return Object.prototype.hasOwnProperty.call(obj, prop);
  560. }
  561.  
  562. module.exports = function(qs, sep, eq, options) {
  563. sep = sep || '&';
  564. eq = eq || '=';
  565. var obj = {};
  566.  
  567. if (typeof qs !== 'string' || qs.length === 0) {
  568. return obj;
  569. }
  570.  
  571. var regexp = /\+/g;
  572. qs = qs.split(sep);
  573.  
  574. var maxKeys = 1000;
  575. if (options && typeof options.maxKeys === 'number') {
  576. maxKeys = options.maxKeys;
  577. }
  578.  
  579. var len = qs.length;
  580. // maxKeys <= 0 means that we should not limit keys count
  581. if (maxKeys > 0 && len > maxKeys) {
  582. len = maxKeys;
  583. }
  584.  
  585. for (var i = 0; i < len; ++i) {
  586. var x = qs[i].replace(regexp, '%20'),
  587. idx = x.indexOf(eq),
  588. kstr, vstr, k, v;
  589.  
  590. if (idx >= 0) {
  591. kstr = x.substr(0, idx);
  592. vstr = x.substr(idx + 1);
  593. } else {
  594. kstr = x;
  595. vstr = '';
  596. }
  597.  
  598. k = decodeURIComponent(kstr);
  599. v = decodeURIComponent(vstr);
  600.  
  601. if (!hasOwnProperty(obj, k)) {
  602. obj[k] = v;
  603. } else if (isArray(obj[k])) {
  604. obj[k].push(v);
  605. } else {
  606. obj[k] = [obj[k], v];
  607. }
  608. }
  609.  
  610. return obj;
  611. };
  612.  
  613. var isArray = Array.isArray || function (xs) {
  614. return Object.prototype.toString.call(xs) === '[object Array]';
  615. };
  616.  
  617. },{}],12:[function(require,module,exports){
  618. // Copyright Joyent, Inc. and other Node contributors.
  619. //
  620. // Permission is hereby granted, free of charge, to any person obtaining a
  621. // copy of this software and associated documentation files (the
  622. // "Software"), to deal in the Software without restriction, including
  623. // without limitation the rights to use, copy, modify, merge, publish,
  624. // distribute, sublicense, and/or sell copies of the Software, and to permit
  625. // persons to whom the Software is furnished to do so, subject to the
  626. // following conditions:
  627. //
  628. // The above copyright notice and this permission notice shall be included
  629. // in all copies or substantial portions of the Software.
  630. //
  631. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  632. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  633. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  634. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  635. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  636. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  637. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  638.  
  639. 'use strict';
  640.  
  641. var stringifyPrimitive = function(v) {
  642. switch (typeof v) {
  643. case 'string':
  644. return v;
  645.  
  646. case 'boolean':
  647. return v ? 'true' : 'false';
  648.  
  649. case 'number':
  650. return isFinite(v) ? v : '';
  651.  
  652. default:
  653. return '';
  654. }
  655. };
  656.  
  657. module.exports = function(obj, sep, eq, name) {
  658. sep = sep || '&';
  659. eq = eq || '=';
  660. if (obj === null) {
  661. obj = undefined;
  662. }
  663.  
  664. if (typeof obj === 'object') {
  665. return map(objectKeys(obj), function(k) {
  666. var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
  667. if (isArray(obj[k])) {
  668. return map(obj[k], function(v) {
  669. return ks + encodeURIComponent(stringifyPrimitive(v));
  670. }).join(sep);
  671. } else {
  672. return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
  673. }
  674. }).join(sep);
  675.  
  676. }
  677.  
  678. if (!name) return '';
  679. return encodeURIComponent(stringifyPrimitive(name)) + eq +
  680. encodeURIComponent(stringifyPrimitive(obj));
  681. };
  682.  
  683. var isArray = Array.isArray || function (xs) {
  684. return Object.prototype.toString.call(xs) === '[object Array]';
  685. };
  686.  
  687. function map (xs, f) {
  688. if (xs.map) return xs.map(f);
  689. var res = [];
  690. for (var i = 0; i < xs.length; i++) {
  691. res.push(f(xs[i], i));
  692. }
  693. return res;
  694. }
  695.  
  696. var objectKeys = Object.keys || function (obj) {
  697. var res = [];
  698. for (var key in obj) {
  699. if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
  700. }
  701. return res;
  702. };
  703.  
  704. },{}],13:[function(require,module,exports){
  705. 'use strict';
  706.  
  707. exports.decode = exports.parse = require('./decode');
  708. exports.encode = exports.stringify = require('./encode');
  709.  
  710. },{"./decode":11,"./encode":12}]},{},[2]);