From c57a3c2d1b2b4ec8fd61b376e6ce49893296f7c5 Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Sat, 2 Jan 2021 18:45:24 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + .prettierrc | 14 +++ package-lock.json | 163 +++++++++++++++++++++++++++++ package.json | 23 +++++ src/index.ts | 182 +++++++++++++++++++++++++++++++++ src/location.ts | 26 +++++ src/months.ts | 16 +++ src/transform-coordinates.d.ts | 11 ++ tsconfig.json | 19 ++++ 9 files changed, 457 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 src/location.ts create mode 100644 src/months.ts create mode 100644 src/transform-coordinates.d.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..885e939 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/node_modules/ +/@types/ +/lib/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..886619a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + useTabs: true, + tabWidth: 4, + trailingComma: "es5", + "overrides": [ + { + "files": "*.yml", + "options": { + "tabWidth": 2, + "useTabs": false + } + } + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8a866a1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,163 @@ +{ + "name": "poznan-sm-submit", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.19.tgz", + "integrity": "sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==" + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, + "async-spawner": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/async-spawner/-/async-spawner-0.1.0.tgz", + "integrity": "sha512-I/5cYWGgH5LJiHqWTwPLu7yM4XqT7dtMo77bPWtdtmOqbT9xpxF/ckasT2kdubMw8D0QD6/nSTPx+ZjvbhW/jQ==", + "requires": { + "@types/node": "^14.14.19", + "emittery": "^0.8.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "axios-cookiejar-support": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz", + "integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==", + "requires": { + "is-redirect": "^1.0.0", + "pify": "^5.0.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "emittery": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.0.tgz", + "integrity": "sha512-XMGToId3CejfmZg/0qOzdTT3WFuAN8fQYtcKXccabRfCzGiWMSTydMshHGLyx9C/ejMl4nw9tvqrn12QVFPIUg==" + }, + "epsg-index": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/epsg-index/-/epsg-index-1.0.0.tgz", + "integrity": "sha512-7EcEC97QaNzSKakpc6HgVdxBc7G1vEuNRIygjVXBvrLA+t8+4QlK6jisf0g552eSzBIAkx0IRmd/CWJKLc/W5A==" + }, + "follow-redirects": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "mgrs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", + "integrity": "sha1-+5FYjnjJACVnI5XLQLJffNatGCk=" + }, + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" + }, + "mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "requires": { + "mime-db": "1.45.0" + } + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "proj4": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.6.3.tgz", + "integrity": "sha512-XRqnLmHWlvi7jqKNTqaOUrVy72JEtOUrnlLki99yZUOSvcSeBaZ1I/EGnQ2LzplSbjSrebGAdikqCLeCxC/YEg==", + "requires": { + "mgrs": "1.0.0", + "wkt-parser": "^1.2.4" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "transform-coordinates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/transform-coordinates/-/transform-coordinates-1.0.0.tgz", + "integrity": "sha512-6kZMpg+TBThUO7lbt9lWbM/DiRED+lczpjjRN3R7CB0xTEfVYqx3X9pBZx0gpihdmiUZNghArYKBiZNG4XDFYA==", + "requires": { + "epsg-index": "^1.0.0", + "proj4": "^2.4.4" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "wkt-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.2.4.tgz", + "integrity": "sha512-ZzKnc7ml/91fOPh5bANBL4vUlWPIYYv11waCtWTkl2TRN+LEmBg60Q1MA8gqV4hEp4MGfSj9JiHz91zw/gTDXg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..19251ae --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "poznan-sm-submit", + "version": "0.1.0", + "description": "submit report to Poznań's Straż Miejska", + "main": "dist/index.js", + "types": "./@types/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "npm run build -- --watch" + }, + "author": "Kuba Orlik", + "license": "ISC", + "dependencies": { + "@types/node": "^14.14.19", + "@types/tough-cookie": "^4.0.0", + "async-spawner": "^0.1.0", + "axios": "^0.21.1", + "axios-cookiejar-support": "^1.0.1", + "form-data": "^3.0.0", + "tough-cookie": "^4.0.0", + "transform-coordinates": "^1.0.0" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..70917ca --- /dev/null +++ b/src/index.ts @@ -0,0 +1,182 @@ +import { promisify } from "util"; +import spawn from "async-spawner"; +import FormData from "form-data"; +import axios from "axios"; +import tough from "tough-cookie"; +import fs from "fs"; +import axiosCookieJarSupport from "axios-cookiejar-support"; +import * as path from "path"; + +import { LocationData, toCityCoordinates } from "./location"; +import months from "./months"; + +axiosCookieJarSupport(axios); + +const SM_URL = + "https://www.um.poznan.pl/mim/forms/sm_zgloszenia.html?instance=poznan_sm"; + +const headers = { + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + Host: "www.um.poznan.pl", + Origin: "https://www.um.poznan.pl", + Referer: + "https://www.um.poznan.pl/mim/forms/sm_zgloszenia.html?instance=poznan_sm", + "User-Agent": + "Mozilla/5.0 (X11; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0", +}; + +const minimal_data = { + lhs: "eurzad", + instance: "poznan_sm", +}; + +function formatTimestamp(timestamp: Date) { + const date_str = `${timestamp.getDate()} ${ + months[timestamp.getMonth()] + } ${timestamp.getFullYear()}`; + const time = `${timestamp.getHours()}:${ + timestamp.getMinutes() < 10 ? "0" : "" + }${timestamp.getMinutes()}`; + return [date_str, time]; +} + +type ContactDetails = { + email: string; + imie_nazwisko: string; + adres: string; +}; + +export async function submit( + location: LocationData, + reason: string, + photos: Array, + timestamp: Date, + contact_details: ContactDetails +) { + if (location.address.includes("\n")) { + throw new Error("no newlines allowed in address"); + } + const { x, y } = toCityCoordinates(location.coordinates); + + const jar = new tough.CookieJar(); + + const transport = axios.create({ + withCredentials: true, + }); + + await transport.get(SM_URL, { + headers, + jar, + }); + + const subject = `Nieprawidłowo zaparkowany samochód - ${location.address}`; + + const [date_str, hour] = formatTimestamp(timestamp); + + const body = `Samochód ${reason || ""}. Zdjęci${ + photos.length === 1 ? "e" : "a" + } wykonane ${date_str} o godz. ${hour} w pobliżu ul. ${ + location.address + }. Wnoszę o ukaranie. + `; + + const main_data = { + ...minimal_data, + srs: "EPSG:2177", + uz_process_personal_data_agreement: "Y", + uz_kategoria: 86808, + uz_temat: subject, + uz_tresc: body, + uz_email: contact_details.email, + uz_odpowiedz: "tak", + uz_imie_nazwisko: contact_details.imie_nazwisko, + uz_adres: contact_details.adres, + }; + + const forms = [ + { + ...main_data, + source: "sm_zgloszenia_step1", + goto_sm_zgloszenia_step1_1: "", + }, + { + source: "sm_zgloszenia_step1_1", + x, + y, + lon: x, + lat: y, + srs: "EPSG:2177", + id_ulica_city: "Poznań", + srs_id: "EPSG:2177", + id_ulica_street_lookup: "Limanowskiego", + goto_sm_zgloszenia_step1: "", + }, + { ...main_data, goto_sm_zgloszenia_step1_2: "" }, + ...photos.map((file_path) => ({ + ...minimal_data, + source: "sm_zgloszenia_step1_2", + goto_sm_zgloszenia_step1_2: "", + uz_file: { + path: file_path, + filename: "photo1.jpg", + contentType: "image/jpeg", + }, + })), + { + ...minimal_data, + source: "sm_zgloszenia_step1_2", + goto_sm_zgloszenia_step1: "", + }, + { ...main_data, source: "sm_zgloszenia_step1", goto_summary: "" }, + ]; + + for (let form of forms) { + const formData = new FormData(); + for (let [key, value] of Object.entries(form)) { + if (value.path) { + formData.append(key, fs.createReadStream(value.path), { + filename: value.filename, + contentType: value.contentType, + }); + } else { + formData.append(key, value); + } + } + const response = await transport.post(SM_URL, formData, { + headers: { ...headers, ...formData.getHeaders() }, + jar, + }); + console.log(response.request._headers); + } + const session_id = (jar as any).store.idx["www.um.poznan.pl"]["/mim"] + .JSESSIONID.value; + + const response = await spawn("curl", [ + "https://www.um.poznan.pl/mim/forms/sm_zgloszenia.html?instance=poznan_sm", + "-H", + `User-Agent: ${headers["User-Agent"]}`, + "-H", + `Accept: ${headers.Accept}`, + "-H", + "Accept-Language: en-US,en;q=0.5", + "--compressed", + "-H", + "Content-Type: application/x-www-form-urlencoded", + "-H", + `Origin: ${headers.Origin}`, + "-H", + "DNT: 1", + "-H", + "Connection: keep-alive", + "-H", + `Referer: ${headers.Referer}`, + "-H", + `Cookie: mim_cookie_test=enabled; JSESSIONID=${session_id}`, + "-H", + "Upgrade-Insecure-Requests: 1", + "--data", + `action=save&source=summary&instance=poznan_sm&x=${x}&y=${y}&lon=${x}&lat=${y}&srs=EPSG%3A2177&goto_thanks=`, + ]); + console.log(response); +} diff --git a/src/location.ts b/src/location.ts new file mode 100644 index 0000000..85fbe1c --- /dev/null +++ b/src/location.ts @@ -0,0 +1,26 @@ +import transformation from "transform-coordinates"; + +export type Coordinates = { + lat: number; + lon: number; +}; + +export type CityCoordinates = { + x: number; + y: number; + z: number; +}; + +export interface LocationData { + coordinates: Coordinates; + address: string; +} + +export function toCityCoordinates(coords: Coordinates): CityCoordinates { + const transform = transformation("EPSG:4326", "EPSG:2177"); + const city_coordinates = transform.forward({ + x: coords.lon, + y: coords.lat, + }); + return city_coordinates; +} diff --git a/src/months.ts b/src/months.ts new file mode 100644 index 0000000..6e3ee6d --- /dev/null +++ b/src/months.ts @@ -0,0 +1,16 @@ +const months = [ + "stycznia", + "lutego", + "marca", + "kwietnia", + "maja", + "czerwca", + "lipca", + "sierpnia", + "września", + "października", + "listopada", + "grudnia", +]; + +export default months; diff --git a/src/transform-coordinates.d.ts b/src/transform-coordinates.d.ts new file mode 100644 index 0000000..8b1aff0 --- /dev/null +++ b/src/transform-coordinates.d.ts @@ -0,0 +1,11 @@ +declare module "transform-coordinates" { + export default function getTransport( + coords1: string, + coords2: string + ): { + forward: (coords: { + x: number; + y: number; + }) => { x: number; y: number; z: number }; + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ce33b64 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "target": "ES2020", + "declaration": true, + "esModuleInterop": true, + "lib": ["es6", "esnext"], + "outDir": "lib", + "checkJs": true, + "allowJs": true, + "declarationDir": "@types", + "sourceMap": true + }, + "include": ["src/**/*"] +}