Greasy Fork is available in English.

Medium Free

Automatically detect Medium articles (including subdomains and custom domains) and replace the URL with freedium.cfd to unlock Medium Posts.

  1. // ==UserScript==
  2. // @name Medium Free
  3. // @name:zh-CN Medium Free
  4. // @namespace https://github.com/blacksev
  5. // @version 1.0.2
  6. // @description:zh-CN 支持自动检测Medium文章(包括子域名和自定义域名),替换URL为freedium.cfd来解锁Medium付费文章
  7. // @description Automatically detect Medium articles (including subdomains and custom domains) and replace the URL with freedium.cfd to unlock Medium Posts.
  8. // @author origin by blacksev
  9. // @match *://medium.com/*
  10. // @match *://*.medium.com/*
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. "use strict";
  17. if (!document.scripts.length) {
  18. return;
  19. }
  20. if (document.body.outerHTML.lastIndexOf("cdn-client.medium.com") < 0) {
  21. return;
  22. }
  23. const key = encodeURIComponent('blacksev:Medium Unlock:Start');
  24. if (window[key]) {
  25. return;
  26. }
  27. window[key] = true;
  28.  
  29. console.log("Here is Medium!!!!");
  30.  
  31. const DragButton = function () {
  32. };
  33. DragButton.prototype = {
  34. //window对象
  35. win: window,
  36. //拖拽dom
  37. ele: null,
  38. //默认配置
  39. options: {
  40. edge: true, //是否吸附边缘,默认吸附
  41. extRoute: null,//标签Ext的路由,进行跳转的
  42. elemId: 'medium-unlock-button',//组件要加载的节点位置ID,默认加在body下
  43. instance: 'freedium.cfd'
  44. },
  45. //系统变量集
  46. data: {
  47. distanceX: 0,
  48. distanceY: 0
  49. },
  50.  
  51. /**
  52. * @method 初始化
  53. * @param opts 由@method config() 提供的配置参数
  54. */
  55. init: function (opts) {
  56. const _this = this;
  57. const option = _this.config(opts, _this.options);//用户配置
  58. const _elem = document.getElementById(option.elemId) ? document.getElementById(option.elemId) : document.body;
  59. if (!_elem) {
  60. console.log("not find nodeId!!");
  61. return;
  62. }
  63. //初始拖拽按钮,加载到_elem内
  64. _this.initEle(_elem);
  65. //注册点击事件
  66. _this.ele.addEventListener('click', function () {
  67. _this.click();
  68. });
  69. //注册拖拽开始事件
  70. _this.ele.addEventListener('touchstart', function (event) {
  71. _this.touchstart(event);
  72. });
  73. //注册拖拽移动事件
  74. document.addEventListener(
  75. 'touchmove',
  76. function (event) {
  77. _this.touchmove(event);
  78. }, {// fix #3 #5
  79. passive: false
  80. });
  81. //注册拖拽完成事件
  82. document.addEventListener('touchend', function () {
  83. _this.touchend();
  84. });
  85. },
  86. /**
  87. * @method 配置
  88. * @param opts { object } 用户提供的参数,在没有提供参数的情况下使用默认参数
  89. * @param options { object } 默认参数
  90. * @return options { object } 返回一个配置对象
  91. */
  92. config: function (opts, options) {
  93. //默认参数
  94. if (!opts) {
  95. return options;
  96. }
  97. for (const key in opts) {
  98. if (!!opts[key]) {
  99. options[key] = opts[key];
  100. }
  101. }
  102. return options;
  103. },
  104.  
  105. /**
  106. * @method 初始拖拽按钮,加载到_elem内
  107. * @param _elem { object } 指定挂载的节点
  108. * @return options { object } 返回一个拖拽按钮
  109. */
  110. initEle: function (_elem) {
  111. const _this = this;
  112. //创建一个div
  113. const ele = document.createElement('div');
  114. //通过createElementNS创建svg元素并设置属性
  115. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  116. svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
  117. svg.setAttribute("class", "unlock-svg");
  118. svg.setAttribute("style", "width:100%;height:100%;vertical-align:unset;transition: color .1s;");
  119. svg.setAttribute("viewBox", "0 0 32 32");
  120. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  121. path.setAttribute("d", "M24 14H12V8a4 4 0 0 1 8 0h2a6 6 0 0 0-12 0v6H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2zm0 14H8V16h16z");
  122. path.setAttribute("fill", "currentColor");
  123.  
  124. svg.appendChild(path);
  125.  
  126. //SVG元素添加到页面内显示
  127. ele.appendChild(svg);
  128.  
  129. ele.id = "drag_button_id";
  130. ele.className = "drag-button-div";
  131.  
  132. //样式
  133. ele.style.position = "fixed";
  134. ele.style.lineHeight = "4rem";
  135. ele.style.width = "4rem";
  136. ele.style.height = "4rem";
  137. ele.style.padding = "0.5rem";
  138. ele.style.textAlign = "center";
  139. ele.style.border = "5px solid #27ae60";
  140. ele.style.borderRadius = "99px";
  141. ele.style.color = "#fff";
  142. ele.style.opacity = "0.5";
  143. ele.style.backgroundColor = "#2ecc71";
  144. ele.style.backgroundClip = "padding-box";
  145. ele.style.textDecoration = "none";
  146. ele.style.top = "13em";
  147. ele.style.right = "0px";
  148. ele.style.zIndex = "1";
  149. ele.style.transition = "box-shadow .2s";
  150.  
  151. //js控制css的黑魔法
  152. const head = document.getElementsByTagName('head')[0];
  153. const style = document.createElement('style');
  154. const declarations = document.createTextNode('.drag-button-div:hover{ box-shadow: 0px 0 0 11px #FFF, 0px 0 0 10px #27ae60, 0px 0 0 50px #FFF inset;}.drag-button-div:active{ box-shadow: 0px 0 0 11px #27ae60, 0px 0 0 10px #27ae60, 0px 0 0 50px #FFF inset;}.drag-button-div:hover .unlock-svg{color:blue;}');
  155. style.type = 'text/css';
  156. if (style.styleSheet) {
  157. style.styleSheet.cssText = declarations.nodeValue;
  158. } else {
  159. style.appendChild(declarations);
  160. }
  161. head.appendChild(style);
  162.  
  163. //动态插入到body中
  164. _elem.insertBefore(ele, _elem.lastChild);
  165. //赋值到全局变量
  166. _this.ele = ele;
  167. //初始化位置
  168. let strStoreDistance = '';
  169. // 居然有android机子不支持localStorage
  170. if (_this.ele.id && _this.win.localStorage && (strStoreDistance = localStorage['Inertia_' + _this.ele.id])) {
  171. const arrStoreDistance = strStoreDistance.split(',');
  172. _this.ele.distanceX = +arrStoreDistance[0];
  173. _this.ele.distanceY = +arrStoreDistance[1];
  174. _this.ele = _this.fnTranslate(_this.ele, _this.ele.distanceX, _this.ele.distanceY);
  175. }
  176. // 显示拖拽元素
  177. _this.ele.style.visibility = 'visible';
  178. // 如果元素在屏幕之外,位置使用初始值
  179. const initBound = _this.ele.getBoundingClientRect();
  180. if (initBound.left < -0.5 * initBound.width ||
  181. initBound.top < -0.5 * initBound.height ||
  182. initBound.right > _this.win.innerWidth + 0.5 * initBound.width ||
  183. initBound.bottom > _this.win.innerHeight + 0.5 * initBound.height
  184. ) {
  185. _this.ele.distanceX = 0;
  186. _this.ele.distanceY = 0;
  187. _this.ele = _this.fnTranslate(_this.ele, 0, 0);
  188. }
  189. },
  190. /**
  191. * easeOutBounce算法
  192. * t: current time(当前时间);
  193. * b: beginning value(初始值);
  194. * c: change in value(变化量);
  195. * d: duration(持续时间)。
  196. */
  197. easeOutBounce: function (t, b, c, d) {
  198. if ((t /= d) < (1 / 2.75)) {
  199. return c * (7.5625 * t * t) + b;
  200. } else if (t < (2 / 2.75)) {
  201. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
  202. } else if (t < (2.5 / 2.75)) {
  203. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
  204. } else {
  205. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
  206. }
  207. },
  208.  
  209. // 设置transform坐标等方法
  210. fnTranslate: function (_ele, x, y) {
  211. x = Math.round(1000 * x) / 1000;
  212. y = Math.round(1000 * y) / 1000;
  213.  
  214. _ele.style.webkitTransform = 'translate(' + [x + 'px', y + 'px'].join(',') + ')';
  215. _ele.style.transform = 'translate3d(' + [x + 'px', y + 'px', 0].join(',') + ')';
  216.  
  217. return _ele
  218. },
  219.  
  220. /**
  221. * 拖拽按钮开始事件
  222. */
  223. touchstart: function (event) {
  224. const _this = this;
  225. const events = event.touches[0] || event;
  226.  
  227. _this.data.posX = events.pageX;
  228. _this.data.posY = events.pageY;
  229.  
  230. _this.data.touching = true;
  231.  
  232. if (_this.ele.distanceX) {
  233. _this.data.distanceX = _this.ele.distanceX;
  234. }
  235. if (_this.ele.distanceY) {
  236. _this.data.distanceY = _this.ele.distanceY;
  237. }
  238.  
  239. // 元素的位置数据
  240. _this.data.bound = _this.ele.getBoundingClientRect();
  241.  
  242. _this.data.timerready = true;
  243. },
  244.  
  245. /**
  246. * 拖拽按钮移动事件
  247. */
  248. touchmove: function (event) {
  249. const _this = this;
  250. if (_this.data.touching !== true) {
  251. return;
  252. }
  253.  
  254. // 当移动开始的时候开始记录时间
  255. if (_this.data.timerready === true) {
  256. _this.data.timerstart = +new Date();
  257. _this.data.timerready = false;
  258. }
  259.  
  260. event.preventDefault();
  261.  
  262. const events = event.touches[0] || event;
  263.  
  264. _this.data.nowX = events.pageX;
  265. _this.data.nowY = events.pageY;
  266.  
  267. let distanceX = _this.data.nowX - _this.data.posX,
  268. distanceY = _this.data.nowY - _this.data.posY;
  269.  
  270. // 此时元素的位置
  271. const absLeft = distanceX + _this.data.bound.left,
  272. absTop = distanceY + _this.data.bound.top,
  273. absRight = absLeft + _this.data.bound.width,
  274. absBottom = absTop + _this.data.bound.height;
  275.  
  276. // 边缘检测
  277. if (absLeft < 0) {
  278. distanceX = distanceX - absLeft;
  279. }
  280. if (absTop < 0) {
  281. distanceY = distanceY - absTop;
  282. }
  283. if (absRight > _this.win.innerWidth) {
  284. distanceX = distanceX - (absRight - _this.win.innerWidth);
  285. }
  286. if (absBottom > _this.win.innerHeight) {
  287. distanceY = distanceY - (absBottom - _this.win.innerHeight);
  288. }
  289.  
  290. // 元素位置跟随
  291. const x = _this.data.distanceX + distanceX,
  292. y = _this.data.distanceY + distanceY;
  293. _this.ele = _this.fnTranslate(_this.ele, x, y);
  294.  
  295. // 缓存移动位置
  296. _this.ele.distanceX = x;
  297. _this.ele.distanceY = y;
  298. },
  299.  
  300. /**
  301. * 拖拽按钮移动完成事件
  302. */
  303. touchend: function () {
  304. const edge = function () {
  305. // 时间
  306. let start = 0, during = 25;
  307. // 初始值和变化量
  308. let init = _this.ele.distanceX, y = _this.ele.distanceY, change = 0;
  309. // 判断元素现在在哪个半区
  310. const bound = _this.ele.getBoundingClientRect();
  311. if (bound.left + bound.width / 2 < _this.win.innerWidth / 2) {
  312. change = -1 * bound.left;
  313. } else {
  314. change = _this.win.innerWidth - bound.right;
  315. }
  316.  
  317. const run = function () {
  318. // 如果用户触摸元素,停止继续动画
  319. if (_this.data.touching === true) {
  320. _this.data.inertiaing = false;
  321. return;
  322. }
  323.  
  324. start++;
  325. const x = _this.easeOutBounce(start, init, change, during);
  326. _this.ele = _this.fnTranslate(_this.ele, x, y);
  327.  
  328. if (start < during) {
  329. requestAnimationFrame(run);
  330. } else {
  331. _this.ele.distanceX = x;
  332. _this.ele.distanceY = y;
  333. _this.data.inertiaing = false;
  334. if (_this.win.localStorage) {
  335. localStorage['Inertia_' + _this.ele.id] = [x, y].join();
  336. }
  337. }
  338. };
  339. run();
  340. };
  341. const _this = this;
  342. if (_this.data.touching === false) {
  343. // fix iOS fixed bug
  344. return;
  345. }
  346. _this.data.touching = false;
  347.  
  348. // 计算速度
  349. _this.data.timerend = +new Date();
  350.  
  351. if (!_this.data.nowX || !_this.data.nowY) {
  352. return;
  353. }
  354.  
  355. // 移动的水平和垂直距离
  356. const distanceX = _this.data.nowX - _this.data.posX,
  357. distanceY = _this.data.nowY - _this.data.posY;
  358.  
  359. if (Math.abs(distanceX) < 5 && Math.abs(distanceY) < 5) {
  360. return;
  361. }
  362.  
  363. // 距离和时间
  364. const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
  365. time = _this.data.timerend - _this.data.timerstart;
  366.  
  367. // 速度,每一个自然刷新此时移动的距离
  368. let speed = distance / time * 16.666;
  369.  
  370. // 经测试,2~60多px不等
  371. // 设置衰减速率
  372. // 数值越小,衰减越快
  373. const rate = Math.min(10, speed);
  374.  
  375. // 开始惯性缓动
  376. _this.data.inertiaing = true;
  377.  
  378. // 反弹的参数
  379. let reverseX = 1, reverseY = 1;
  380.  
  381. // 速度计算法
  382. const step = function () {
  383. if (_this.data.touching === true) {
  384. _this.data.inertiaing = false;
  385. return;
  386. }
  387. speed = speed - speed / rate;
  388.  
  389. // 根据运动角度,分配给x, y方向
  390. let moveX = reverseX * speed * distanceX / distance, moveY = reverseY * speed * distanceY / distance;
  391.  
  392. // 此时元素的各个数值
  393. const bound = _this.ele.getBoundingClientRect();
  394.  
  395. if (moveX < 0 && bound.left + moveX < 0) {
  396. moveX = 0 - bound.left;
  397. // 碰触边缘方向反转
  398. reverseX = reverseX * -1;
  399. } else if (moveX > 0 && bound.right + moveX > _this.win.innerWidth) {
  400. moveX = _this.win.innerWidth - bound.right;
  401. reverseX = reverseX * -1;
  402. }
  403.  
  404. if (moveY < 0 && bound.top + moveY < 0) {
  405. moveY = -1 * bound.top;
  406. reverseY = -1 * reverseY;
  407. } else if (moveY > 0 && bound.bottom + moveY > _this.win.innerHeight) {
  408. moveY = _this.win.innerHeight - bound.bottom;
  409. reverseY = -1 * reverseY;
  410. }
  411.  
  412. const x = _this.ele.distanceX + moveX, y = _this.ele.distanceY + moveY;
  413. // 位置变化
  414. _this.ele = _this.fnTranslate(_this.ele, x, y);
  415.  
  416. _this.ele.distanceX = x;
  417. _this.ele.distanceY = y;
  418.  
  419. if (speed < 0.1) {
  420. speed = 0;
  421. if (_this.options.edge === false) {
  422. _this.data.inertiaing = false;
  423.  
  424. if (_this.win.localStorage) {
  425. localStorage['Inertia_' + _this.ele.id] = [x, y].join();
  426. }
  427. } else {
  428. // 边缘吸附
  429. edge();
  430. }
  431. } else {
  432. requestAnimationFrame(step);
  433. }
  434. };
  435. step();
  436. },
  437. /**
  438. * 拖拽按钮点击事件
  439. */
  440. click: function () {
  441. const _this = this;
  442. window.location.hostname = _this.options.instance;
  443. }
  444. };
  445. new DragButton().init();
  446. }());