分類
程序

python使用pyftpdlib實現ftp服務

需要使用ftp分享文件,記得很久之前折騰過ftp服務的搭建,賬號權限設置都挺複雜,這次使用python和pyftpdlib實現,幾分鐘就搞定啦。

#安裝pyftpdlib,當然還是推薦在虛擬環境裡裝
pip install pyftpdlib

然後新建一個python文件myFtp.py,內容如下:

import os

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

def main():
    # Instantiate a dummy authorizer for managing 'virtual' users
    authorizer = DummyAuthorizer()

    # 第一行新建一個具有讀寫權限的用戶(用戶名,密碼,ftp文件夾)
    # 第二行新建匿名讀取賬戶
    authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
    authorizer.add_anonymous(os.getcwd())

    # Instantiate FTP handler class
    handler = FTPHandler
    handler.authorizer = authorizer

    # 設置一個個性化歡迎語 (客戶端來連接的時候會返回給它)
    handler.banner = "基於pyftpdlib的ftp服務已準備好"

    # Specify a masquerade address and the range of ports to use for
    # passive connections.  Decomment in case you're behind a NAT.
    #handler.masquerade_address = '151.25.42.11'
    #handler.passive_ports = range(60000, 65535)

    # 監聽任意ip目標的2121端口,ftp標準端口是21
    address = ('', 2121)
    server = FTPServer(address, handler)

    # set a limit for connections
    server.max_cons = 256
    server.max_cons_per_ip = 5

    # start ftp server
    server.serve_forever()

if __name__ == '__main__':
    main()
#最後運行就可以了
python myFtp.py

其實這次也是走了彎路的,那就是先嘗試了Twisted,stackoverflow的網友說可以一行命令實現ftp服務。結果pip裝不上,需要下載了安裝包用

pip install 
裝。裝好後匿名用戶可以跑,設置了密碼就顯示密碼錯誤,搜索一番也沒找到辦法,於是使用了pyftpdlib。雖然實現ftp服務失敗了,但Twisted確實是一個很厲害的項目,有時間的話可以學習一下。 Twisted also supports many common network protocols, including SMTP, POP3, IMAP, SSHv2, and DNS.

分類
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。

分類
程序

幹掉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。

分類
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。

分類
程序

python查詢aqicn的空氣質量數據

#!/data/data/com.termux/files/usr/bin/python
# -*- coding: utf8 -*-

#aqicn空氣質量 #https://aqicn.org/api/
#v2 同時指定多個監測站,已防止單個站點很久不更新數據
import json,requests,sys,time

#token從https://aqicn.org/data-platform/token/獲取,只需郵箱即可
token = 'YourToken'
#預設為當前IP位置,可自行指定基站,幫助 --help
stationList = ["@5851","@5855","@5860"]

def getAqi(station):    
    aqiUrl = "https://api.waqi.info/feed/"+station+"/?token="+token
    try:
        res = requests.get(aqiUrl)
        a = res.json()
        if a['status']=="ok":            
            if time.time() - a['data']['time']['v'] < 86400:
                return a
            else:
                return {"status":a['data']['city']['name']+"的數據已過期。"}
        else:
            res = requests.get(aqiUrl)
            a = res.json()
            return a
    except:
        return {"status":"net error"}

def searchStation(keyword):
    searchUrl = "https://api.waqi.info/search/?token="+token+"&keyword="+keyword
    try:
        res = requests.get(searchUrl)
        a = res.json()
        res = []
        if a['status']=="ok":
            for s in a['data']:
                resT={}
                resT['uid']=s['uid']
                resT['name']=s['station']['name']
                resT['aqi']=s['aqi']+" ("+s['time']['stime']+")"
                res.append(resT)
            return res
        else:
            return a
    except:
        return {"status":"net error"}
def processAqi(aqiJson):
    s=aqiJson
#     print(json.dumps(s, indent=4))
    res={}
    if s['status']=="ok":
        res['cityName']=s['data']['city']['name']
        res['time']=s['data']['time']['s']
        res['aqi']=s['data']['aqi']
        try:
            res['pm25']=s['data']['iaqi']['pm25']['v']
        except:
            res['pm25']="N/A"
        try:
            res['pm10']=s['data']['iaqi']['pm10']['v']
        except:
            res['pm10']="N/A"
        try:
            res['temp']=s['data']['iaqi']['t']['v']
        except:
            res['temp']="N/A"
        try:
            res['humidity']=s['data']['iaqi']['h']['v']
        except:
            res['humidity']="N/A"
        try:
            res['wind']=s['data']['iaqi']['w']['v']
        except:
            res['wind']="N/A"
    else:
        res=s
    return res

