分類
Linux

Hourly Reminder

忙起來的時候不知不覺時間就過去了,而久坐對健康絕對不是什麼好事。那麼就產生了一個需求,提醒我時間的流逝。Android上有個不錯的開源應用Hourly Reminder,但是我是在工作的時候才需要提醒,所以最好是桌面彈窗。簡單搜索發現Fedora預裝的notify-send命令就能很好的實現toast效果,搭配crontab就能滿足需求了。

crontab -e
#單行Hourly Reminder簡潔版
0 9-18 * * 1-5 notify-send -t 6000 '整點咯' 'It is ? time.'
#Hourly Reminder python 豪華版
0 9-18 * * 1-5 /home/42/Programs/p37/bin/python /home/42/eclipse-workspace/scriptsP37/hourlyReminder.py

file:/home/42/eclipse-workspace/scriptsP37/hourlyReminder.py

#!/home/42/Programs/p37/bin/python
import time,datetime,subprocess,random

ts = int(time.time())
hour = datetime.datetime.fromtimestamp(ts).strftime('%H')
title = "現在時刻"+hour+"點整"
emojiList=['?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','☕️','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?']
content = '\r        '+random.choice(emojiList)+'\r'
subprocess.check_call(['notify-send','-t','6000',title,content])

定時任務參考自crontab guru。 Emoji複製自Get Emoji

代碼里的emoji被wordpress強制轉義,從段落里複製吧emojiList=['?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','☕️','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?']

本文更新於 2021/09/13。

分類
软件

Fedora使用Impactor安裝ipa

使用Impactor可以為蘋果手機安裝ipa程序,無論手機是否越獄。從Cydia Impactor下載需要的包並解壓。

./Impactor

./Impactor: error while loading shared libraries: libudev.so.0: cannot open shared object file: No such file or directory
#若出現上述錯誤則
sudo ln -sf /usr/lib64/libudev.so.1 /usr/lib64/libudev.so.0

./Impactor: error while loading shared libraries: libatomic.so.1: cannot open shared object file: No such file or directory
#若出現上述錯誤則
sudo dnf install libatomic

./Impactor: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory
#若出現上述錯誤則
sudo dnf install ncurses-compat-libs

啟動Impactor後,先選中手機,再在Device>Install package選擇要安裝的包,輸入蘋果賬號密碼即可。




Fedora找尋丟失庫的方法,如

./Impactor: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory
sudo dnf provides libncurses.so.5
ncurses-compat-libs-6.0-8.20170212.fc26.i686 : Ncurses compatibility libraries
軟體庫    :fedora
符合之來源:
Provide    : libncurses.so.5
sudo dnf install ncurses-compat-libs
分類
說說

棄用ibus-rime

試圖在Fedora26上使用Rime輸入法,經過數小時嘗試都不能完整打出自己想要的句子,只能吐血半升後放棄。IBus Intelligent Pinyin雖然有不少缺點也傻傻的,但仍然是我目前遇到最好用的開源拼音輸入法。查看源碼發現已經智能拼音已經更新到了1.10.92,在Fedora26上嘗試編譯失敗後(一天內兩次失敗,真是受打擊?),決定升級Fedora。

迅速新裝了一個Fedora 29 Xfce,很好用,美滋滋。

分類
程序

幹掉QQProtect.exe

本文使用python3搭配windows的定時任務來實現正常使用TIM而不運行QQProtect.exe的功能。之所以沒有用bat是因為bat會彈出黑色命令行窗口,而使用pythonw則不會彈窗,體驗較好。方法參考自知乎如何禁用QPCore service启动项?

#file:killQQP.pyw
#杀死QQProtect.exe:killQQP.pyw 1
#启动TIM时杀死QQProtect.exe:killQQP.pyw 2

import os,time,sys

def killProgress(targetProgress):
    if targetProgress in os.popen('tasklist /FI \"IMAGENAME eq '+targetProgress+'\"').read():
        os.system('TASKKILL /F /IM '+targetProgress)

progressToBeKill = "QQProtect.exe"
progressToBeLaunch = "TIM.exe"
targetProgressFile = 'E:\\\"Program Files (x86)\"\\Tencent\\TIM\Bin\\QQScLauncher.exe' 
  
if str(sys.argv[1]) == "1":
    killProgress(progressToBeKill)
elif str(sys.argv[1]) == "2":
    count = 0
    time.sleep(5)
    os.system(targetProgressFile)
    time.sleep(2)
    while count < 5:
        killProgress(progressToBeKill)
        time.sleep(10)
        count = count + 1
        
else:
    killProgress(progressToBeKill)

