Last active
January 11, 2026 11:13
-
-
Save pgaskin/b02ba7c2bf3eeb722852c216c69e09c3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Command aftership-order-status extracts the full status information from an | |
| // aftership order-status webpage. | |
| package main | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "errors" | |
| "math/bits" | |
| "os" | |
| "slices" | |
| "strings" | |
| "unicode/utf16" | |
| "unicode/utf8" | |
| "unsafe" | |
| "golang.org/x/net/html" | |
| "golang.org/x/net/html/atom" | |
| ) | |
| // https://XXXXX.aftership.com/order-status?XXXXX | |
| // (decompress=>JSON.parse(decompress(JSON.parse(document.getElementById('__NEXT_DATA__').textContent).props.initialProps.initialData.initialState)).atoms.order.order)(function(r){let t=String.fromCharCode,e=Error("invalid"),h=0,o=0,f=0,i=t=>{let i=0;for(let l=0;l<t;l++){if(0==f){if(h>=r.length)throw e;f=32768,o=r.charCodeAt(h++)}o&f&&(i|=1<<l),f>>=1}return i},l,n=[],a="";for(;;){let r,h=n.length,o=i(32-Math.clz32(3+h));if(2==o)return a;if(o>2){if(o-=3,!l||o>h)throw e;r=o<h?n[o]:l+l.charAt(0)}else r=t(i(o?16:8)),n.push(r);l&&n.push(l+r.charAt(0)),l=r,a+=r}}) | |
| func main() { | |
| doc, err := html.Parse(os.Stdin) | |
| if err != nil { | |
| panic(err) | |
| } | |
| var data bytes.Buffer | |
| for child := range doc.Descendants() { | |
| if child.Type == html.ElementNode && child.DataAtom == atom.Script && slices.ContainsFunc(child.Attr, func(a html.Attribute) bool { | |
| return a.Key == "id" && a.Val == "__NEXT_DATA__" | |
| }) { | |
| for n := range child.Descendants() { | |
| if n.Type == html.TextNode { | |
| data.WriteString(n.Data) | |
| } | |
| } | |
| break | |
| } | |
| } | |
| var obj struct { | |
| Props struct { | |
| InitialProps struct { | |
| InitialData struct { | |
| InitialState json.RawMessage `json:"initialState"` | |
| } `json:"initialData"` | |
| } `json:"initialProps"` | |
| } `json:"props"` | |
| } | |
| if err := json.NewDecoder(&data).Decode(&obj); err != nil { | |
| panic(err) | |
| } | |
| s, err := unquote(nil, []byte(obj.Props.InitialProps.InitialData.InitialState)) | |
| if err != nil { | |
| panic(err) | |
| } | |
| d, err := decompress(nil, s) | |
| if err != nil { | |
| panic(err) | |
| } | |
| var obj1 struct { | |
| Atoms struct { | |
| Order struct { | |
| Order json.RawMessage `json:"order"` | |
| } `json:"order"` | |
| } `json:"atoms"` | |
| } | |
| if err := json.NewDecoder(strings.NewReader(string(utf16.Decode(d)))).Decode(&obj1); err != nil { | |
| panic(err) | |
| } | |
| f := new(bytes.Buffer) | |
| if err := json.Indent(f, []byte(obj1.Atoms.Order.Order), "", " "); err != nil { | |
| panic(err) | |
| } | |
| e := json.NewEncoder(os.Stdout) | |
| e.SetIndent("", " ") | |
| e.SetEscapeHTML(false) | |
| if err := e.Encode(obj1.Atoms.Order.Order); err != nil { | |
| panic(err) | |
| } | |
| } | |
| // https://gist.github.com/pgaskin/1d2f1c7f5002670432247dd5adf85557 | |
| // decompress decompresses a lzstring-compressed byte sequence from a slice of | |
| // bytes or utf-16 code units, appending the result into an slice of utf-16 code | |
| // units. To decode the result, use string(utf16.Decode(dst)). | |
| func decompress[T byte | uint16](dst []uint16, src []T) ([]uint16, error) { | |
| var ( | |
| dat uint32 | |
| bit uint32 | |
| ) | |
| ubits := func(bits int) (res uint32, ok bool) { | |
| // interpret src as a big-endian bitstream, and read a little-endian uint from it | |
| for i := range bits { | |
| if bit == 0 { | |
| if len(src) == 0 { | |
| return res, false | |
| } | |
| bit = 1 << (uint(unsafe.Sizeof(src[0])*8) - 1) | |
| dat = uint32(src[0]) | |
| src = src[1:] | |
| } | |
| if dat&bit != 0 { | |
| res |= 1 << i | |
| } | |
| bit >>= 1 // msb to lsb | |
| } | |
| return res, true | |
| } | |
| var ( | |
| last uint32 // last chunk start index | |
| dict [][2]uint32 | |
| ) | |
| for { | |
| dictSize := uint32(len(dict)) | |
| op, ok := ubits(bits.Len32(3 + dictSize)) | |
| if !ok { | |
| return dst, errors.New("unexpected end of stream") | |
| } | |
| if op == 2 { | |
| return dst, nil | |
| } | |
| chunk := uint32(len(dst)) // current chunk start index | |
| if op > 2 { | |
| idx := op - 3 | |
| if dictSize == 0 { | |
| return dst, errors.New("first packet must be a literal") | |
| } | |
| if idx > dictSize { | |
| return dst, errors.New("dictionary index out of range") | |
| } | |
| if idx == dictSize { | |
| dst = append(dst, dst[last:]...) | |
| dst = append(dst, dst[last]) | |
| } else { | |
| dst = append(dst, dst[dict[idx][0]:dict[idx][1]]...) | |
| } | |
| } else { | |
| bits := [...]int{ | |
| 0: 8, | |
| 1: 16, | |
| }[op] | |
| lit, ok := ubits(bits) | |
| if !ok { | |
| return dst, errors.New("unexpected end of stream") | |
| } | |
| dst = append(dst, uint16(lit)) | |
| dict = append(dict, [2]uint32{chunk, chunk + 1}) | |
| } | |
| if dictSize != 0 { | |
| dict = append(dict, [2]uint32{last, chunk + 1}) | |
| } | |
| last = chunk | |
| } | |
| } | |
| // unquote unquotes a valid JSON string as a series of UTF-16 code units. Any | |
| // junk after the end of the string is ignored. This is roughly equivalent to | |
| // the following JS: | |
| // | |
| // function unquote(s) { | |
| // s = JSON.parse(s) | |
| // return Array(s.length).keys().map(i => s.charCodeAt(i)).toArray() | |
| // } | |
| // | |
| // Unlike the usual Go strings and JSON libraries, this will split high UTF-8 | |
| // characters into the UTF-16 surrogate pairs, and it will preserve invalid | |
| // surrogate pairs as their raw hex values. | |
| // | |
| // This is intended for use when parsing and decompressing raw lzstrings | |
| // serialized as JSON, i.e.: | |
| // | |
| // JSON.stringify(LZString.compress("whatever")) | |
| func unquote[T string | []byte](dst []uint16, src T) ([]uint16, error) { | |
| if len(src) == 0 || src[0] != '"' { | |
| return dst, errors.New("json string missing start quote") | |
| } | |
| src = src[1:] | |
| for { | |
| if len(src) == 0 { | |
| return dst, errors.New("json string missing end quote") | |
| } | |
| r, rn := utf8.DecodeRuneInString(string(src[:min(len(src), 4)])) | |
| if r == utf8.RuneError { | |
| return dst, errors.New("json is not valid utf-8") | |
| } | |
| src = src[rn:] | |
| switch { | |
| case r == '"': | |
| return dst, nil | |
| case r == '\\': | |
| if len(src) == 0 { | |
| return dst, errors.New("unexpected eof in json escape") | |
| } | |
| e := src[0] | |
| src = src[1:] | |
| switch e { | |
| case '"', '\\', '/': | |
| dst = append(dst, uint16(e)) | |
| case 'b': | |
| dst = append(dst, '\b') | |
| case 'f': | |
| dst = append(dst, '\f') | |
| case 'n': | |
| dst = append(dst, '\n') | |
| case 'r': | |
| dst = append(dst, '\r') | |
| case 't': | |
| dst = append(dst, '\t') | |
| case 'u': | |
| if len(src) < 4 { | |
| return dst, errors.New("invalid json unicode escape") | |
| } | |
| var v uint16 | |
| for _, c := range []byte(src[:4]) { | |
| switch { | |
| case '0' <= c && c <= '9': | |
| c = c - '0' | |
| case 'a' <= c && c <= 'f': | |
| c = 10 + c - 'a' | |
| case 'A' <= c && c <= 'F': | |
| c = 10 + c - 'A' | |
| default: | |
| return dst, errors.New("invalid json unicode escape") | |
| } | |
| v = v*16 + uint16(c) | |
| } | |
| src = src[4:] | |
| dst = append(dst, v) | |
| default: | |
| return dst, errors.New("invalid json escape") | |
| } | |
| case r >= 1<<16: | |
| // would be encoded as a surrogate pair, so split it into the two code units | |
| dst = append(dst, uint16(0xd800+((r-1<<16)>>10)&0x3ff), uint16(0xdc00+(r-1<<16)&0x3ff)) | |
| default: | |
| dst = append(dst, uint16(r)) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment