summaryrefslogtreecommitdiff
path: root/jimbrella/jform.py
diff options
context:
space:
mode:
Diffstat (limited to 'jimbrella/jform.py')
-rw-r--r--jimbrella/jform.py104
1 files changed, 104 insertions, 0 deletions
diff --git a/jimbrella/jform.py b/jimbrella/jform.py
new file mode 100644
index 0000000..dff6654
--- /dev/null
+++ b/jimbrella/jform.py
@@ -0,0 +1,104 @@
+import requests
+import json
+
+
+class JForm:
+ """Retrieves results from one jForm questionnaire.
+
+ In this context, "JForm" refers to this class, and "jForm", the online service also known as
+ SJTU Questionnaires, hosted on https://wj.sjtu.edu.cn/.
+
+ JImbrella's database relies on JForm to sync its database against jForm's.
+
+ Each JForm object requires a checkpoint file in which to store the id of the latest answer
+ sheet it has read. Each time the API detects the presence of new answer sheets, the file will
+ be overwritten.
+ """
+
+ def __init__(self, name: str, url: str, checkpoint_fp: str):
+ self._name = name # internal identifier
+ self.url = url
+ self._checkpoint_fp = checkpoint_fp
+
+ def _get(self, page=1) -> dict:
+ """Internal method to HTTP GET the API in JSON format."""
+ resp = requests.get(
+ self.url,
+ params={
+ "params": json.dumps(
+ {
+ "current": page,
+ "pageSize": 100,
+ }
+ ),
+ "sort": json.dumps({"id": "desc"}),
+ },
+ )
+ return resp
+
+ def _read_checkpoint(self) -> int:
+ """Read checkpoint file and returns contents. No safeguards."""
+ try:
+ with open(self._checkpoint_fp) as f:
+ checkpoint = f.read()
+ f.close()
+ return int(checkpoint)
+ except FileNotFoundError:
+ return 0
+
+ def _write_checkpoint(self, checkpoint: int) -> None:
+ """Write into checkpoint file."""
+ try:
+ with open(self._checkpoint_fp, "x") as f:
+ f.write(str(checkpoint))
+ f.close()
+ except FileExistsError:
+ with open(self._checkpoint_fp, "w") as f:
+ f.write(str(checkpoint))
+ f.close()
+
+ def get_unread(self) -> list:
+ """Get unread answers to required fields as a list of dicts, most recent last."""
+ checkpoint = self._read_checkpoint()
+ unread = []
+ latest_id = 0
+ page = 1
+ found_read = False
+ while not found_read:
+ try:
+ resp = self._get(page=page)
+ except:
+ break # quietly abort
+ if resp.status_code != 200:
+ break
+
+ sheets = resp.json()["data"]["rows"]
+ if not latest_id:
+ # the first page of sheets we have retrieved this run.
+ # on this page, the first answer sheet is the latest.
+ # update checkpoint. next time, stop before this id.
+ latest_id = sheets[0]["id"]
+ self._write_checkpoint(latest_id)
+
+ if not sheets:
+ # somehow jForm doesn't respond with a 404
+ # when we exceed the total pages of a questionnaire
+ # instead it just returns 200 along with an empty data field
+ break
+ for sheet in sheets:
+ if sheet["id"] <= checkpoint:
+ # is checkpoint or earlier than checkpoint
+ found_read = True
+ break
+ ans = sheet["answers"]
+ unread.append(
+ {
+ "name": ans[0]["answer"],
+ "id": ans[1]["answer"],
+ "phone": ans[2]["answer"],
+ "key": ans[3]["answer"],
+ }
+ )
+ page += 1
+
+ return unread