def main():
    global station
    if len(sys.argv)==1:
        for station in stationList:
            res = processAqi(getAqi(station))
            print(json.dumps(res, indent=2, ensure_ascii=False))
    elif len(sys.argv)==2:
        station = str(sys.argv[1])
        if station == "--help":
            res={"@5851":"根據觀測點編號查詢","Nanyang":"根據城市名稱查詢",
                 "here":"根據IP查詢","s shenzhen":"查詢深圳觀測點",}
        else:
            res = processAqi(getAqi(station))
        print(json.dumps(res, indent=2, ensure_ascii=False))
    elif len(sys.argv)==3:
        res = searchStation(str(sys.argv[2]))
        print(json.dumps(res, indent=2, ensure_ascii=False))
    
if __name__ == '__main__':
    main()

前天迎來如秋後的第一場中度霧霾。

本文更新於 2018/11/08。

分類
程序

python守護tomcat

下面腳本實現定時檢測tomcat是否還活着,如果死了就啟動它,如果活太久了(24小時)就殺掉再啟動。

#!/data/pythons/p35/bin/python
# -*- coding: utf8 -*-

#定時任務 /etc/crontab
#*/5 * * * * root (/data/pythons/p36/bin/python /data/pythons/scripts/serviceDeamon.py tomcat-8780)

import psutil,time,datetime,subprocess,sys

def log42(logFile,logText):
    ts = int(time.time())
    dt = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
    with open("/tmp/"+dt[0:10]+logFile+".log", "a") as myfile:
        myfile.write(dt+" "+logText+"\n")
        
def startByCmdline(cmdlineKeyword): 
    command = 'export JAVA_HOME=/usr/local/java/jdk1.7.0_71;/bin/sh /data/web/'+cmdlineKeyword+'/bin/startup.sh'
    status,output = subprocess.getstatusoutput(command)
    log42('service'+cmdlineKeyword,"started"+ str(status))
    
def killByCmdline(cmdlineKeyword):    
    alive = False
    for proc in psutil.process_iter():
        try:
            pinfo = proc.as_dict(attrs=['pid', 'name','cmdline','create_time','ppid'])
        except psutil.NoSuchProcess:
            pass
        else:
            if any('/'+cmdlineKeyword in s for s in pinfo['cmdline']):
                alive = True
                aliveTime = time.time()-pinfo['create_time']
                if aliveTime > 60*60*24:        
                    log42('service'+cmdlineKeyword,"old enough "+str(aliveTime/60))
                    p = psutil.Process(pinfo['pid'])
                    p.kill()
                    time.sleep(3)
                    startByCmdline(cmdlineKeyword)
                else:
                    log42('service'+cmdlineKeyword,"too young "+str(aliveTime/60))
            else:
                pass
            
    if not alive :
        log42('service'+cmdlineKeyword,"died")
        startByCmdline(cmdlineKeyword)
                
if __name__ == '__main__':
    #'tomcat-8780'
    if str(sys.argv[1]).__contains__('tomcat') :
        killByCmdline(str(sys.argv[1]))
    else:
        log42('service',"wrong param")
        print('param like:tomcat-8780')
分類
程序

在win平台處理自定義協議

在IOS上,Safari可以通過網頁跳轉到snssdk141://detail?id=123456789拉起指定應用並展示內容,但是Safari並不顯示此鏈接。通過添加註冊表和Python可以實現在PC上獲取到這個自定義的協議鏈接。

首先添加註冊表來監聽這種協議,保存下面文本為p.reg運行即可導入:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\snssdk141]
@="\"URL:My Protocol\""
"URL Protocol"=""

[HKEY_CLASSES_ROOT\snssdk141\shell]

[HKEY_CLASSES_ROOT\snssdk141\shell\open]

[HKEY_CLASSES_ROOT\snssdk141\shell\open\command]
@="\"C:\\Users\\42\\AppData\\Local\\Programs\\Python\\Python35\\python.exe\" \"F:\\scripts\\tt.py\" %1"


然後新建F:\\scripts\tt.py內容如下,把鏈接複製到剪切版:

###!C:\Users\42\AppData\Local\Programs\Python\Python35
# coding:utf-8
#snssdk141://detail/?id=123456789
import sys,pyperclip

if __name__ == '__main__':    
    if str(sys.argv[1]) :
        pyperclip.copy(sys.argv[1])
    else:
        pyperclip.copy('fail')

在火狐打開會自動跳轉的網頁,就會彈出選擇框,選擇python運行後就在剪切板里獲取到了snssdk141://detail/?id=123456789。

彎路:火狐並不能添加處理自定義協議的設置。Chrome 58版本在網絡面板可以看到snssdk141://detail/?id=123456789這個鏈接,但是新版Chrome、Chronium和Firefox ESR均無法顯示出網頁中試圖訪問自定義鏈接的請求。requests的作者新開發了個工具叫requests-html,可以支持js和用chrome渲染頁面,及跟蹤網頁跳轉,但是文檔似乎還不完整。

參考:Launching applications using custom browser protocols