然後在開始菜單運行taskschd.msc,新建定時任務。第一個定時任務:常規選項卡勾選“使用最高權限運行”;觸發器選項卡選擇“登錄時”;操作選項卡,程序或腳本填pythonw的位置,比如我的是C:\Users\42\AppData\Local\Programs\Python\Python35\pythonw.exe,添加參數則填寫上面腳本所在位置加參數,如E:\code\killQQP.pyw 1,保存。第二個定時任務:常規選項卡勾選“使用最高權限運行”;觸發器選項卡選擇“發生事件時”,然後日誌選“應用程序”,源填入QPCore,時間ID填入0;操作選項卡,程序或腳本填pythonw的位置,我的還是C:\Users\42\AppData\Local\Programs\Python\Python35\pythonw.exe,添加參數填寫腳本所在位置加參數,如E:\code\killQQP.pyw 2,保存即可。QQ用戶需要修改targetProgressFile為QQ的啟動文件,可以通過在QQ啟動快捷方式上右鍵查看詳情取得。

附:啟動TIM時幹掉QQProtect.exe的定時任務的導出文件killQQP2.xml。

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2018-11-05T11:11:00.0447223</Date>
    <Author>42-PC\42</Author>
  </RegistrationInfo>
  <Triggers>
    <EventTrigger>
      <Enabled>true</Enabled>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Application"&gt;&lt;Select Path="Application"&gt;*[System[Provider[@Name='QPCore'] and EventID=0]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
    </EventTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>42-PC\42</UserId>
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Users\42\AppData\Local\Programs\Python\Python35\pythonw.exe</Command>
      <Arguments>E:\code\killQQP.pyw 2</Arguments>
    </Exec>
  </Actions>
</Task>

本文更新於 2018/11/16。

分類
說說

18103113

Suddenly, they find that they can't take a picture without a camera.

分類
Linux

使用Termux搭建假wifi熱點

注:本文旨在學習研究wifi熱點相關知識,請勿用於任何違反道德之事。

需要一台Root過的Android(系統版本不低於5.0)手機,安裝好Termux和python。實現Captive Portal需要兩部分,請求重定向和授權頁面。重定向的實現有多種,這裡既然是假熱點,所以使用最簡單的iptables重定向dns。授權頁面則直接採用jczic/MicroWebSrv實現的網頁服務器。把MicroWebSrv解壓到~/scripts/MicroWeb,然後新建如下文件

#~/scripts/MicroWeb/forward.sh
remove127(){
  /system/bin/iptables -t nat -D PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.43.1:8000&&/system/bin/iptables -t nat -D PREROUTING -p tcp --dport 443 -j DNAT --to-destination 192.168.43.1:8000
}
add127(){
  /system/bin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.43.1:8000&&/system/bin/iptables -t nat -I PREROUTING -p tcp --dport 443 -j DNAT --to-destination 192.168.43.1:8000
}
if [[ $1 = "1" ]]; then
  echo "start"       
  add127
elif [[ $1 = "0" ]]; then
  echo "stop"
  remove127
fi
#~/scripts/MicroWeb/web.py
from microWebSrv import MicroWebSrv
mws = MicroWebSrv(port=8000,bindIP='0.0.0.0',webPath="/data/data/com.termux/files/home/scripts/MicroWeb/www")
mws.SetNotFoundPageUrl("http://192.168.43.1:8000/hotspot-detect.html")
mws.Start(False)
#~/scripts/MicroWeb/www/hotspot-detect.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="format-detection" content="telephone=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
  
  <title>Captive Portal</title>
  <script>
  /*!
 * clipboard.js v2.0.0
 * https://zenorocha.github.io/clipboard.js
 * 
 * Licensed MIT © Zeno Rocha
 */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),a=function(){function t(e){n(this,t),this.resolveOptions(e),this.initSelection()}return i(t,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n},function(t,e,n){var o,r,i;!function(a,c){r=[t,n(0),n(2),n(1)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e,n,o){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function c(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function u(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}var l=r(e),s=r(n),f=r(o),d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},h=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),p=function(t){function e(t,n){i(this,e);var o=a(this,(e.__proto__||Object.getPrototypeOf(e)).call(this));return o.resolveOptions(n),o.listenClick(t),o}return c(e,t),h(e,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])});
  </script>
    <style>
        .wrapper {
            text-align: center;
        }

        .button {
            position: absolute;
            top: 50%;
            margin-top:2em;
        }
    </style>
</head>
<body>
    <div class="wrapper">
        <button class="btn" id="clickButton" data-clipboard-text="just kidding :)">
            Connect to the Internet
        </button>
    </div>

    <script>
        var clipboard = new ClipboardJS('.btn');    
        clipboard.on('success', function(e) {
            console.info('Action:', e.action);
            console.info('Text:', e.text);
            console.info('Trigger:', e.trigger);

            e.clearSelection();
        });

        clipboard.on('error', function(e) {
            console.error('Action:', e.action);
            console.error('Trigger:', e.trigger);
        });  
    </script>
</body>
</html>

上面文件唯一需要修改的地方是你手機的wifi網關,可以在打開手機熱點後通過ip addr命令查看,我的是wlan0網卡,網關是192.168.43.1。

#iptable需要root權限
su
./forward.sh 1
#然后新开一个session運行web server
python web.py
#這時其他手機連上此熱點,即會彈出自定義頁面hotspot-detect.html。

本文更新於 2018/11/02。

分類
Linux 软件

AFWall+ 安卓可用的iptables

安卓手機如果root了,便可以通過AFWall+Github項目地址)來使用iptables管控網絡。

