├── readjson.py ├── LICENSE ├── README.md ├── boshdata.py ├── boshencode.py ├── index.html ├── js ├── encode.js ├── decode.js └── index.js ├── .gitignore ├── boshdecode.py └── data.json /readjson.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Author: Admin 3 | # @Date: 2019-11-01 16:52:34 4 | # @Last Modified by: Admin 5 | # @Last Modified time: 2019-11-01 18:18:14 6 | def readJSON(fileName=""): 7 | import json 8 | if fileName != '': 9 | strList = fileName.split(".") 10 | if strList[len(strList)-1].lower() == "json": 11 | with open(fileName, mode='r', encoding="utf-8") as file: 12 | return json.loads(file.read()) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2012 Romain Lespinasse 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BoshMessenger 2 | 废话信使,能够用废话承载二进制数据。 3 | 感谢 [menzi11/BullshitGenerator](https://github.com/menzi11/BullshitGenerator) 4 | 5 | ## Usage 6 | `boshencode.py` 和 `boshdecode.py` 都从标准输入 `stdin` 读取数据、从标准输出 `stdout` 输出结果。 7 | 8 | ``` 9 | # 原文件: original.bin 10 | 11 | # 编码 12 | python3 boshencode.py < original.bin > bosh.txt 13 | 14 | # 解码 15 | python3 boshdecode.py < bosh.txt > decoded.bin 16 | ``` 17 | 18 | ## Note 19 | 目前,废话信使 _BoshMessenger_ 一次只能够传递 32767 字节的信息。 20 | 21 | 注意,废话信使的放大倍数(编码后文件大小 / 原始文件大小)很大。 22 | 23 | | 编码 | 放大倍数 | 24 | | -------- | ------- | 25 | | UTF-8 | 100 | 26 | | GBK | 65 | 27 | 28 | 这可是真的废话 29 | -------------------------------------------------------------------------------- /boshdata.py: -------------------------------------------------------------------------------- 1 | import readjson 2 | 3 | FORTUNE_CAP = 4096 # 2 ** 12 4 | 5 | datafile = "data.json" 6 | data = readjson.readJSON(datafile) 7 | 8 | fortune_skel = data["famous"] 9 | before = data["before"] 10 | after = data["after"] 11 | 12 | bosh = data["bosh"] 13 | fortunes = [] 14 | 15 | # Bake fortunes 16 | for af in after: 17 | for bef in before: 18 | for forts in fortune_skel: 19 | # Fortune skeleton goes like: "Mark Twain a, blah blah blah, b" 20 | baked = forts.replace("a", bef).replace("b", af) 21 | fortunes.append(baked) 22 | 23 | # Avoid changes 24 | fortunes = tuple(fortunes[:FORTUNE_CAP]) 25 | 26 | def set_topic(topic): 27 | global bosh 28 | bosh = [ s.replace("x", topic) for s in bosh ] 29 | 30 | # TODO: Support user-defined topic 31 | set_topic("这件事") 32 | 33 | if __name__ == "__main__": 34 | # Test 35 | print("\n".join(fortunes)) 36 | -------------------------------------------------------------------------------- /boshencode.py: -------------------------------------------------------------------------------- 1 | import boshdata 2 | import struct 3 | import random 4 | import io 5 | 6 | BFCAP = 12 7 | BFMASK = (2 ** BFCAP) - 1 8 | 9 | def byte2_pack(ibytes): 10 | "little-endian! returns a tuple like (11, 4095)" 11 | ibytes += b"\0\0" 12 | # Packing unsigned short int, should be 2-byte 13 | num = struct.unpack(b"> 4) 16 | return (ret1, ret2) 17 | 18 | def byte2_encode(ibytes): 19 | rets = byte2_pack(ibytes) 20 | return boshdata.bosh[rets[0]] + boshdata.fortunes[rets[1]] 21 | 22 | def gen_length_indicator(length): 23 | "The length indicator is in little-endian." 24 | if length >= 2**(16): 25 | raise ValueError("Too long! Ensure your data is shorter than " + str(2**(BCAP*2))) 26 | sps = byte2_pack(struct.pack(" 175 and random.random() < 0.07) or len(tmp) > 416: 38 | iobuffer.write(tmp) 39 | iobuffer.write("\n") 40 | tmp = "" 41 | 42 | if tmp: 43 | iobuffer.write(tmp) 44 | iobuffer.write("\n") 45 | iobuffer.seek(0) 46 | return iobuffer.read() 47 | 48 | if __name__ == "__main__": 49 | import sys 50 | desc_stdin = sys.stdin.fileno() 51 | st_in = open(desc_stdin, "rb", closefd=False).read() 52 | sys.stdout.write(bytes_encode(st_in)) 53 | 54 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BoshMessenger 6 | 33 | 34 | 35 |
36 |

