├── .gitignore ├── Dockerfile ├── lib ├── iconv.sh ├── iconvx64.sh ├── example.lua ├── tinycurl.sh ├── tinycurlx64.sh ├── Makefile └── main.c ├── scripts ├── rime.lua └── lua │ ├── trigger.lua │ ├── baidu.lua │ ├── google.lua │ ├── sougou.lua │ └── json.lua ├── README.md ├── Makefile └── .github └── workflows └── build.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.dll 3 | *.dSYM 4 | *.tar.gz 5 | lib/curl* 6 | lib/lua-iconv* 7 | lib/libiconv* 8 | lib/librime-* 9 | lib/thirdparty 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | RUN apt-get update 3 | RUN apt-get install -qq ca-certificates --no-install-recommends 4 | RUN update-ca-certificates 5 | RUN apt-get install -qq git curl build-essential mingw-w64 unzip zip p7zip-full --no-install-recommends 6 | -------------------------------------------------------------------------------- /lib/iconv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export CC="i686-w64-mingw32-gcc" 3 | export CFLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables" 4 | export LDFLAGS="-Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections -static-libgcc" 5 | mkdir -p build32 && cd build32 && ../configure --host i686-w64-mingw32 --disable-pthreads --enable-static --disable-shared && make -j$(nproc) 6 | -------------------------------------------------------------------------------- /lib/iconvx64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export CC="x86_64-w64-mingw32-gcc" 3 | export CFLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables" 4 | export LDFLAGS="-Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections -static-libgcc" 5 | mkdir -p build64 && cd build64 && ../configure --host x86_64-w64-mingw32 --disable-pthreads --enable-static --disable-shared && make -j$(nproc) 6 | -------------------------------------------------------------------------------- /scripts/rime.lua: -------------------------------------------------------------------------------- 1 | 2 | --- 云拼音,Control+t 为云输入触发键 3 | --- 使用方法: 4 | --- 将 "lua_translator@cloud_pinyin_translator" 和 "lua_processor@cloud_pinyin_processor" 5 | --- 分别加到输入方案的 engine/translators 和 engine/processors 中 6 | local cloud_pinyin_provider = require("baidu") 7 | -- local cloud_pinyin_provider = require("google") 8 | -- local cloud_pinyin_provider = require("sougou") 9 | local cloud_pinyin = require("trigger")("Control+t", cloud_pinyin_provider) 10 | cloud_pinyin_translator = cloud_pinyin.translator 11 | cloud_pinyin_processor = cloud_pinyin.processor 12 | -------------------------------------------------------------------------------- /scripts/lua/trigger.lua: -------------------------------------------------------------------------------- 1 | local function make(trig_key, trig_translator) 2 | local flag = false 3 | 4 | local function processor(key, env) 5 | local kAccepted = 1 6 | local kNoop = 2 7 | local engine = env.engine 8 | local context = engine.context 9 | 10 | if key:repr() == trig_key then 11 | if context:is_composing() then 12 | flag = true 13 | context:refresh_non_confirmed_composition() 14 | return kAccepted 15 | end 16 | end 17 | 18 | return kNoop 19 | end 20 | 21 | local function translator(input, seg, env) 22 | if flag then 23 | flag = false 24 | trig_translator(input, seg, env) 25 | end 26 | end 27 | 28 | return { processor = processor, translator = translator } 29 | end 30 | 31 | return make 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # librime-cloud: RIME 云输入插件 2 | 3 | ## 获取 4 | 免责声明:下述二进制文件是使用 GitHub CI,引用公开获取的源代码和二进制程序自动生成的,仅供参考。 5 | 请了解运行未知来源二进制文件的风险。使用下述文件造成的后果请您自行承担,制作者不承担任何责任。 6 | 7 | [GitHub Release](https://github.com/hchunhui/librime-cloud/releases) 8 | 9 | 您也可以从 https://github.com/hchunhui/librime-cloud 获取编译脚本,从源码编译。 10 | 11 | ## 安装 12 | 1. 编译或获取压缩包 13 | 2. 解压 14 | - Windows 平台(小狼毫 >= 0.14.0) 15 | - 将 `out-mingw` 下所有文件复制到小狼毫的程序文件夹下 16 | - 将 `scripts` 下所有文件复制到小狼毫的用户目录下 17 | - Linux 平台(librime 需编译 lua 支持) 18 | - 将 `out-linux` 下所有文件复制到 `/usr/local/lib/lua/$LUAV` 下 19 | - 将 `scripts` 下所有文件复制到用户目录下 20 | - macOS 平台(小企鹅) 21 | - 将 `out-macos` 下所有文件复制到 `/usr/local/lib/lua/$LUAV` 下 22 | - 将 `scripts` 下所有文件复制到 `~/.local/share/fcitx5/rime` 下 23 | 3. 配置:见 `scripts/rime.lua` 中的注释 24 | 25 | ## 使用 26 | 默认情况下,在输入状态下按 `Control+t` 触发一次云输入,云候选前五项自动加到候选菜单最前方。 27 | -------------------------------------------------------------------------------- /scripts/lua/baidu.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local http = require("simplehttp") 3 | http.TIMEOUT = 0.5 4 | 5 | local function make_url(input, bg, ed) 6 | return 'https://olime.baidu.com/py?input=' .. input .. 7 | '&inputtype=py&bg='.. bg .. '&ed='.. ed .. 8 | '&result=hanzi&resultcoding=utf-8&ch_en=0&clientinfo=web&version=1' 9 | end 10 | 11 | local function translator(input, seg) 12 | local url = make_url(input, 0, 5) 13 | local reply = http.request(url) 14 | local _, j = pcall(json.decode, reply) 15 | if j.status == "T" and j.result and j.result[1] then 16 | for i, v in ipairs(j.result[1]) do 17 | local c = Candidate("simple", seg.start, seg.start + v[2], v[1], "(百度云拼音)") 18 | c.quality = 2 19 | if string.gsub(v[3].pinyin, "'", "") == string.sub(input, 1, v[2]) then 20 | c.preedit = string.gsub(v[3].pinyin, "'", " ") 21 | end 22 | yield(c) 23 | end 24 | end 25 | end 26 | 27 | return translator 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: linux win32 2 | 3 | linux: 4 | cd lib && make clean simplehttp.so iconv.so 5 | rm -rf out-linux && mkdir out-linux && cp lib/simplehttp.so lib/iconv.so out-linux 6 | tar czvf linux-`uname -m`-lua5.4.tar.gz out-linux scripts README.md 7 | 8 | win32: 9 | cd lib && make clean simplehttp.dll simplehttpx64.dll iconv.dll iconvx64.dll 10 | rm -rf out-mingw && mkdir out-mingw && cp lib/simplehttp.dll lib/iconv.dll out-mingw 11 | rm -rf out-mingw64 && mkdir out-mingw64 && cp lib/simplehttpx64.dll out-mingw64/simplehttp.dll && cp lib/iconvx64.dll out-mingw64/iconv.dll 12 | zip -r win32-lua5.4.zip out-mingw out-mingw64 scripts README.md 13 | 14 | macos: 15 | cd lib && make PLAT=macos clean simplehttp.so iconv.so 16 | rm -rf out-macos && mkdir out-macos && cp lib/simplehttp.so lib/iconv.so out-macos 17 | tar czvf macos-`uname -m`-lua5.4.tar.gz out-macos scripts README.md 18 | 19 | clean: 20 | rm -rf out-mingw out-linux out-macos linux-*.tar.gz win32-*.zipa macos-*.tar.gz 21 | 22 | purge: clean 23 | cd lib && make purge -------------------------------------------------------------------------------- /lib/example.lua: -------------------------------------------------------------------------------- 1 | local http = require("simplehttp") 2 | http.TIMEOUT = 1.5 -- set timeout in secs 3 | 4 | -- simple form 5 | --[[ 6 | response, code = http.request(url [, body]) 7 | ]] 8 | -- GET 9 | local r, c = http.request("https://www.baidu.com") 10 | assert(c == 200) 11 | print(r) 12 | -- POST 13 | local r, c = http.request("https://postman-echo.com/post", "test") 14 | assert(c == 200) 15 | print(r) 16 | 17 | -- advanced form 18 | --[[ 19 | response, code, headers = http.request{ 20 | url = string, 21 | [method = string,] 22 | [headers = header-table,] 23 | [data = string,] 24 | } 25 | ]] 26 | local r, c, h = http.request{url = "https://www.baidu.com"} 27 | assert(c == 200) 28 | for k, v in pairs(h) do 29 | print(k, v) 30 | end 31 | 32 | local r, c, h = http.request{ 33 | url = "https://postman-echo.com/post", 34 | method = "POST", 35 | headers = { 36 | ["foo"] = "42", 37 | ["bar"] = "baz", 38 | }, 39 | data = "test", 40 | } 41 | assert(c == 200) 42 | -------------------------------------------------------------------------------- /lib/tinycurl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export CC="i686-w64-mingw32-gcc" 3 | export CFLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables" 4 | export LDFLAGS="-Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections -static-libgcc" 5 | mkdir -p build32 && cd build32 && ../configure --host i686-w64-mingw32 \ 6 | --disable-pthreads \ 7 | --enable-static \ 8 | --disable-shared \ 9 | --disable-cookies \ 10 | --disable-dict \ 11 | --disable-file \ 12 | --disable-ftp \ 13 | --disable-gopher \ 14 | --disable-imap \ 15 | --disable-ldap \ 16 | --disable-mqtt \ 17 | --disable-pop3 \ 18 | --disable-proxy \ 19 | --disable-rtsp \ 20 | --disable-smtp \ 21 | --disable-telnet \ 22 | --disable-tftp \ 23 | --disable-unix-sockets \ 24 | --disable-verbose \ 25 | --disable-versioned-symbols \ 26 | --disable-http-auth \ 27 | --disable-doh \ 28 | --disable-mime \ 29 | --disable-dateparse \ 30 | --disable-netrc \ 31 | --disable-dnsshuffle \ 32 | --disable-progress-meter \ 33 | --enable-maintainer-mode \ 34 | --enable-werror \ 35 | --without-brotli \ 36 | --without-gssapi \ 37 | --without-libidn2 \ 38 | --without-libpsl \ 39 | --without-librtmp \ 40 | --without-libssh2 \ 41 | --without-nghttp2 \ 42 | --without-zlib \ 43 | --without-zstd \ 44 | --with-schannel \ 45 | && make -j$(nproc) 46 | -------------------------------------------------------------------------------- /lib/tinycurlx64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export CC="x86_64-w64-mingw32-gcc" 3 | export CFLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables" 4 | export LDFLAGS="-Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections -static-libgcc" 5 | mkdir -p build64 && cd build64 && ../configure --host x86_64-w64-mingw32 \ 6 | --disable-pthreads \ 7 | --enable-static \ 8 | --disable-shared \ 9 | --disable-cookies \ 10 | --disable-dict \ 11 | --disable-file \ 12 | --disable-ftp \ 13 | --disable-gopher \ 14 | --disable-imap \ 15 | --disable-ldap \ 16 | --disable-mqtt \ 17 | --disable-pop3 \ 18 | --disable-proxy \ 19 | --disable-rtsp \ 20 | --disable-smtp \ 21 | --disable-telnet \ 22 | --disable-tftp \ 23 | --disable-unix-sockets \ 24 | --disable-verbose \ 25 | --disable-versioned-symbols \ 26 | --disable-http-auth \ 27 | --disable-doh \ 28 | --disable-mime \ 29 | --disable-dateparse \ 30 | --disable-netrc \ 31 | --disable-dnsshuffle \ 32 | --disable-progress-meter \ 33 | --enable-maintainer-mode \ 34 | --enable-werror \ 35 | --without-brotli \ 36 | --without-gssapi \ 37 | --without-libidn2 \ 38 | --without-libpsl \ 39 | --without-librtmp \ 40 | --without-libssh2 \ 41 | --without-nghttp2 \ 42 | --without-zlib \ 43 | --without-zstd \ 44 | --with-schannel \ 45 | && make -j$(nproc) 46 | -------------------------------------------------------------------------------- /scripts/lua/google.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local http = require("simplehttp") 3 | http.TIMEOUT = 1.5 4 | 5 | local function make_url(input, num) 6 | return 'https://inputtools.google.com/request?text=' .. 7 | input .. '&itc=zh-t-i0-pinyin&num=' .. num .. '&cp=0&cs=1&ie=utf-8&oe=utf-8' 8 | end 9 | 10 | local function translator(input, seg) 11 | local url = make_url(input, 5) 12 | local reply = http.request(url) 13 | local _, j = pcall(json.decode, reply) 14 | if j[1] == 'SUCCESS' and j[2] and j[2][1] then 15 | for i, v in ipairs(j[2][1][2]) do 16 | local matched_length = j[2][1][4].matched_length 17 | if matched_length ~= nil then 18 | matched_length = matched_length[i] 19 | else 20 | matched_length = string.len(j[2][1][1]) 21 | end 22 | local annotation = j[2][1][4].annotation[i] 23 | local c = Candidate("simple", seg.start, seg.start + matched_length, v, "(谷歌云拼音)") 24 | c.quality = 2 25 | if string.gsub(annotation, " ", "") == string.sub(input, 1, matched_length) then 26 | c.preedit = annotation 27 | end 28 | yield(c) 29 | end 30 | end 31 | end 32 | 33 | return translator 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | os: [linux, win32] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: build docker image 21 | run: docker build -t build . 22 | 23 | - name: prepare 24 | run: cd lib && make prepare && cd .. 25 | 26 | - name: build in docker image 27 | run: docker run -v $PWD:/work --rm build bash -c "cd /work && make ${{ matrix.os }}" 28 | 29 | - name: upload artifacts 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: ${{ matrix.os }} 33 | path: | 34 | ${{ matrix.os }}*.zip 35 | ${{ matrix.os }}*.tar.gz 36 | 37 | build_macos: 38 | runs-on: ${{ matrix.os }} 39 | strategy: 40 | matrix: 41 | os: [macos-14, macos-15-intel] 42 | 43 | steps: 44 | - uses: actions/checkout@v3 45 | 46 | - name: build 47 | run: make macos 48 | 49 | - name: upload artifacts 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: ${{ matrix.os }} 53 | path: macos*.tar.gz 54 | 55 | release: 56 | needs: [ build , build_macos ] 57 | runs-on: ubuntu-latest 58 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/master') 59 | steps: 60 | - name: download artifacts 61 | uses: actions/download-artifact@v4 62 | with: 63 | path: artifact 64 | merge-multiple: true 65 | 66 | - name: release 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | run: wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh && bash upload.sh artifact/* 70 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CC_mingw = i686-w64-mingw32-gcc 3 | CC_mingw64 = x86_64-w64-mingw32-gcc 4 | 5 | LUAV ?= 5.4 6 | LUAINC = thirdparty/lua${LUAV} 7 | LUALIB = -Llibrime-lua${LUAV}/dist/lib -lrime 8 | LUALIB64 = -Llibrime-lua${LUAV}-x64/dist/lib -lrime 9 | 10 | CURLV ?= 8.11.1 11 | CURLINC = curl-${CURLV}/include 12 | CURLLIB = -Lcurl-${CURLV}/build32/lib/.libs -lcurl -lws2_32 -lcrypt32 -lbcrypt 13 | CURLLIB64 = -Lcurl-${CURLV}/build64/lib/.libs -lcurl -lws2_32 -lcrypt32 -lbcrypt 14 | 15 | ICONV_V ?= 1.18 16 | LUA_ICONV_V ?= 7.1.0 17 | ICONV_INC = libiconv-${ICONV_V}/build32/include 18 | ICONV_INC64 = libiconv-${ICONV_V}/build64/include 19 | ICONV_LIB = -Llibiconv-${ICONV_V}/build32/lib/.libs -liconv 20 | ICONV_LIB64 = -Llibiconv-${ICONV_V}/build64/lib/.libs -liconv 21 | 22 | PLAT ?= linux 23 | LDFLAGS_linux = -Wl,--gc-sections -Wl,-s 24 | LDFLAGS_macos = -Wl,-bundle -Wl,-undefined,dynamic_lookup 25 | LDFLAGS = ${LDFLAGS_${PLAT}} 26 | 27 | all: simplehttp.so simplehttp.dll simplehttpx64.dll iconv.so iconv.dll iconvx64.dll 28 | 29 | librime-lua5.4: 30 | curl -L -O https://github.com/rime/librime/releases/download/1.7.3/rime-with-plugins-1.7.3-win32.zip 31 | unzip rime-with-plugins-1.7.3-win32.zip -d librime-lua5.4 32 | 33 | librime-lua5.4-x64: 34 | curl -L -O https://github.com/rime/librime/releases/download/1.11.0/rime-76a0a16-Windows-msvc-x64.7z 35 | 7z x -olibrime-lua5.4-x64 rime-76a0a16-Windows-msvc-x64.7z 36 | 37 | thirdparty: 38 | git clone -b thirdparty http://github.com/hchunhui/librime-lua.git thirdparty 39 | 40 | curl-${CURLV}: 41 | curl -L -O https://curl.se/download/curl-${CURLV}.tar.gz 42 | tar xvf curl-${CURLV}.tar.gz 43 | 44 | libiconv-${ICONV_V}: 45 | curl -L -O https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${ICONV_V}.tar.gz 46 | tar xvf libiconv-${ICONV_V}.tar.gz 47 | 48 | lua-iconv-${LUA_ICONV_V}: 49 | git clone -b v${LUA_ICONV_V} https://github.com/lunarmodules/lua-iconv.git lua-iconv-${LUA_ICONV_V} 50 | 51 | prepare: librime-lua5.4 librime-lua5.4-x64 thirdparty curl-${CURLV} lua-iconv-${LUA_ICONV_V} 52 | 53 | curl-${CURLV}/build32/lib/.libs: curl-${CURLV} 54 | cd curl-${CURLV} && sh ../tinycurl.sh 55 | 56 | curl-${CURLV}/build64/lib/.libs: curl-${CURLV} 57 | cd curl-${CURLV} && sh ../tinycurlx64.sh 58 | 59 | libiconv-${ICONV_V}/build32/lib/.libs: libiconv-${ICONV_V} 60 | cd libiconv-${ICONV_V} && sh ../iconv.sh 61 | 62 | libiconv-${ICONV_V}/build64/lib/.libs: libiconv-${ICONV_V} 63 | cd libiconv-${ICONV_V} && sh ../iconvx64.sh 64 | 65 | simplehttp.so: main.c thirdparty curl-${CURLV} 66 | ${CC} -I${LUAINC} -I${CURLINC} -Os -fPIC -shared ${LDFLAGS} -o $@ $< 67 | 68 | simplehttp.dll: main.c librime-lua${LUAV} thirdparty curl-${CURLV}/build32/lib/.libs 69 | ${CC_mingw} -I${LUAINC} -I${CURLINC} -Os -shared -o $@ $< -Wl,--gc-sections -Wl,-s -static-libgcc ${LUALIB} ${CURLLIB} 70 | 71 | simplehttpx64.dll: main.c librime-lua${LUAV}-x64 thirdparty curl-${CURLV}/build64/lib/.libs 72 | ${CC_mingw64} -I${LUAINC} -I${CURLINC} -Os -shared -o $@ $< -Wl,--gc-sections -Wl,-s -static-libgcc ${LUALIB64} ${CURLLIB64} 73 | 74 | lua-iconv-${LUA_ICONV_V}/luaiconv.c: lua-iconv-${LUA_ICONV_V} 75 | 76 | iconv.so: lua-iconv-${LUA_ICONV_V}/luaiconv.c 77 | ${CC} -I${LUAINC} -Os -fPIC -shared ${LDFLAGS} -o $@ $< 78 | 79 | iconv.dll: lua-iconv-${LUA_ICONV_V}/luaiconv.c librime-lua${LUAV} libiconv-${ICONV_V}/build32/lib/.libs 80 | ${CC_mingw} -I${LUAINC} -I${ICONV_INC} -Os -shared -o $@ $< -Wl,--gc-sections -Wl,-s -static-libgcc ${LUALIB} ${ICONV_LIB} 81 | 82 | iconvx64.dll: lua-iconv-${LUA_ICONV_V}/luaiconv.c librime-lua${LUAV}-x64 libiconv-${ICONV_V}/build64/lib/.libs 83 | ${CC_mingw64} -I${LUAINC} -I${ICONV_INC64} -Os -shared -o $@ $< -Wl,--gc-sections -Wl,-s -static-libgcc ${LUALIB64} ${ICONV_LIB64} 84 | 85 | clean: 86 | rm -f simplehttp.so simplehttp.dll simplehttpx64.dll iconv.so iconv.dll iconvx64.dll 87 | 88 | purge: clean 89 | rm -rf lua-iconv-* curl-* libiconv-* librime-* thirdparty 90 | rm -rf *.zip *.tar.gz *.7z 91 | -------------------------------------------------------------------------------- /scripts/lua/sougou.lua: -------------------------------------------------------------------------------- 1 | local http = require("simplehttp") 2 | local iconv = require("iconv") 3 | 4 | -- Algorithm from https://github.com/wanghuafeng/common_utils/blob/master/rong_tools/sogou_cloud_words.py 5 | 6 | function rc(x) 7 | local start = 0 8 | for i = 1, #x do 9 | start = start ~ string.byte(x, i) 10 | end 11 | return string.char(start) 12 | end 13 | 14 | function serial_keys(keys) 15 | local token = "\0\5\0\0\0\0\1" 16 | local total_len = #token + #keys + 3 17 | local data = string.char(total_len) .. token .. string.char(#keys) .. keys 18 | return data .. rc(data) 19 | end 20 | 21 | function key_from_serial(data) 22 | local token = "\0\5\0\0\0\0\1" 23 | local total_len = string.byte(data, 1) 24 | local key_len = total_len - #token - 3 25 | return string.sub(data, -key_len - 1, -2) 26 | end 27 | 28 | function open_sogou(keys, durtot, version) 29 | durtot = durtot or 0 30 | version = version or "3.7" 31 | local url = string.format( 32 | "http://shouji.sogou.com/web_ime/mobile.php?durtot=%d&h=000000000000000&r=store_mf_wandoujia&v=%s", durtot, 33 | version) 34 | local data = serial_keys(keys) 35 | return http.request(url, data) 36 | end 37 | 38 | function parse_result(result) 39 | local words = {} 40 | 41 | if string.byte(result, 1) + 2 ~= #result then 42 | log.error("[sougou] invalid size, expected", string.byte(result, 1) + 2, "got", #result) 43 | return words 44 | end 45 | 46 | local num_words = string.unpack(" 32 then 48 | log.warning("[sougou] strange words num", num_words) 49 | end 50 | 51 | local pos = 0x14 -- data packet starts at 0x14 52 | 53 | for i = 1, num_words do 54 | local str_len = string.unpack(" 0xFF then 56 | log.error("[sougou] Invalid string length") 57 | end 58 | pos = pos + 2 59 | 60 | if str_len == 0 then 61 | -- Skip empty string 62 | else 63 | local word = string.sub(result, pos + 1, pos + str_len) 64 | local cd, err = iconv.new("utf-8", "utf-16le") 65 | if not cd then 66 | log.error(string.format("[sougou] word %s can't convert to utf-8: %s", word, err)) 67 | else 68 | word, err = cd:iconv(word) 69 | if not word then 70 | log.error(string.format("[sougou] word can't convert to utf-8: %s", err)) 71 | end 72 | end 73 | table.insert(words, word) 74 | end 75 | pos = pos + str_len 76 | 77 | -- unknown part, like 0x12c, 0x12b, etc. 78 | str_len = string.unpack(", status code: %d", keys, code)) 97 | return {} 98 | end 99 | 100 | return parse_result(resp) 101 | end 102 | 103 | local function translator(input, seg) 104 | local list = get_cloud_words(input) 105 | local yielded_candidates = 0 106 | local max_candidates = 5 107 | for i, v in ipairs(list) do 108 | if yielded_candidates >= max_candidates then 109 | break 110 | end 111 | 112 | local c = Candidate("simple", seg.start, seg._end, v, "(搜狗云拼音)") 113 | c.quality = 2 114 | yield(c) 115 | 116 | yielded_candidates = yielded_candidates + 1 117 | end 118 | end 119 | 120 | return translator 121 | -------------------------------------------------------------------------------- /scripts/lua/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2018 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.1" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | [ "\\" ] = "\\\\", 35 | [ "\"" ] = "\\\"", 36 | [ "\b" ] = "\\b", 37 | [ "\f" ] = "\\f", 38 | [ "\n" ] = "\\n", 39 | [ "\r" ] = "\\r", 40 | [ "\t" ] = "\\t", 41 | } 42 | 43 | local escape_char_map_inv = { [ "\\/" ] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | 49 | local function escape_char(c) 50 | return escape_char_map[c] or string.format("\\u%04x", c:byte()) 51 | end 52 | 53 | 54 | local function encode_nil(val) 55 | return "null" 56 | end 57 | 58 | 59 | local function encode_table(val, stack) 60 | local res = {} 61 | stack = stack or {} 62 | 63 | -- Circular reference? 64 | if stack[val] then error("circular reference") end 65 | 66 | stack[val] = true 67 | 68 | if val[1] ~= nil or next(val) == nil then 69 | -- Treat as array -- check keys are valid and it is not sparse 70 | local n = 0 71 | for k in pairs(val) do 72 | if type(k) ~= "number" then 73 | error("invalid table: mixed or invalid key types") 74 | end 75 | n = n + 1 76 | end 77 | if n ~= #val then 78 | error("invalid table: sparse array") 79 | end 80 | -- Encode 81 | for i, v in ipairs(val) do 82 | table.insert(res, encode(v, stack)) 83 | end 84 | stack[val] = nil 85 | return "[" .. table.concat(res, ",") .. "]" 86 | 87 | else 88 | -- Treat as an object 89 | for k, v in pairs(val) do 90 | if type(k) ~= "string" then 91 | error("invalid table: mixed or invalid key types") 92 | end 93 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 94 | end 95 | stack[val] = nil 96 | return "{" .. table.concat(res, ",") .. "}" 97 | end 98 | end 99 | 100 | 101 | local function encode_string(val) 102 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 103 | end 104 | 105 | 106 | local function encode_number(val) 107 | -- Check for NaN, -inf and inf 108 | if val ~= val or val <= -math.huge or val >= math.huge then 109 | error("unexpected number value '" .. tostring(val) .. "'") 110 | end 111 | return string.format("%.14g", val) 112 | end 113 | 114 | 115 | local type_func_map = { 116 | [ "nil" ] = encode_nil, 117 | [ "table" ] = encode_table, 118 | [ "string" ] = encode_string, 119 | [ "number" ] = encode_number, 120 | [ "boolean" ] = tostring, 121 | } 122 | 123 | 124 | encode = function(val, stack) 125 | local t = type(val) 126 | local f = type_func_map[t] 127 | if f then 128 | return f(val, stack) 129 | end 130 | error("unexpected type '" .. t .. "'") 131 | end 132 | 133 | 134 | function json.encode(val) 135 | return ( encode(val) ) 136 | end 137 | 138 | 139 | ------------------------------------------------------------------------------- 140 | -- Decode 141 | ------------------------------------------------------------------------------- 142 | 143 | local parse 144 | 145 | local function create_set(...) 146 | local res = {} 147 | for i = 1, select("#", ...) do 148 | res[ select(i, ...) ] = true 149 | end 150 | return res 151 | end 152 | 153 | local space_chars = create_set(" ", "\t", "\r", "\n") 154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 156 | local literals = create_set("true", "false", "null") 157 | 158 | local literal_map = { 159 | [ "true" ] = true, 160 | [ "false" ] = false, 161 | [ "null" ] = nil, 162 | } 163 | 164 | 165 | local function next_char(str, idx, set, negate) 166 | for i = idx, #str do 167 | if set[str:sub(i, i)] ~= negate then 168 | return i 169 | end 170 | end 171 | return #str + 1 172 | end 173 | 174 | 175 | local function decode_error(str, idx, msg) 176 | local line_count = 1 177 | local col_count = 1 178 | for i = 1, idx - 1 do 179 | col_count = col_count + 1 180 | if str:sub(i, i) == "\n" then 181 | line_count = line_count + 1 182 | col_count = 1 183 | end 184 | end 185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 186 | end 187 | 188 | 189 | local function codepoint_to_utf8(n) 190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 191 | local f = math.floor 192 | if n <= 0x7f then 193 | return string.char(n) 194 | elseif n <= 0x7ff then 195 | return string.char(f(n / 64) + 192, n % 64 + 128) 196 | elseif n <= 0xffff then 197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 198 | elseif n <= 0x10ffff then 199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 200 | f(n % 4096 / 64) + 128, n % 64 + 128) 201 | end 202 | error( string.format("invalid unicode codepoint '%x'", n) ) 203 | end 204 | 205 | 206 | local function parse_unicode_escape(s) 207 | local n1 = tonumber( s:sub(3, 6), 16 ) 208 | local n2 = tonumber( s:sub(9, 12), 16 ) 209 | -- Surrogate pair? 210 | if n2 then 211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 212 | else 213 | return codepoint_to_utf8(n1) 214 | end 215 | end 216 | 217 | 218 | local function parse_string(str, i) 219 | local has_unicode_escape = false 220 | local has_surrogate_escape = false 221 | local has_escape = false 222 | local last 223 | for j = i + 1, #str do 224 | local x = str:byte(j) 225 | 226 | if x < 32 then 227 | decode_error(str, j, "control character in string") 228 | end 229 | 230 | if last == 92 then -- "\\" (escape char) 231 | if x == 117 then -- "u" (unicode escape sequence) 232 | local hex = str:sub(j + 1, j + 5) 233 | if not hex:find("%x%x%x%x") then 234 | decode_error(str, j, "invalid unicode escape in string") 235 | end 236 | if hex:find("^[dD][89aAbB]") then 237 | has_surrogate_escape = true 238 | else 239 | has_unicode_escape = true 240 | end 241 | else 242 | local c = string.char(x) 243 | if not escape_chars[c] then 244 | decode_error(str, j, "invalid escape char '" .. c .. "' in string") 245 | end 246 | has_escape = true 247 | end 248 | last = nil 249 | 250 | elseif x == 34 then -- '"' (end of string) 251 | local s = str:sub(i + 1, j - 1) 252 | if has_surrogate_escape then 253 | s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) 254 | end 255 | if has_unicode_escape then 256 | s = s:gsub("\\u....", parse_unicode_escape) 257 | end 258 | if has_escape then 259 | s = s:gsub("\\.", escape_char_map_inv) 260 | end 261 | return s, j + 1 262 | 263 | else 264 | last = x 265 | end 266 | end 267 | decode_error(str, i, "expected closing quote for string") 268 | end 269 | 270 | 271 | local function parse_number(str, i) 272 | local x = next_char(str, i, delim_chars) 273 | local s = str:sub(i, x - 1) 274 | local n = tonumber(s) 275 | if not n then 276 | decode_error(str, i, "invalid number '" .. s .. "'") 277 | end 278 | return n, x 279 | end 280 | 281 | 282 | local function parse_literal(str, i) 283 | local x = next_char(str, i, delim_chars) 284 | local word = str:sub(i, x - 1) 285 | if not literals[word] then 286 | decode_error(str, i, "invalid literal '" .. word .. "'") 287 | end 288 | return literal_map[word], x 289 | end 290 | 291 | 292 | local function parse_array(str, i) 293 | local res = {} 294 | local n = 1 295 | i = i + 1 296 | while 1 do 297 | local x 298 | i = next_char(str, i, space_chars, true) 299 | -- Empty / end of array? 300 | if str:sub(i, i) == "]" then 301 | i = i + 1 302 | break 303 | end 304 | -- Read token 305 | x, i = parse(str, i) 306 | res[n] = x 307 | n = n + 1 308 | -- Next token 309 | i = next_char(str, i, space_chars, true) 310 | local chr = str:sub(i, i) 311 | i = i + 1 312 | if chr == "]" then break end 313 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 314 | end 315 | return res, i 316 | end 317 | 318 | 319 | local function parse_object(str, i) 320 | local res = {} 321 | i = i + 1 322 | while 1 do 323 | local key, val 324 | i = next_char(str, i, space_chars, true) 325 | -- Empty / end of object? 326 | if str:sub(i, i) == "}" then 327 | i = i + 1 328 | break 329 | end 330 | -- Read key 331 | if str:sub(i, i) ~= '"' then 332 | decode_error(str, i, "expected string for key") 333 | end 334 | key, i = parse(str, i) 335 | -- Read ':' delimiter 336 | i = next_char(str, i, space_chars, true) 337 | if str:sub(i, i) ~= ":" then 338 | decode_error(str, i, "expected ':' after key") 339 | end 340 | i = next_char(str, i + 1, space_chars, true) 341 | -- Read value 342 | val, i = parse(str, i) 343 | -- Set 344 | res[key] = val 345 | -- Next token 346 | i = next_char(str, i, space_chars, true) 347 | local chr = str:sub(i, i) 348 | i = i + 1 349 | if chr == "}" then break end 350 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 351 | end 352 | return res, i 353 | end 354 | 355 | 356 | local char_func_map = { 357 | [ '"' ] = parse_string, 358 | [ "0" ] = parse_number, 359 | [ "1" ] = parse_number, 360 | [ "2" ] = parse_number, 361 | [ "3" ] = parse_number, 362 | [ "4" ] = parse_number, 363 | [ "5" ] = parse_number, 364 | [ "6" ] = parse_number, 365 | [ "7" ] = parse_number, 366 | [ "8" ] = parse_number, 367 | [ "9" ] = parse_number, 368 | [ "-" ] = parse_number, 369 | [ "t" ] = parse_literal, 370 | [ "f" ] = parse_literal, 371 | [ "n" ] = parse_literal, 372 | [ "[" ] = parse_array, 373 | [ "{" ] = parse_object, 374 | } 375 | 376 | 377 | parse = function(str, idx) 378 | local chr = str:sub(idx, idx) 379 | local f = char_func_map[chr] 380 | if f then 381 | return f(str, idx) 382 | end 383 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 384 | end 385 | 386 | 387 | function json.decode(str) 388 | if type(str) ~= "string" then 389 | error("expected argument of type string, got " .. type(str)) 390 | end 391 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 392 | idx = next_char(str, idx, space_chars, true) 393 | if idx <= #str then 394 | decode_error(str, idx, "trailing garbage") 395 | end 396 | return res 397 | end 398 | 399 | 400 | return json 401 | -------------------------------------------------------------------------------- /lib/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | simplified version of love2d/lua-https 3 | Copyright (c) 2023 Chunhui He 4 | 5 | Distributed under the same terms as the license of the original software 6 | 7 | Modification: 8 | - be compatible with luasocket.http 9 | - add timeout support 10 | - use libcurl as the only backend 11 | - handle multiple headers (e.g. Set-Cookie) 12 | - keep header order 13 | 14 | Original copyright notice: 15 | ---- 16 | Copyright (c) 2019-2022 LOVE Development Team 17 | 18 | This software is provided 'as-is', without any express or implied 19 | warranty. In no event will the authors be held liable for any damages 20 | arising from the use of this software. 21 | 22 | Permission is granted to anyone to use this software for any purpose, 23 | including commercial applications, and to alter it and redistribute it 24 | freely, subject to the following restrictions: 25 | 26 | 1. The origin of this software must not be misrepresented; you must not 27 | claim that you wrote the original software. If you use this software 28 | in a product, an acknowledgment in the product documentation would be 29 | appreciated but is not required. 30 | 2. Altered source versions must be plainly marked as such, and must not be 31 | misrepresented as being the original software. 32 | 3. This notice may not be removed or altered from any source distribution. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #ifndef _WIN32 41 | #include 42 | #else 43 | #define CURL_STATICLIB 44 | #endif 45 | #include 46 | 47 | typedef struct { 48 | char *str; 49 | size_t len; 50 | size_t pos; 51 | } String; 52 | 53 | static size_t string_reader(char *ptr, size_t size, size_t nb, void *ud) 54 | { 55 | String *reader = ud; 56 | size_t maxCount = (reader->len - reader->pos) / size; 57 | size_t desiredCount = maxCount < nb ? maxCount : nb; 58 | size_t desiredBytes = desiredCount * size; 59 | memcpy(ptr, reader->str + reader->pos, desiredBytes); 60 | reader->pos += desiredBytes; 61 | return desiredCount; 62 | } 63 | 64 | static size_t string_writer0(const char *ptr, size_t size, size_t nb, void *ud) 65 | { 66 | String *writer = ud; 67 | size_t count = size * nb; 68 | writer->str = realloc(writer->str, writer->len + count + 1); 69 | memcpy(writer->str + writer->len, ptr, count); 70 | writer->len += count; 71 | writer->str[writer->len++] = 0; 72 | return count; 73 | } 74 | 75 | static size_t string_writer(const char *ptr, size_t size, size_t nb, void *ud) 76 | { 77 | String *writer = ud; 78 | size_t count = size * nb; 79 | writer->str = realloc(writer->str, writer->len + count); 80 | memcpy(writer->str + writer->len, ptr, count); 81 | writer->len += count; 82 | return count; 83 | } 84 | 85 | typedef struct { 86 | size_t urllen; 87 | size_t postdatalen; 88 | const char *url; 89 | const char *method; 90 | const char *postdata; 91 | String header; 92 | long timeout; 93 | } Request; 94 | 95 | typedef struct { 96 | const char *error; 97 | String header; 98 | String body; 99 | int code; 100 | } Reply; 101 | 102 | typedef struct { 103 | bool loaded; 104 | #ifndef _WIN32 105 | void *handle; 106 | CURLcode (*global_init)(long flags); 107 | void (*global_cleanup)(void); 108 | CURL *(*easy_init)(void); 109 | void (*easy_cleanup)(CURL *curl); 110 | CURLcode (*easy_setopt)(CURL *curl, CURLoption option, ...); 111 | CURLcode (*easy_perform)(CURL *curl); 112 | CURLcode (*easy_getinfo)(CURL *curl, CURLINFO info, ...); 113 | struct curl_slist *(*slist_append)(struct curl_slist *list, 114 | const char *data); 115 | void (*slist_free_all)(struct curl_slist *list); 116 | #endif 117 | } Context; 118 | 119 | #ifndef _WIN32 120 | #define curl_(x) curl->x 121 | #else 122 | #define curl_(x) curl_ ## x 123 | #endif 124 | 125 | static bool init(Context *curl) 126 | { 127 | if (curl->loaded) 128 | return true; 129 | #ifndef _WIN32 130 | #define LOAD(x) if (!(curl->x = dlsym(curl->handle, "curl_" # x))) return false 131 | #ifdef __APPLE__ 132 | curl->handle = dlopen("/usr/lib/libcurl.4.dylib", RTLD_LAZY); 133 | #else 134 | curl->handle = dlopen("libcurl.so.4", RTLD_LAZY); 135 | if (!curl->handle) 136 | curl->handle = dlopen("libcurl-gnutls.so.4", RTLD_LAZY); 137 | #endif 138 | if (!curl->handle) 139 | return false; 140 | LOAD(global_init); 141 | LOAD(global_cleanup); 142 | LOAD(easy_init); 143 | LOAD(easy_cleanup); 144 | LOAD(easy_setopt); 145 | LOAD(easy_perform); 146 | LOAD(easy_getinfo); 147 | LOAD(slist_append); 148 | LOAD(slist_free_all); 149 | #undef LOAD 150 | #endif 151 | curl_(global_init)(CURL_GLOBAL_DEFAULT); 152 | curl->loaded = true; 153 | return true; 154 | } 155 | 156 | static void cleanup(Context *curl) 157 | { 158 | if (curl->loaded) 159 | curl_(global_cleanup)(); 160 | #ifndef _WIN32 161 | if (curl->handle) 162 | dlclose(curl->handle); 163 | #endif 164 | curl->loaded = false; 165 | } 166 | 167 | static bool request(Context *curl, const Request *req, Reply *reply) 168 | { 169 | CURL *ch = curl_(easy_init)(); 170 | if (!ch) { 171 | reply->error = "Could not create curl request"; 172 | return false; 173 | } 174 | 175 | curl_(easy_setopt)(ch, CURLOPT_URL, req->url); 176 | curl_(easy_setopt)(ch, CURLOPT_FOLLOWLOCATION, 1L); 177 | curl_(easy_setopt)(ch, CURLOPT_CUSTOMREQUEST, req->method); 178 | if (req->timeout) 179 | curl_(easy_setopt)(ch, CURLOPT_TIMEOUT_MS, req->timeout); 180 | 181 | if (req->postdatalen > 0 && 182 | strcmp(req->method, "GET") && strcmp(req->method, "HEAD")) { 183 | String reader; 184 | reader.str = (char *) req->postdata; 185 | reader.len = req->postdatalen; 186 | reader.pos = 0; 187 | curl_(easy_setopt)(ch, CURLOPT_UPLOAD, 1L); 188 | curl_(easy_setopt)(ch, CURLOPT_READFUNCTION, string_reader); 189 | curl_(easy_setopt)(ch, CURLOPT_READDATA, &reader); 190 | curl_(easy_setopt)(ch, CURLOPT_INFILESIZE_LARGE, 191 | (curl_off_t) reader.len); 192 | } 193 | if (strcmp(req->method, "HEAD") == 0) 194 | curl_(easy_setopt)(ch, CURLOPT_NOBODY, 1L); 195 | 196 | struct curl_slist *headers = NULL; 197 | const char *p = req->header.str; 198 | const char *e = req->header.str + req->header.len; 199 | while (p < e) { 200 | headers = curl_(slist_append)(headers, p); 201 | p += strlen(p) + 1; 202 | } 203 | if (headers) 204 | curl_(easy_setopt)(ch, CURLOPT_HTTPHEADER, headers); 205 | 206 | curl_(easy_setopt)(ch, CURLOPT_WRITEFUNCTION, string_writer); 207 | curl_(easy_setopt)(ch, CURLOPT_WRITEDATA, &reply->body); 208 | curl_(easy_setopt)(ch, CURLOPT_HEADERFUNCTION, string_writer0); 209 | curl_(easy_setopt)(ch, CURLOPT_HEADERDATA, &reply->header); 210 | 211 | curl_(easy_perform)(ch); 212 | long code; 213 | curl_(easy_getinfo)(ch, CURLINFO_RESPONSE_CODE, &code); 214 | reply->code = (int) code; 215 | 216 | if (headers) 217 | curl_(slist_free_all)(headers); 218 | curl_(easy_cleanup)(ch); 219 | return true; 220 | } 221 | 222 | static int string_gc(lua_State *L) 223 | { 224 | String *string = lua_touserdata(L, 1); 225 | free(string->str); 226 | return 0; 227 | } 228 | 229 | static int readonly_error(lua_State *L) 230 | { 231 | luaL_error(L, "attempt to update a read-only table"); 232 | return 0; 233 | } 234 | 235 | static int ordered_next(lua_State *L) 236 | { 237 | lua_pushvalue(L, lua_upvalueindex(2)); 238 | lua_rawget(L, 1); 239 | if (lua_isnil(L, -1)) 240 | return 1; 241 | 242 | lua_pushvalue(L, -1); 243 | lua_rawget(L, lua_upvalueindex(1)); 244 | 245 | int i = lua_tointeger(L, lua_upvalueindex(2)); 246 | lua_pushinteger(L, i + 1); 247 | lua_replace(L, lua_upvalueindex(2)); 248 | return 2; 249 | } 250 | 251 | static int ordered_pairs(lua_State *L) 252 | { 253 | lua_pushvalue(L, 1); 254 | lua_pushinteger(L, 1); 255 | lua_pushcclosure(L, ordered_next, 2); 256 | lua_pushvalue(L, lua_upvalueindex(1)); 257 | lua_pushnil(L); 258 | return 3; 259 | } 260 | 261 | static int w_request(lua_State *L) 262 | { 263 | Context *curl = lua_touserdata(L, lua_upvalueindex(2)); 264 | Request req; 265 | Reply reply; 266 | bool advanced = false; 267 | const char *post_defhdr = "Content-Type: " 268 | "application/x-www-form-urlencoded"; 269 | memset(&req, 0, sizeof(req)); 270 | memset(&reply, 0, sizeof(reply)); 271 | 272 | req.method = "GET"; 273 | lua_getfield(L, lua_upvalueindex(1), "TIMEOUT"); 274 | if (!lua_isnoneornil(L, -1)) { 275 | double timeout = luaL_checknumber(L, -1); 276 | req.timeout = timeout * 1000; 277 | } 278 | lua_pop(L, 1); 279 | 280 | int table_idx = 0; 281 | if (lua_istable(L, 1)) { 282 | table_idx = 1; 283 | } else { 284 | req.url = luaL_checklstring(L, 1, &req.urllen); 285 | if (lua_isstring(L, 2)) { 286 | req.postdata = (char *) 287 | luaL_checklstring(L, -1, &req.postdatalen); 288 | req.method = "POST"; 289 | string_writer(post_defhdr, 1, 290 | strlen(post_defhdr) + 1, &req.header); 291 | } 292 | } 293 | 294 | if (table_idx && lua_istable(L, table_idx)) { 295 | const char *defhdr = NULL; 296 | advanced = true; 297 | 298 | if (req.url == NULL) { 299 | lua_getfield(L, table_idx, "url"); 300 | req.url = luaL_checklstring(L, -1, &req.urllen); 301 | lua_pop(L, 1); 302 | } 303 | 304 | lua_getfield(L, table_idx, "data"); 305 | if (!lua_isnoneornil(L, -1)) { 306 | req.postdata = (char *) 307 | luaL_checklstring(L, -1, &req.postdatalen); 308 | req.method = "POST"; 309 | defhdr = post_defhdr; 310 | } 311 | lua_pop(L, 1); 312 | 313 | lua_getfield(L, table_idx, "method"); 314 | if (!lua_isnoneornil(L, -1)) { 315 | req.method = luaL_checkstring(L, -1); 316 | } 317 | lua_pop(L, 1); 318 | 319 | lua_getfield(L, table_idx, "headers"); 320 | bool flag = false; 321 | if (!lua_isnoneornil(L, -1)) { 322 | String *buffer = lua_newuserdata(L, sizeof(String)); 323 | memset(buffer, 0, sizeof(String)); 324 | lua_createtable(L, 0, 1); 325 | lua_pushcfunction(L, string_gc); 326 | lua_setfield(L, -2, "__gc"); 327 | lua_setmetatable(L, -2); 328 | 329 | int idx = lua_gettop(L) - 1; 330 | lua_getglobal(L, "pairs"); 331 | lua_pushvalue(L, idx); 332 | lua_call(L, 1, 3); 333 | while (true) { 334 | /* _f _s _var */ 335 | lua_pushvalue(L, -3); 336 | lua_pushvalue(L, -3); 337 | lua_rotate(L, -3, -1); 338 | /* _f _s _f _s _var */ 339 | lua_call(L, 2, 2); 340 | /* _f _s _var val */ 341 | if (lua_isnil(L, -2)) { 342 | lua_pop(L, 4); 343 | break; 344 | } 345 | size_t hl, cl; 346 | const char *h = luaL_checklstring(L, -2, &hl); 347 | const char *c = luaL_checklstring(L, -1, &cl); 348 | if (strcmp(h, "Content-Type") == 0) 349 | flag = true; 350 | string_writer(h, 1, hl, buffer); 351 | string_writer(": ", 1, 2, buffer); 352 | string_writer(c, 1, cl, buffer); 353 | string_writer("", 1, 1, buffer); 354 | lua_pop(L, 1); 355 | } 356 | req.header = *buffer; 357 | buffer->str = NULL; 358 | lua_pop(L, 1); 359 | } 360 | lua_pop(L, 1); 361 | if (!flag && defhdr) { 362 | string_writer(defhdr, 1, 363 | strlen(defhdr) + 1, &req.header); 364 | } 365 | } 366 | 367 | bool ok = request(curl, &req, &reply); 368 | free(req.header.str); 369 | 370 | if (!ok) { 371 | lua_pushnil(L); 372 | lua_pushstring(L, reply.error); 373 | return 2; 374 | } 375 | 376 | lua_pushlstring(L, reply.body.str, reply.body.len); 377 | lua_pushinteger(L, reply.code); 378 | free(reply.body.str); 379 | 380 | if (advanced) { 381 | int fid = 1; 382 | lua_newtable(L); // field array, for keeping order 383 | 384 | lua_newtable(L); // header map 385 | char *p = reply.header.str; 386 | char *e = reply.header.str + reply.header.len; 387 | while (p < e) { 388 | char *q = strchr(p, ':'); 389 | size_t plen = strlen(p); 390 | if (q) { 391 | lua_pushlstring(L, p, q - p); 392 | char *r = strchr(p + 1, '\r'); 393 | if (!r) 394 | r = p + plen; 395 | char *s = q + 1; 396 | while (*s == ' ') s++; 397 | size_t xlen = r - s; 398 | lua_pushvalue(L, -1); 399 | lua_gettable(L, -3); 400 | if (lua_isnil(L, -1)) { 401 | lua_pop(L, 1); 402 | 403 | lua_pushvalue(L, -1); 404 | lua_rawseti(L, -4, fid++); 405 | 406 | lua_pushlstring(L, s, xlen); 407 | } else { 408 | size_t olen; 409 | const char *ostr = 410 | lua_tolstring(L, -1, &olen); 411 | char *nstr = malloc(olen + 2 + xlen); 412 | memcpy(nstr, ostr, olen); 413 | memcpy(nstr + olen, ", ", 2); 414 | memcpy(nstr + olen + 2, s, xlen); 415 | lua_pop(L, 1); 416 | lua_pushlstring(L, nstr, 417 | olen + 2 + xlen); 418 | free(nstr); 419 | } 420 | lua_settable(L, -3); 421 | } 422 | p += plen + 1; 423 | } 424 | free(reply.header.str); 425 | 426 | lua_createtable(L, 0, 3); 427 | lua_pushvalue(L, -2); 428 | lua_setfield(L, -2, "__index"); 429 | lua_pushcfunction(L, readonly_error); 430 | lua_setfield(L, -2, "__newindex"); 431 | lua_pushvalue(L, -3); 432 | lua_pushcclosure(L, ordered_pairs, 1); 433 | lua_setfield(L, -2, "__pairs"); 434 | lua_setmetatable(L, -2); 435 | lua_remove(L, -2); 436 | return 3; 437 | } 438 | return 2; 439 | } 440 | 441 | #ifdef _WIN32 442 | #define DLLEXPORT __declspec(dllexport) 443 | #else 444 | #define DLLEXPORT 445 | #endif 446 | 447 | static Context curl; 448 | 449 | extern int DLLEXPORT luaopen_simplehttp(lua_State *L) 450 | { 451 | if (!init(&curl)) 452 | luaL_error(L, "unable to initialize libcurl"); 453 | 454 | lua_newtable(L); 455 | 456 | lua_pushvalue(L, -1); 457 | lua_pushlightuserdata(L, &curl); 458 | lua_pushcclosure(L, w_request, 2); 459 | lua_setfield(L, -2, "request"); 460 | return 1; 461 | } 462 | 463 | __attribute__((destructor)) 464 | static int close_simplehttp(void) 465 | { 466 | cleanup(&curl); 467 | return 0; 468 | } 469 | --------------------------------------------------------------------------------