1 module webuntis;
2 
3 import std.stdio;
4 import std.array;
5 import std.algorithm;
6 import std.exception;
7 import std.net.curl;
8 import std.json;
9 import std.format;
10 import std.conv;
11 import std.string;
12 import std.process;
13 import std.datetime;
14 
15 import objects;
16 import timetable;
17 
18 pragma(lib,"curl");
19 
20 class Session
21 {
22 	private string username;
23 	private string password;
24 	private string sessionID;
25 	private string url;
26 	private string client;
27 
28 	private Teacher[] teacherCache;
29 	private Subject[] subjectCache;
30 
31 	this(SessionConfiguration conf)
32 	{
33 		this.username = conf.username;
34 		this.password = conf.password;
35 		this.url = format("https://%s.webuntis.com/WebUntis/jsonrpc.do?school=%s",conf.server,conf.school);
36 		this.client = conf.client;
37 	}
38 	public void login()
39 	{
40 		auto params = format("{\"user\":\"%s\",\"password\":\"%s\",\"client\":\"%s\"}",
41 				username,password,client);
42 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"authenticate",params);
43 		auto response = sendRequest(req.toJSON());
44 		try
45 		{
46 			sessionID = response["result"]["sessionId"].str;
47 		}
48 		catch (JSONException ex)
49 		{
50 			throw new WebUntisException(format("Login Error: %s",response["error"]["message"].str));
51 		}
52 	}
53 	public void logout()
54 	{
55 		auto params = "{}";
56 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"logout",params);
57 		auto response = sendRequest(req.toJSON());
58 		try
59 		{
60 			cast(void) response["result"].isNull();
61 		}
62 		catch (JSONException ex)
63 		{
64 			throw new WebUntisException(format("Login Error: %s",response["error"]["message"].str));
65 		}
66 	}
67 
68 	public SchoolClass[] getClasses()
69 	{
70 		auto params = "{}";
71 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"getKlassen",params);
72 		auto response = sendRequest(req.toJSON());
73 		SchoolClass[] classes;
74 		try
75 		{
76 			foreach(class_;response["result"].array)
77 			{
78 				auto newClass = SchoolClass(
79 						to!int(class_["id"].integer),
80 						class_["name"].str,
81 						class_["longName"].str,
82 						class_["active"].type == JSON_TYPE.TRUE
83 						);
84 				classes ~= newClass;
85 			}
86 		}
87 		catch (JSONException ex)
88 		{
89 			throw new WebUntisException(format("Classes Error: %s",response["error"]["message"].str));
90 		}
91 		return classes;
92 	}
93 
94 	public Timetable getTimetable(int startDate,int endDate,int class_)
95 	{
96 		teacherCache = getTeachers();
97 		subjectCache = getSubjects();
98 		auto params = format("{\"id\":%s,
99 			\"type\":1,
100 			\"startDate\":\"%s\",
101 			\"endDate\":\"%s\"}",class_,startDate,endDate);
102 
103 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"getTimetable",params);
104 		auto response = sendRequest(req.toJSON());
105 		Timetable t;
106 		ClassUnit[] units;
107 		try
108 		{
109 			t = Timetable();	
110 			foreach(rawUnit;response["result"].array)
111 			{
112 				auto unit = ClassUnit(
113 						subjectCache.filter!(x => x.id == rawUnit["su"][0]["id"].integer).front,
114 						teacherCache.filter!(x => x.id == rawUnit["te"][0]["id"].integer).front,
115 						to!int(rawUnit["startTime"].integer),
116 						to!int(rawUnit["date"].integer)
117 						);
118 				units ~= unit;
119 			}
120 		}
121 		catch (JSONException ex)
122 		{
123 			throw new WebUntisException(format("Timetable Error: %s",response["error"]["message"].str));
124 		}
125 
126 		bool startTimeComp(ClassUnit x, ClassUnit y) @safe pure nothrow { return x.startTime > y.startTime; }
127 		for (int date = startDate; date < endDate; date++)
128 		{
129 			auto dayunits = array(units.filter!(x => x.date == date));
130 			SchoolDay day;
131 			day.units ~= dayunits;
132 			t.days ~= day;
133 		}
134 		return t;
135 	}
136 
137 	public Teacher[] getTeachers()
138 	{
139 		auto params = "{}";
140 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"getTeachers",params);
141 		auto response = sendRequest(req.toJSON());
142 		Teacher[] teachers;
143 		try
144 		{
145 			foreach(teacher;response["result"].array)
146 			{
147 				//ID,Name,foreName,longName,active
148 				auto newTeacher = Teacher(
149 						to!int(teacher["id"].integer),
150 						teacher["name"].str,
151 						teacher["foreName"].str,
152 						teacher["longName"].str,
153 						teacher["active"].type == JSON_TYPE.TRUE
154 						);
155 				teachers ~= newTeacher;
156 			}
157 		}
158 		catch (JSONException ex)
159 		{
160 			throw new WebUntisException(format("Teachers Error: %s",response["error"]["message"].str));
161 		}
162 		return teachers;
163 	}
164 
165 	public Subject[] getSubjects()
166 	{
167 		auto params = "{}";
168 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"getSubjects",params);
169 		auto response = sendRequest(req.toJSON());
170 		Subject[] subjects;
171 		try
172 		{
173 			foreach(subject;response["result"].array)
174 			{
175 				//ID,Name,longName,alternateName,active
176 				auto newSubject = Subject(
177 						to!int(subject["id"].integer),
178 						subject["name"].str,
179 						subject["longName"].str,
180 						subject["alternateName"].str,
181 						subject["active"].type == JSON_TYPE.TRUE
182 						);
183 				subjects ~= newSubject;
184 			}
185 		}
186 		catch (JSONException ex)
187 		{
188 			throw new WebUntisException(format("Subjects Error: %s",response["error"]["message"].str));
189 		}
190 		return subjects;
191 	}
192 
193 	public Room[] getRooms()
194 	{
195 		auto params = "{}";
196 		auto req = Request(to!string(Clock.currTime().toUnixTime()),"getRooms",params);
197 		auto response = sendRequest(req.toJSON());
198 		Room[] rooms;
199 		try
200 		{
201 			foreach(room;response["result"].array)
202 			{
203 				//ID,Name,longName,active
204 				auto newRoom = Room(
205 						to!int(room["id"].integer),
206 						room["name"].str,
207 						room["longName"].str,
208 						room["active"].type == JSON_TYPE.TRUE
209 						);
210 				rooms ~= newRoom;
211 			}
212 		}
213 		catch (JSONException ex)
214 		{
215 			throw new WebUntisException(format("Subjects Error: %s",response["error"]["message"].str));
216 		}
217 		return rooms;
218 	}
219 
220 	private JSONValue sendRequest(JSONValue data)
221 	{
222 		string reqbody = data.toString();
223 		auto client = HTTP();
224 		client.addRequestHeader("Content-Type","application/json");
225 		client.postData = reqbody;
226 
227 		if (sessionID != null)
228 		{
229 			client.addRequestHeader("Cookie",format("JSESSIONID=%s",sessionID));
230 		}
231 
232 		// Response handling setup
233 		string response = post(url,reqbody,client).dup;
234 		// Parsing and returning the JSON
235 		return parseJSON(response);
236 	}
237 }
238 
239 
240 struct Request
241 {
242 	string id;
243 	string method;
244 	string params;
245 	string jsonrpc = "2.0";
246 	JSONValue toJSON()
247 	{
248 		string jsonString = chomp(format(
249 				"{
250 					\"id\":\"%s\",
251 					\"method\":\"%s\",
252 					\"params\": %s ,
253 					\"jsonrpc\":\"%s\"
254 				}",
255 				id,
256 				method,
257 				params,
258 				jsonrpc
259 				));
260 		JSONValue value = parseJSON(jsonString);
261 		return value;
262 	}
263 }
264 
265 struct SessionConfiguration
266 {
267 	string username;
268 	string password;
269 	string server;
270 	string school;
271 	string client;
272 }
273 
274 /**
275 	Exception thrown on Webuntis Errors
276 */
277 class WebUntisException : Exception
278 {
279 	this(string msg)
280 	{
281 		super(msg);
282 	}
283 }
284 
285 // All Testing happens HERE
286 unittest
287 {
288 	SessionConfiguration sconf = SessionConfiguration(
289 		environment["wuuser"],
290 		"wrongpassword",
291 		environment["wuserver"],
292 		environment["wuschool"],
293 		"WebUntis API dlang wrapper");
294 
295 	Session s = new Session(sconf);
296 
297 	writeln("Begin of Tests");
298 	writeln("---------------------------------------");
299 
300 	writeln("Testing method access before login");
301 	assertThrown!WebUntisException(s.getClasses());
302 	assertThrown!WebUntisException(s.getTeachers());
303 	assertThrown!WebUntisException(s.logout());
304 	writeln("OK");
305 
306 	writeln("---------------------------------------");
307 
308 	writeln("Testing Login with wrong password");
309 	assertThrown!WebUntisException(s.login());
310 	writeln("OK");
311 
312 	writeln("---------------------------------------");
313 
314 	// Setting the real password
315 	sconf.password = environment["wupassword"];
316 	s = new Session(sconf);
317 
318 	writeln("Testing Login with right password");
319 	assertNotThrown!WebUntisException(s.login());
320 	writeln("OK");
321 
322 	writeln("---------------------------------------");
323 
324 	writeln("Testing Teachers");
325 	auto teachers = s.getTeachers();
326 	assert(teachers.length > 0);
327 	writef("Found %s teachers\n",teachers.length);
328 	writef("For Example: %s\n",teachers[$/2].longName);
329 
330 	writeln("OK");
331 
332 	writeln("---------------------------------------");
333 
334 	writeln("Testing Subjects");
335 	auto subjects = s.getSubjects();
336 	assert(subjects.length > 0);
337 	writef("Found %s subjects\n",subjects.length);
338 	writef("For Example: %s\n",subjects[$/2].longName);
339 
340 	writeln("OK");
341 
342 	writeln("---------------------------------------");
343 
344 	writeln("Testing Rooms");
345 	auto rooms = s.getRooms();
346 	assert(subjects.length > 0);
347 	writef("Found %s Rooms\n",rooms.length);
348 	writef("For Example: %s\n",rooms[$/2].longName);
349 
350 	writeln("OK");
351 
352 	writeln("---------------------------------------");
353 
354 	writeln("Testing Classes");
355 	auto classes = s.getClasses();
356 	assert(classes.length > 0);
357 	writef("Found %s classes\n",classes.length);
358 	writef("For Example %s\n",classes[$/2].name);
359 	writeln("OK");
360 
361 	writeln("---------------------------------------");
362 
363 	writeln("Testing Timetable");
364 	auto table = s.getTimetable(20150925,20150926,37);
365 	writeln("OK");
366 
367 	writeln("---------------------------------------");
368 	s.logout();
369 }