在安卓上使用DNSCrypt

由於TCP旁路阻斷技術的應用,DNSCrypt已經不能為日常使用網絡帶來太多方便,所以僅僅是記錄一下使用方法而已。先安裝好AFWall+並根據自己的喜好定製軟件的聯網規則,然後啟用iptables。我們使用Termux來至執行iptabls命令。根據自己的手機系統下載dnscrypt-proxy,比如我的就是dnscrypt-proxy-android_arm64-2.0.17.zip。如果不知道自己的手機系統,可以運行uname -a,如果出現aarch64就跟我一樣下載android_arm64版本就行。下載後解壓到/data/data/com.termux/files/home/opt/dns目錄里。複製一份文件夾里的配置文件即cp example-dnscrypt-proxy.toml dnscrypt-proxy.toml就可以了,配置也可以參考Fedora使用DNSCrypt。下面的文件我是放在家目錄下的,文件名是dns.sh,記得加上執行權限。

remove127(){
  /system/bin/iptables -t nat -D OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -D OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -D OUTPUT -d 114.114.114.114 -p tcp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -D OUTPUT -d 114.114.114.114 -p udp --dport 53 -j DNAT --to-destination 114.114.114.114:53
}
add127(){
  /system/bin/iptables -t nat -I OUTPUT -d 114.114.114.114 -p tcp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -I OUTPUT -d 114.114.114.114 -p udp --dport 53 -j DNAT --to-destination 114.114.114.114:53&&/system/bin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53&&/system/bin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:53
}
startDns(){
  /data/data/com.termux/files/home/opt/dns/dnscrypt-proxy -config /data/data/com.termux/files/home/opt/dns/dnscrypt-proxy.toml > /dev/null 2>1 &
  echo "starting dns"
  sleep 15
}
stopDns(){
  pkill dnscrypt-proxy
  echo "killed dns"
}
test(){
  var1=$(su -c "/system/bin/iptables -C OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:53")
}
status(){
  vt1="$(ps -ef | grep dnscrypt-proxy | wc -l)"
  if [[ $vt1 = "2" ]]; then
    dnsproxyStatus="1"
  else
    dnsproxyStatus="0"
  fi
  vt2="$(/system/bin/iptables -t nat -L | grep 127.0.0.1:53 | wc -l)"
  if [[ $vt2 = "2" ]]; then
    iptablesStatus="1"
  else
    iptablesStatus="0"
  fi
}
if [[ $1 = "1" ]]; then
  echo "start"      
  remove127              
  sleep 5                
  startDns       
  add127
elif [[ $1 = "2" ]]; then
  echo "restart"        
  remove127
  stopDns         
  sleep 3           
  startDns               
  add127 
elif [[ $1 = "0" ]]; then
  echo "stop"
  remove127
  stopDns
elif [[ $1 = "s" ]]; then
  status
  echo "dns: $dnsproxyStatus | iptables: $iptablesStatus"
else
  status
  if [[ $dnsproxyStatus = "1" ]]; then
    echo "$(date) dns ok" >> /data/data/com.termux/files/home/log.log
  else
    remove127
    stopDns
    remove127
    sleep 5
    startDns
    add127
    echo "$(date) dns started" >> /data/data/com.termux/files/home/log.log
  fi
  
fi
#使用說明
#首先切換的超級用戶
su
#查看當前狀態,1代表開啟,0代表關閉
./dns.sh s
#關閉dnscrypt-proxy
./dns.sh 0
#開啟dnscrypt-proxy
./dns.sh 1
#重新啟動dnscrypt-proxy
./dns.sh 2

啟動完成後可以通過dnsleaktest.com來查看當前的本機的dns。本來腳本還做了自啟動的適配,但是無論是Termux的自動啟動還是Magisk的自動啟動,都不太好用就算了。

本文更新於 2019/08/08。