Console Importer

Easily import JS and CSS resources from Chrome console.

  1. // ==UserScript==
  2. // @author Lete114
  3. // @version 0.1
  4. // @license MIT License
  5. // @name Console Importer
  6. // @description Easily import JS and CSS resources from Chrome console.
  7. // @match *://*/*
  8. // @icon data:image/png;base64,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
  9. // @namespace https://github.com/Lete114/my-tampermonkey-scripts
  10. // @grant unsafeWindow
  11. // ==/UserScript==
  12.  
  13. ;(function () {
  14. 'use strict'
  15. // https://github.com/pd4d10/console-importer
  16. const window = unsafeWindow
  17. const tiza = new Tiza()
  18. const PREFIX_TEXT = '[$i]: '
  19. const prefix = tiza.color('blue').text
  20. const strong = tiza.color('blue').bold().text
  21. const error = tiza.color('red').text
  22. const log = (...args) => tiza.log(prefix(PREFIX_TEXT), ...args)
  23. const logError = (...args) => tiza.log(error(PREFIX_TEXT), ...args)
  24.  
  25. /**
  26. * @type { Set<string> | null }
  27. */
  28. let lastGlobalVariableSet = null
  29.  
  30. /**
  31. *
  32. * @param { string } name
  33. * @returns
  34. */
  35. function createBeforeLoad(name) {
  36. return () => {
  37. lastGlobalVariableSet = new Set(Object.keys(window))
  38. log(strong(name), ' is loading, please be patient...')
  39. }
  40. }
  41.  
  42. /**
  43. *
  44. * @param { string } name
  45. * @param { string? } url
  46. * @returns
  47. */
  48. function createOnLoad(name, url) {
  49. return () => {
  50. const urlText = url ? `(${url})` : ''
  51. log(strong(name), `${urlText} is loaded.`)
  52.  
  53. const currentGlobalVariables = Object.keys(window)
  54. const newGlobalVariables = currentGlobalVariables.filter(
  55. (key) => !lastGlobalVariableSet?.has(key)
  56. )
  57. if (newGlobalVariables.length > 0) {
  58. log(
  59. 'The new global variables are as follows: ',
  60. strong(newGlobalVariables.join(',')),
  61. ' . Maybe you can use them.'
  62. )
  63. } else {
  64. // maybe css request or script loaded already
  65. }
  66. // Update global variable list
  67. lastGlobalVariableSet = new Set(currentGlobalVariables)
  68. }
  69. }
  70.  
  71. /**
  72. *
  73. * @param { string } name
  74. * @param { string? } url
  75. * @returns
  76. */
  77. function createOnError(name, url) {
  78. return () => {
  79. const urlText = url ? `(${strong(url)})` : ''
  80. logError(
  81. 'Fail to load ',
  82. strong(name),
  83. ', is this URL',
  84. urlText,
  85. ' correct?'
  86. )
  87. }
  88. }
  89.  
  90. // Try to remove referrer for security
  91. // https://imququ.com/post/referrer-policy.html
  92. // https://www.w3.org/TR/referrer-policy/
  93. function addNoReferrerMeta() {
  94. const originMeta =
  95. document.querySelector < HTMLMetaElement > 'meta[name=referrer]'
  96.  
  97. if (originMeta) {
  98. // If there is already a referrer policy meta tag, save origin content
  99. // and then change it, call `remove` to restore it
  100. const content = originMeta.content
  101. originMeta.content = 'no-referrer'
  102. return function remove() {
  103. originMeta.content = content
  104. }
  105. } else {
  106. // Else, create a new one, call `remove` to delete it
  107. const meta = document.createElement('meta')
  108. meta.name = 'referrer'
  109. meta.content = 'no-referrer'
  110. document.head.appendChild(meta)
  111. return function remove() {
  112. // Removing meta tag directly not work, should set it to default first
  113. meta.content = 'no-referrer-when-downgrade'
  114. document.head.removeChild(meta)
  115. }
  116. }
  117. }
  118.  
  119. /**
  120. * Insert script tag
  121. * @param { sting } url
  122. * @param { function } onload
  123. * @param { function } onerror
  124. */
  125. function injectScript(url, onload, onerror) {
  126. const remove = addNoReferrerMeta()
  127. const script = document.createElement('script')
  128. script.src = url
  129. script.onload = onload
  130. script.onerror = onerror
  131. document.body.appendChild(script)
  132. remove()
  133. document.body.removeChild(script)
  134. }
  135.  
  136. /**
  137. * Insert link tag
  138. * @param { string } url
  139. * @param { function } onload
  140. * @param { function } onerror
  141. */
  142. function injectStyle(url, onload, onerror) {
  143. const remove = addNoReferrerMeta()
  144. const link = document.createElement('link')
  145. link.href = url
  146. link.rel = 'stylesheet'
  147. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#Stylesheet_load_events
  148. link.onload = onload
  149. link.onerror = onerror
  150. document.head.appendChild(link)
  151. remove()
  152. // Should not remove <link> tag, unlike <script>
  153. }
  154.  
  155. /**
  156. *
  157. * @param { string } url
  158. * @param {*} beforeLoad
  159. * @param {*} onload
  160. * @param {*} onerror
  161. * @returns
  162. */
  163. function inject(
  164. url,
  165. beforeLoad = createBeforeLoad(url),
  166. onload = createOnLoad(url),
  167. onerror = createOnError(url)
  168. ) {
  169. beforeLoad()
  170.  
  171. // Handle CSS
  172. if (/\.css$/.test(url)) {
  173. return injectStyle(url, onload, onerror)
  174. }
  175.  
  176. // Handle JS
  177. return injectScript(url, onload, onerror)
  178. }
  179.  
  180. /**
  181. * From cdnjs
  182. * https://cdnjs.com/
  183. * @param { string } name
  184. */
  185. function cdnjs(name) {
  186. log('Searching for ', strong(name), ', please be patient...')
  187. fetch(`https://api.cdnjs.com/libraries?search=${name}`, {
  188. referrerPolicy: 'no-referrer'
  189. })
  190. .then((res) => res.json())
  191. .then(({ results }) => {
  192. if (results.length === 0) {
  193. logError(
  194. 'Sorry, ',
  195. strong(name),
  196. ' not found, please try another keyword.'
  197. )
  198. return
  199. }
  200.  
  201. const matchedResult = results.filter((item) => item.name === name)
  202. const { name: exactName, latest: url } = matchedResult[0] || results[0]
  203. if (name !== exactName) {
  204. log(
  205. strong(name),
  206. ' not found, import ',
  207. strong(exactName),
  208. ' instead.'
  209. )
  210. }
  211.  
  212. inject(
  213. url,
  214. createBeforeLoad(exactName),
  215. createOnLoad(exactName, url),
  216. createOnError(exactName, url)
  217. )
  218. })
  219. .catch(() => {
  220. logError(
  221. 'There appears to be some trouble with your network. If you think this is a bug, please report an issue:'
  222. )
  223. logError('https://github.com/pd4d10/console-importer/issues')
  224. })
  225. }
  226.  
  227. /**
  228. * From unpkg
  229. * https://unpkg.com
  230. * @param { string } name
  231. */
  232. function unpkg(name) {
  233. createBeforeLoad(name)()
  234. const url = `https://unpkg.com/${name}`
  235. injectScript(url, createOnLoad(name, url), createOnError(name, url))
  236. }
  237.  
  238. /**
  239. * https://www.jsdelivr.com/esm
  240. * @param { string } name
  241. * @returns
  242. */
  243. async function esm(name) {
  244. log(strong(name), '(esm) is loading, please be patient...')
  245. const res = await import(`https://esm.run/${name}`)
  246. return res
  247. }
  248.  
  249. /**
  250. * Entry
  251. * @param { string } originName
  252. * @returns
  253. */
  254. function importer(originName) {
  255. if (typeof originName !== 'string') {
  256. throw new Error('Argument should be a string, please check it.')
  257. }
  258.  
  259. // Trim string
  260. const name = originName.trim()
  261.  
  262. // If it is a valid URL, inject it directly
  263. if (/^https?:\/\//.test(name)) {
  264. return inject(name)
  265. }
  266.  
  267. // If version specified, try unpkg
  268. if (name.indexOf('@') !== -1) {
  269. return unpkg(name)
  270. }
  271.  
  272. return cdnjs(name)
  273. }
  274.  
  275. importer.cdnjs = cdnjs
  276. importer.unpkg = unpkg
  277. importer.esm = esm
  278.  
  279. // Do not output annoying ugly string of function content
  280. importer.toString = () => '$i'
  281.  
  282. // Assign to console
  283. console.$i = importer
  284.  
  285. // Do not break existing $i
  286. const win = window
  287. if (typeof win.$i === 'undefined') {
  288. win.$i = importer
  289. } else {
  290. log('$i is already in use, please use `console.$i` instead')
  291. }
  292.  
  293. function Tiza(currentStyles, texts, styles) {
  294. if (currentStyles === void 0) {
  295. currentStyles = []
  296. }
  297. if (texts === void 0) {
  298. texts = []
  299. }
  300. if (styles === void 0) {
  301. styles = []
  302. }
  303. var _this = this
  304. // Get method
  305. this.getCurrentStyles = function () {
  306. return _this._currentStyles
  307. }
  308. this.getTexts = function () {
  309. return _this._texts
  310. }
  311. this.getStyles = function () {
  312. return _this._styles
  313. }
  314. // Push a style to current Styles
  315. this.style = function (s) {
  316. return new Tiza(
  317. _this._currentStyles.concat([s]),
  318. _this._texts,
  319. _this._styles
  320. )
  321. }
  322. // Alias for style method
  323. this.color = function (c) {
  324. return _this.style('color:' + c)
  325. }
  326. this.bgColor = function (c) {
  327. return _this.style('background-color:' + c)
  328. }
  329. this.bold = function () {
  330. return _this.style('font-weight:bold')
  331. }
  332. this.italic = function () {
  333. return _this.style('font-style:italic')
  334. }
  335. this.size = function (n) {
  336. var s = typeof n === 'number' ? n + 'px' : n // Convert number to px
  337. return _this.style('font-size:' + s)
  338. }
  339. // Clear all current styles
  340. this.reset = function () {
  341. return new Tiza([], _this._texts, _this._styles)
  342. }
  343. this.text = function () {
  344. var args = []
  345. for (var _i = 0; _i < arguments.length; _i++) {
  346. args[_i] = arguments[_i]
  347. }
  348. var texts = _this._texts.slice()
  349. var styles = _this._styles.slice()
  350. args.forEach(function (arg) {
  351. if (arg instanceof Tiza) {
  352. texts.push.apply(texts, arg.getTexts())
  353. styles.push.apply(styles, arg.getStyles())
  354. } else {
  355. texts.push(arg)
  356. styles.push(_this._currentStyles.join(';'))
  357. }
  358. })
  359. return new Tiza(_this._currentStyles, texts, styles)
  360. }
  361. this.space = function (count) {
  362. if (count === void 0) {
  363. count = 1
  364. }
  365. return _this.text(repeat(' ', count))
  366. }
  367. this.newline = function (count) {
  368. if (count === void 0) {
  369. count = 1
  370. }
  371. return _this.text(repeat('\n', count))
  372. }
  373. this._output = function (type) {
  374. return function () {
  375. var args = []
  376. for (var _i = 0; _i < arguments.length; _i++) {
  377. args[_i] = arguments[_i]
  378. }
  379. var ins = _this.text.apply(_this, args)
  380. console[type].apply(
  381. console,
  382. [
  383. ins
  384. .getTexts()
  385. .map(function (t) {
  386. return '%c' + t
  387. })
  388. .join('')
  389. ].concat(ins._styles)
  390. )
  391. return new Tiza(ins.getCurrentStyles())
  392. }
  393. }
  394. this.log = this._output('log')
  395. this.info = this._output('info')
  396. this.warn = this._output('warn')
  397. this.error = this._output('error')
  398. this._currentStyles = currentStyles
  399. this._texts = texts
  400. this._styles = styles
  401. }
  402. })()