The Bosh Messenger in UTF-8

37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 |
50 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /js/encode.js: -------------------------------------------------------------------------------- 1 | function forceEncodeURI(text) 2 | { 3 | var ret = ""; 4 | for (i in text) 5 | { 6 | var chcode = text.charCodeAt(i); 7 | if (chcode < 128) 8 | { 9 | var ts = chcode.toString(16); 10 | ts = "00".substr(0, 2-ts.length) + ts; 11 | ret += "%" + ts; 12 | } 13 | else 14 | { 15 | ret += encodeURI(text[i]); 16 | } 17 | } 18 | return ret; 19 | } 20 | 21 | function byte2_encode(inum) 22 | { 23 | lpart = inum & 0x0F; 24 | hpart = inum >> 4; 25 | return bosh[lpart] + fortunes[hpart]; 26 | } 27 | 28 | function gen_length_indicator(length) 29 | { 30 | if (length >= 65536) 31 | { 32 | throw "Too large!"; 33 | } 34 | return byte2_encode(length); 35 | } 36 | 37 | function boshEncode(text) 38 | { 39 | var URIFormat = forceEncodeURI(text); 40 | var ret = "  ", tmp = ""; 41 | ret += gen_length_indicator(URIFormat.length / 3); 42 | 43 | // Prevent Index-out-of-Range 44 | URIFormat += "%00%00" 45 | while (URIFormat.length >= 6) 46 | { 47 | var unit = URIFormat.substr(0, 6).replace(/%/g, ""); 48 | // after parseInt, newbytes are BIG-endian 49 | // what we're expecting are LITTIE-endian ones 50 | var newbytes = parseInt(unit, 16); 51 | newbytes = ((newbytes & 0xFF) << 8) | (newbytes >> 8); 52 | 53 | var newtext = byte2_encode(newbytes); 54 | 55 | tmp += newtext; 56 | if ((tmp.length > 175 && Math.random() < 0.07) || tmp.length > 416) 57 | { 58 | ret += tmp; 59 | // full-width spaces 60 | ret += '\n'; 61 | tmp = "  "; 62 | } 63 | URIFormat = URIFormat.substr(6); 64 | } 65 | if (tmp.length > 2) // if not empty 66 | { 67 | ret += tmp; 68 | } 69 | return ret; 70 | } 71 | -------------------------------------------------------------------------------- /js/decode.js: -------------------------------------------------------------------------------- 1 | function byte2_decode(text) 2 | { 3 | // returns URIComponent like "%AA%BB" 4 | var component, truncated = 0; 5 | var lpart = -1, hpart = -1; 6 | text = text.trim(); 7 | 8 | // Bosh comes first 9 | for (lpart in bosh) 10 | { 11 | var pattern = bosh[lpart]; 12 | if (text.startsWith(pattern)) 13 | { 14 | truncated += pattern.length; 15 | text = text.substr(pattern.length); 16 | break; 17 | } 18 | } 19 | // Fortunes 20 | for (hpart in fortunes) 21 | { 22 | var pattern = fortunes[hpart]; 23 | if (text.startsWith(pattern)) 24 | { 25 | truncated += pattern.length; 26 | text = text.substr(pattern.length); 27 | break; 28 | } 29 | } 30 | //console.log(lpart + " " + hpart); 31 | var combined = (hpart << 4) | lpart; 32 | var hexcomb = combined.toString(16); 33 | hexcomb = "0000".substr(0, 4-hexcomb.length) + hexcomb; 34 | component = "%" + hexcomb.substr(0, 2) + "%" + hexcomb.substr(2, 2); 35 | 36 | return [component, truncated]; 37 | } 38 | 39 | function decode_length_indicator(text) 40 | { 41 | var comp_trunc = byte2_decode(text); 42 | var length = parseInt(comp_trunc[0].replace(/%/g, ""), 16); 43 | return [length, comp_trunc[1] ]; 44 | } 45 | 46 | function boshDecode(text) 47 | { 48 | // trim() cannot remove full-width spaces 49 | text = text.replace(/[ ]/g, "").replace(/[ ]/g, ""); 50 | var length_trunc = decode_length_indicator(text); 51 | var datalen = length_trunc[0]; 52 | var decodedURI = "" 53 | text = text.substr(length_trunc[1]); 54 | 55 | while (text.length > 0 && decodedURI.length < datalen * 3) 56 | { 57 | text = text.trim(); 58 | var comp_trunc = byte2_decode(text); 59 | // got BIG-endian, transform into LITTLE-endian 60 | decodedURI += comp_trunc[0].substr(3, 3) + comp_trunc[0].substr(0, 3); 61 | text = text.substr(comp_trunc[1]); 62 | } 63 | return decodedURI.substr(0, datalen*3); 64 | } 65 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | // BoshMessenger, Licensed under WTFPL 2 | // Author: UMRnInside 3 | 4 | // will be array 5 | fortunes = null; 6 | bosh = null; 7 | 8 | function loadData() 9 | { 10 | var req = new XMLHttpRequest(); 11 | req.open('GET', './data.json'); 12 | 13 | req.onload = function() { 14 | if (req.status == 200) { 15 | //console.log(req.responseText); 16 | var jsonobj = JSON.parse(req.responseText); 17 | var af, bf, fort; 18 | fortunes = new Array(); 19 | 20 | for (af in jsonobj.after) 21 | { 22 | for (bf in jsonobj.before) 23 | { 24 | for (fort in jsonobj.famous) 25 | { 26 | var as = jsonobj.after[af]; 27 | var bs = jsonobj.before[bf]; 28 | var fs = jsonobj.famous[fort]; 29 | var tstr = fs.replace(/a/, bs).replace(/b/, as); 30 | fortunes.push(tstr.replace(/x/g, "这件事")); 31 | } 32 | } 33 | } 34 | 35 | bosh = new Array(); 36 | for (b in jsonobj.bosh) 37 | { 38 | var text = jsonobj.bosh[b].replace(/x/g, "这件事"); 39 | bosh.push(text); 40 | } 41 | } else { 42 | console.log("Failed"); 43 | } 44 | }; 45 | 46 | // Handle network errors 47 | req.onerror = function() { 48 | console.log("Network Error"); 49 | }; 50 | 51 | // Make the request 52 | req.send(); 53 | } 54 | 55 | loadData(); 56 | 57 | const decodedArea = document.getElementById('decoded-area'); 58 | const encodedArea = document.getElementById('encoded-area'); 59 | const decodeBtn = document.getElementById('decode-btn'); 60 | const encodeBtn = document.getElementById('encode-btn'); 61 | 62 | encodeBtn.addEventListener('click', e=>{ 63 | encodedArea.value = ''; 64 | const encoded = boshEncode(decodedArea.value); 65 | encodedArea.value = encoded; 66 | }); 67 | 68 | decodeBtn.addEventListener('click', e=>{ 69 | decodedArea.value = ''; 70 | const decoded = boshDecode(encodedArea.value); 71 | decodedArea.value = decodeURIComponent(decoded); 72 | }); 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /boshdecode.py: -------------------------------------------------------------------------------- 1 | import boshdata 2 | import struct 3 | import io 4 | 5 | BFCAP = 12 6 | BFMASK = (2 ** BFCAP) - 1 7 | 8 | def byte2_unpack(numtuple): 9 | "little-endian! receives a tuple like (11, 4095) and return corrosponding bytes" 10 | num = numtuple[0] | (numtuple[1] << 4) 11 | # Packing unsigned short int, should be 2-byte 12 | return struct.pack(b"