├── .gitignore ├── Procfile ├── README.md ├── app.json ├── gulpfile.js ├── lib └── kintai.js ├── package.json └── src └── kintai.js /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitode909/kintai-api/a3601196703879e459bc8972574ee80f654d9770/.gitignore -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: phantomjs lib/kintai.js server -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kintai API - The e-asp wrapper 2 | 3 | Kintai API allows you to punch time cards via simple HTTP request. 4 | 5 | # Deploy to Heroku 6 | 7 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 8 | 9 | To deploy Kintai API, you must set following kintai variables. 10 | 11 | - KINTAI_URL 12 | - The URL to record your time card. like this: `https://****H.HTML` 13 | - KINTAI_STAFF_ID 14 | - Your Staff ID. 15 | - KINTAI_PASSWORD 16 | - The password to login. 17 | - API_TOKEN 18 | - The API token for authorization (basically auto-generated). 19 | 20 | # How to get API token 21 | 22 | With deploy to heroku button, the API token is auto-generated. You can get it at Heroku's application settings → Config Variables → Reveal Config Vars. 23 | 24 | # Endpoints 25 | 26 | ``` 27 | GET /up?api_token=*** : Start working 28 | GET /down?api_token=*** : Stop working 29 | GET /report.json?api_token=*** : Print the report (JSON) 30 | GET /report.txt?api_token=*** : Print the monthly report (plain text) 31 | ``` 32 | 33 | # Get the Kintai Button 34 | 35 | With IFTTT's DO Button and Maker Channel, you can punch time card from the smartphone's home screen. 36 | 37 | ![](https://i.gyazo.com/7cf735891ba6c127f3b0183c8a4d7433.png) 38 | 39 | - [DO Button - IFTTT](https://ifttt.com/products/do/button) 40 | - [Connect Maker to hundreds of apps - IFTTT](https://ifttt.com/maker) 41 | 42 | 43 | # Run Locally 44 | 45 | ## Setup 46 | 47 | ``` 48 | npm install 49 | npm run build 50 | brew install phantomjs 51 | ``` 52 | 53 | ## Run 54 | 55 | ``` 56 | KINTAI_URL='***' KINTAI_STAFF_ID='***' KINTAI_PASSWORD='***' API_TOKEN='***' PORT=3001 phantomjs lib/kintai.js 57 | ``` 58 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kintai-api", 3 | "website": "https://github.com/hitode909/kintai-api", 4 | "repository": "https://github.com/hitode909/kintai-api", 5 | "env": { 6 | "KINTAI_URL": { 7 | "description": "The URL to record your time card. like this: https://****H.HTML" 8 | }, 9 | "KINTAI_STAFF_ID": { 10 | "description": "Your Staff ID." 11 | }, 12 | "KINTAI_PASSWORD": { 13 | "description": "The Password to login." 14 | }, 15 | "API_TOKEN": { 16 | "description": "API Token to authorize. Example: /up?api_key=12345", 17 | "generator": "secret" 18 | } 19 | }, 20 | "buildpacks": [ 21 | { 22 | "url": "https://github.com/stomita/heroku-buildpack-phantomjs" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const source = require('vinyl-source-stream'); 3 | const browserify = require('browserify'); 4 | 5 | gulp.task('build', function() { 6 | browserify('src/kintai.js' 7 | ).transform("babelify", {presets: ["es2015"]} 8 | ).bundle( 9 | ).pipe( 10 | source('kintai.js') 11 | ).pipe(gulp.dest('lib')); 12 | }); 13 | 14 | gulp.task('watch', function() { 15 | gulp.watch('src/*.js', ['build']); 16 | }); 17 | 18 | gulp.task('default', ['build']); 19 | -------------------------------------------------------------------------------- /lib/kintai.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { 306 | result.push(date + ' ' + up1 + '〜' + down1 + ' (' + up2 + '〜' + down2 + ')'); 307 | } else { 308 | result.push(date); 309 | } 310 | }); 311 | return result.join("\n"); 312 | }); 313 | 314 | done(result); 315 | } 316 | }, { 317 | key: 'loginButtonSelector', 318 | get: function get() { 319 | return 'input[name="&OK"]'; 320 | } 321 | }]); 322 | 323 | return Report; 324 | }(KintaiClient); 325 | 326 | var StatsReport = function (_Report) { 327 | _inherits(StatsReport, _Report); 328 | 329 | function StatsReport() { 330 | _classCallCheck(this, StatsReport); 331 | 332 | return _possibleConstructorReturn(this, (StatsReport.__proto__ || Object.getPrototypeOf(StatsReport)).apply(this, arguments)); 333 | } 334 | 335 | _createClass(StatsReport, [{ 336 | key: 'collectReport', 337 | value: function collectReport(page, done) { 338 | var result = page.evaluate(function () { 339 | var normalize = function normalize(text) { 340 | return text.replace(/\s+/g, ' ').replace(/^\s*/g, '').replace(/\s*$/g, ''); 341 | }; 342 | 343 | var stats_table = document.querySelectorAll('table')[13]; 344 | var cells = stats_table.querySelectorAll('td'); 345 | var report = {}; 346 | var key = void 0; 347 | Array.prototype.forEach.call(cells, function (cell) { 348 | var v = normalize(cell.textContent); 349 | if (v.match(/^[\d.()\/ ]+$/)) { 350 | var values = v.split(/[()\/ ]/).filter(function (v) { 351 | return v.length > 0; 352 | }).map(function (v) { 353 | return +v; 354 | }); 355 | if (values.length > 1) { 356 | report[key] = values; 357 | } else { 358 | report[key] = values[0]; 359 | } 360 | } else { 361 | key = v; 362 | } 363 | }); 364 | 365 | var collectMonthly = function collectMonthly() { 366 | var days_table = document.querySelector('#DUM_EZZOPCK-0001').parentNode.parentNode.parentNode.parentNode; 367 | 368 | var result = []; 369 | Array.prototype.forEach.call(days_table.children, function (day) { 370 | // tdを含まないtd 371 | var columns = []; 372 | Array.prototype.forEach.call(day.querySelector('tr').querySelectorAll('td'), function (td) { 373 | if (!td.querySelector('td')) { 374 | columns.push(td); 375 | } 376 | }); 377 | 378 | if (columns.length < 10) return; 379 | 380 | // 日付, 打刻開始, 打刻終了, 開始時刻, 終了時刻 381 | var date = normalize(columns[1].textContent); 382 | var up1 = normalize(columns[6].textContent); 383 | var down1 = normalize(columns[7].textContent); 384 | 385 | var up2 = normalize(columns[8].textContent); 386 | var down2 = normalize(columns[9].textContent); 387 | 388 | result.push([date, up1, down1, up2, down2].map(function (v) { 389 | return v.length > 0 ? v : null; 390 | })); 391 | }); 392 | return result; 393 | }; 394 | 395 | report['月報'] = collectMonthly(); 396 | 397 | return JSON.stringify(report); 398 | }); 399 | done(result); 400 | } 401 | }]); 402 | 403 | return StatsReport; 404 | }(Report); 405 | 406 | var system = phantomjsRequire('system'); 407 | 408 | var KintaiServer = function () { 409 | function KintaiServer(KINTAI_URL, KINTAI_STAFF_ID, KINTAI_PASSWORD, API_TOKEN, PORT) { 410 | _classCallCheck(this, KintaiServer); 411 | 412 | this.kintai_url = KINTAI_URL; 413 | this.staff_id = KINTAI_STAFF_ID; 414 | this.password = KINTAI_PASSWORD; 415 | this.api_token = API_TOKEN; 416 | this.port = PORT; 417 | } 418 | 419 | _createClass(KintaiServer, [{ 420 | key: 'run', 421 | value: function run() { 422 | var _this11 = this; 423 | 424 | var WebServer = phantomjsRequire('webserver'); 425 | var server = WebServer.create(); 426 | server.listen(this.port, function (request, response) { 427 | var segments = request.url.split(/\?/); 428 | var path = segments[0]; 429 | var parameters = segments[1]; 430 | 431 | var controllers = { 432 | '/': function _() { 433 | _this11.performRoot(response); 434 | }, 435 | '/up': function up() { 436 | _this11.authorize(parameters, response, function () { 437 | return _this11.performUp(response); 438 | }); 439 | }, 440 | '/down': function down() { 441 | _this11.authorize(parameters, response, function () { 442 | return _this11.performDown(response); 443 | }); 444 | }, 445 | '/report.json': function reportJson() { 446 | _this11.authorize(parameters, response, function () { 447 | return _this11.performStatsReport(response); 448 | }); 449 | }, 450 | '/report.txt': function reportTxt() { 451 | _this11.authorize(parameters, response, function () { 452 | return _this11.performReport(response); 453 | }); 454 | } 455 | }; 456 | 457 | var controller = controllers[path]; 458 | if (!controller) { 459 | _this11.perform404(response); 460 | return; 461 | } 462 | 463 | controller(); 464 | }); 465 | console.log('Server is runnning on ' + this.port + '.'); 466 | } 467 | }, { 468 | key: 'setupCompleted', 469 | value: function setupCompleted() { 470 | return this.kintai_url && this.staff_id && this.password && this.api_token; 471 | } 472 | }, { 473 | key: 'authorize', 474 | value: function authorize(parameters, response, whenSuccess) { 475 | if (parameters !== 'api_token=' + this.api_token) { 476 | response.statusCode = 400; 477 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 478 | response.write('?api_token=*** required'); 479 | response.close(); 480 | return; 481 | } 482 | whenSuccess(); 483 | } 484 | }, { 485 | key: 'performKintai', 486 | value: function performKintai(job, response) { 487 | var _this12 = this; 488 | 489 | if (this.setupCompleted()) { 490 | job.run(function (content) { 491 | _this12.returnsText(content, response); 492 | }); 493 | } else { 494 | response.statusCode = 400; 495 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 496 | response.write('Setup is not completed. Visit /'); 497 | response.close(); 498 | } 499 | } 500 | }, { 501 | key: 'returnsText', 502 | value: function returnsText(content, response) { 503 | response.statusCode = 200; 504 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 505 | response.write(content); 506 | response.close(); 507 | } 508 | }, { 509 | key: 'performRoot', 510 | value: function performRoot(response) { 511 | response.statusCode = 200; 512 | if (this.setupCompleted()) { 513 | response.write('# Kintai API\nWelcome, ' + this.staff_id + '!\n'); 514 | } else { 515 | var message = '# Kintai API\nSet following kintai variables at heroku Dashboard (https://dashboard.heroku.com/).\n\n KINTAI_URL=\'https://***\'\n KINTAI_STAFF_ID=\'***\'\n KINTAI_PASSWORD=\'***\'\n\nAnd you must set the API_TOKEN for authorization.\n\n API_TOKEN=\'***\'\n'; 516 | response.write(message); 517 | } 518 | response.close(); 519 | } 520 | }, { 521 | key: 'performUp', 522 | value: function performUp(response) { 523 | var kintai = new Shukkin(this.kintai_url, this.staff_id, this.password); 524 | this.performKintai(kintai, response); 525 | } 526 | }, { 527 | key: 'performDown', 528 | value: function performDown(response) { 529 | var kintai = new Taikin(this.kintai_url, this.staff_id, this.password); 530 | this.performKintai(kintai, response); 531 | } 532 | }, { 533 | key: 'performReport', 534 | value: function performReport(response) { 535 | var report = new Report(this.kintai_url, this.staff_id, this.password); 536 | this.performKintai(report, response); 537 | } 538 | }, { 539 | key: 'performStatsReport', 540 | value: function performStatsReport(response) { 541 | var report = new StatsReport(this.kintai_url, this.staff_id, this.password); 542 | this.performKintai(report, response); 543 | } 544 | }, { 545 | key: 'perform404', 546 | value: function perform404(response) { 547 | response.statusCode = 404; 548 | response.write('Not Found'); 549 | response.close(); 550 | } 551 | }]); 552 | 553 | return KintaiServer; 554 | }(); 555 | 556 | ; 557 | 558 | var server = new KintaiServer(system.env['KINTAI_URL'], system.env['KINTAI_STAFF_ID'], system.env['KINTAI_PASSWORD'], system.env['API_TOKEN'], system.env['PORT']); 559 | server.run(); 560 | 561 | },{}]},{},[1]); 562 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kintai-api", 3 | "version": "1.0.0", 4 | "description": "https://github.com/hitode909/kintai-api", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "gulp build", 8 | "watch": "gulp watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/hitode909/kintai-api.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/hitode909/kintai-api/issues" 18 | }, 19 | "homepage": "https://github.com/hitode909/kintai-api#readme", 20 | "devDependencies": { 21 | "babel-preset-es2015": "^6.14.0", 22 | "babelify": "^7.3.0", 23 | "browserify": "^13.0.1", 24 | "gulp": "^3.9.1", 25 | "gulp-coffee": "^2.3.2" 26 | }, 27 | "dependencies": { 28 | "coffeeify": "^2.0.1", 29 | "gulp-coffeeify": "^0.1.8", 30 | "vinyl-source-stream": "^1.1.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/kintai.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let phantomjsRequire = require; 4 | 5 | class KintaiClient { 6 | constructor (timecardURL, username, password) { 7 | this.timecardURL = timecardURL; 8 | this.username = username; 9 | this.password = password; 10 | } 11 | 12 | createPage () { 13 | const page = (phantomjsRequire('webpage')).create(); 14 | page.viewportSize = { 15 | width: 1280, 16 | height: 800, 17 | }; 18 | page.onConsoleMessage = msg => console.log(msg); 19 | page.onError = msg => console.log(`in page error: ${msg}`); 20 | return page; 21 | } 22 | 23 | auth (page) { 24 | page.evaluate( 25 | (tupple) => { 26 | const userName = tupple[0]; 27 | const password = tupple[1]; 28 | const userNameElement = document.querySelector('input[type="text"]'); 29 | if (!userNameElement) { throw "userNameElement not found"; } 30 | userNameElement.value = userName; 31 | 32 | const passwordElement = document.querySelector('input[type="password"]'); 33 | if (!passwordElement) { throw "passwordElement not found"; } 34 | passwordElement.value = password; 35 | }, 36 | [this.username, this.password] 37 | ); 38 | } 39 | 40 | getElementCenterPosition (page, selector) { 41 | const position = page.evaluate( 42 | (selectorInPage) => { 43 | const button = document.querySelector(selectorInPage); 44 | if (!button) { throw `selector ${selectorInPage} not found`; } 45 | return button.getBoundingClientRect(); 46 | }, 47 | selector 48 | ); 49 | 50 | return [ 51 | position.left + (position.width * 0.5), 52 | position.top + (position.height * 0.5) 53 | ]; 54 | } 55 | 56 | click (page, position) { 57 | page.sendEvent('click', position[0], position[1]); 58 | } 59 | 60 | 61 | error (message) { 62 | console.error(message); 63 | phantom.exit(1); 64 | } 65 | 66 | getFrameURL (windowID, done) { 67 | const page = this.createPage(); 68 | page.open( 69 | this.timecardURL, 70 | (status) => { 71 | if (status !== 'success') { this.error('Unable to open the page'); } 72 | 73 | const url = page.evaluate(() => document.querySelector('frame[src*="LANSAWEB"]').src); 74 | const urlForID = url.replace(/EW\d{4}/, windowID); 75 | done(urlForID); 76 | } 77 | ); 78 | } 79 | 80 | getDakokuURL (done) { 81 | this.getFrameURL('EW1015', done); 82 | } 83 | 84 | getReportURL (done) { 85 | this.getFrameURL('EW1010', done); 86 | } 87 | } 88 | 89 | class AbstructKintai extends KintaiClient { 90 | run (done) { 91 | this.getDakokuURL((url) => { 92 | const page = this.createPage(); 93 | page.open( 94 | url, 95 | status => { 96 | if (status !== 'success') { this.error('Unable to open the page'); } 97 | 98 | this.perform(page, done); 99 | }); 100 | }); 101 | } 102 | 103 | perform (page, done) { 104 | this.auth(page); 105 | 106 | const position = this.getElementCenterPosition(page, this.buttonSelector); 107 | 108 | this.click(page, position); 109 | 110 | page.onLoadFinished = () => { 111 | const message = page.evaluate(() => (document.querySelector('option')).textContent); 112 | 113 | const report = []; 114 | report.push(`${ this.action }が完了しました`); 115 | report.push(`メッセージ: ${message.trim()}`); 116 | 117 | if (done) { 118 | done(report.join("\n")); 119 | } 120 | }; 121 | } 122 | } 123 | 124 | class Shukkin extends AbstructKintai { 125 | get command () { 126 | return 'up'; 127 | } 128 | get action () { 129 | return '出勤'; 130 | } 131 | get buttonSelector () { 132 | return 'input[src*="start"]'; 133 | } 134 | } 135 | 136 | class Taikin extends AbstructKintai { 137 | get command () { 138 | return 'down'; 139 | } 140 | get action () { 141 | return '退勤'; 142 | } 143 | get buttonSelector () { 144 | return 'input[src*="end"]'; 145 | } 146 | } 147 | 148 | class Report extends KintaiClient { 149 | run (done) { 150 | this.getReportURL((url) => this.gotURL(url, done)); 151 | } 152 | 153 | gotURL (url, done) { 154 | const page = this.createPage(); 155 | 156 | page.open( 157 | url, 158 | status => { 159 | if (status !== "success") { this.error("Unable to open the page"); } 160 | 161 | this.auth(page); 162 | 163 | const position = this.getElementCenterPosition(page, this.loginButtonSelector); 164 | this.click(page, position); 165 | 166 | page.onLoadFinished = () => { 167 | // 月報開くコマンド 168 | page.evaluate( 169 | () => window.GotoMenuItem('EN4020', ' ') 170 | ); 171 | 172 | page.onLoadFinished = () => { 173 | this.collectReport(page, done); 174 | }; 175 | }; 176 | } 177 | ); 178 | } 179 | 180 | collectReport (page, done) { 181 | const result = page.evaluate(() => { 182 | const days_table = document.querySelector('#DUM_EZZOPCK-0001').parentNode.parentNode.parentNode.parentNode; 183 | const normalize = text => (text.replace(/^\s*/, '')).replace(/\s*$/, ''); 184 | 185 | const result = []; 186 | result.push("日付 打刻開始〜打刻終了 (開始時刻〜終了時刻)"); 187 | Array.prototype.forEach.call(days_table.children, day => { 188 | // tdを含まないtd 189 | const columns = []; 190 | Array.prototype.forEach.call(day.querySelector('tr').querySelectorAll('td'), (td) => { 191 | if (!td.querySelector('td')) { 192 | columns.push(td); 193 | } 194 | }); 195 | 196 | if (columns.length < 10) return; 197 | 198 | // 日付, 打刻開始, 打刻終了, 開始時刻, 終了時刻 199 | const date = normalize(columns[1].textContent); 200 | const up1 = normalize(columns[6].textContent); 201 | const down1 = normalize(columns[7].textContent); 202 | 203 | const up2 = normalize(columns[8].textContent); 204 | const down2 = normalize(columns[9].textContent); 205 | 206 | if (up1.length > 0) { 207 | result.push(`${date} ${up1}〜${down1} (${up2}〜${down2})`); 208 | } else { 209 | result.push(date); 210 | } 211 | }); 212 | return result.join("\n"); 213 | }); 214 | 215 | done(result); 216 | } 217 | 218 | get loginButtonSelector () { 219 | return 'input[name="&OK"]'; 220 | } 221 | } 222 | 223 | class StatsReport extends Report { 224 | collectReport (page, done) { 225 | const result = page.evaluate(() => { 226 | const normalize = text => (text.replace(/\s+/g, ' ').replace(/^\s*/g, '')).replace(/\s*$/g, ''); 227 | 228 | const stats_table = document.querySelectorAll('table')[13]; 229 | const cells = stats_table.querySelectorAll('td'); 230 | const report = {}; 231 | let key; 232 | Array.prototype.forEach.call(cells, cell => { 233 | var v = normalize(cell.textContent); 234 | if (v.match(/^[\d.()\/ ]+$/)) { 235 | const values = v.split(/[()\/ ]/).filter(v => v.length > 0).map(v => + v); 236 | if (values.length > 1) { 237 | report[key] = values; 238 | } else { 239 | report[key] = values[0]; 240 | } 241 | } else { 242 | key = v; 243 | } 244 | }); 245 | 246 | const collectMonthly = () => { 247 | const days_table = document.querySelector('#DUM_EZZOPCK-0001').parentNode.parentNode.parentNode.parentNode; 248 | 249 | const result = []; 250 | Array.prototype.forEach.call(days_table.children, day => { 251 | // tdを含まないtd 252 | const columns = []; 253 | Array.prototype.forEach.call(day.querySelector('tr').querySelectorAll('td'), (td) => { 254 | if (!td.querySelector('td')) { 255 | columns.push(td); 256 | } 257 | }); 258 | 259 | if (columns.length < 10) return; 260 | 261 | // 日付, 打刻開始, 打刻終了, 開始時刻, 終了時刻 262 | const date = normalize(columns[1].textContent); 263 | const up1 = normalize(columns[6].textContent); 264 | const down1 = normalize(columns[7].textContent); 265 | 266 | const up2 = normalize(columns[8].textContent); 267 | const down2 = normalize(columns[9].textContent); 268 | 269 | result.push([date, up1, down1, up2, down2].map(v => v.length > 0 ? v : null )); 270 | }); 271 | return result; 272 | }; 273 | 274 | report['月報'] = collectMonthly(); 275 | 276 | return JSON.stringify(report); 277 | }); 278 | done(result); 279 | } 280 | } 281 | 282 | const system = phantomjsRequire('system'); 283 | 284 | class KintaiServer { 285 | constructor (KINTAI_URL, KINTAI_STAFF_ID, KINTAI_PASSWORD, API_TOKEN, PORT) { 286 | this.kintai_url = KINTAI_URL; 287 | this.staff_id = KINTAI_STAFF_ID; 288 | this.password = KINTAI_PASSWORD; 289 | this.api_token = API_TOKEN; 290 | this.port = PORT; 291 | } 292 | 293 | run () { 294 | const WebServer = phantomjsRequire('webserver'); 295 | const server = WebServer.create(); 296 | server.listen( 297 | this.port, 298 | (request, response) => { 299 | const segments = request.url.split(/\?/); 300 | const path = segments[0]; 301 | const parameters = segments[1]; 302 | 303 | const controllers = { 304 | '/': () => { 305 | this.performRoot(response); 306 | }, 307 | '/up': () => { 308 | this.authorize(parameters, response, () => this.performUp(response)); 309 | }, 310 | '/down': () => { 311 | this.authorize(parameters, response, () => this.performDown(response)); 312 | }, 313 | '/report.json': () => { 314 | this.authorize(parameters, response, () => this.performStatsReport(response)); 315 | }, 316 | '/report.txt': () => { 317 | this.authorize(parameters, response, () => this.performReport(response)); 318 | }, 319 | }; 320 | 321 | const controller = controllers[path]; 322 | if (!controller) { 323 | this.perform404(response); 324 | return; 325 | } 326 | 327 | controller(); 328 | } 329 | ); 330 | console.log(`Server is runnning on ${this.port}.`); 331 | } 332 | 333 | setupCompleted () { 334 | return this.kintai_url && this.staff_id && this.password && this.api_token; 335 | } 336 | 337 | authorize (parameters, response, whenSuccess) { 338 | if (parameters !== `api_token=${this.api_token}`) { 339 | response.statusCode = 400; 340 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 341 | response.write('?api_token=*** required'); 342 | response.close(); 343 | return; 344 | } 345 | whenSuccess(); 346 | } 347 | 348 | performKintai (job, response) { 349 | if (this.setupCompleted()) { 350 | job.run((content) => { 351 | this.returnsText(content, response); 352 | }); 353 | } else { 354 | response.statusCode = 400; 355 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 356 | response.write('Setup is not completed. Visit /'); 357 | response.close(); 358 | } 359 | } 360 | 361 | returnsText (content, response) { 362 | response.statusCode = 200; 363 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); 364 | response.write(content); 365 | response.close(); 366 | } 367 | 368 | performRoot (response) { 369 | response.statusCode = 200; 370 | if (this.setupCompleted()) { 371 | response.write(`# Kintai API 372 | Welcome, ${this.staff_id}! 373 | `); 374 | } else { 375 | const message = `# Kintai API 376 | Set following kintai variables at heroku Dashboard (https://dashboard.heroku.com/). 377 | 378 | KINTAI_URL='https://***' 379 | KINTAI_STAFF_ID='***' 380 | KINTAI_PASSWORD='***' 381 | 382 | And you must set the API_TOKEN for authorization. 383 | 384 | API_TOKEN='***' 385 | `; 386 | response.write(message); 387 | } 388 | response.close(); 389 | } 390 | 391 | performUp (response) { 392 | const kintai = new Shukkin(this.kintai_url, this.staff_id, this.password); 393 | this.performKintai(kintai, response); 394 | } 395 | 396 | performDown (response) { 397 | const kintai = new Taikin(this.kintai_url, this.staff_id, this.password); 398 | this.performKintai(kintai, response); 399 | } 400 | 401 | performReport (response) { 402 | const report = new Report(this.kintai_url, this.staff_id, this.password); 403 | this.performKintai(report, response); 404 | } 405 | 406 | performStatsReport (response) { 407 | const report = new StatsReport(this.kintai_url, this.staff_id, this.password); 408 | this.performKintai(report, response); 409 | } 410 | 411 | perform404 (response) { 412 | response.statusCode = 404; 413 | response.write('Not Found'); 414 | response.close(); 415 | } 416 | }; 417 | 418 | const server = new KintaiServer(system.env['KINTAI_URL'], system.env['KINTAI_STAFF_ID'], system.env['KINTAI_PASSWORD'], system.env['API_TOKEN'], system.env['PORT']); 419 | server.run(); 420 | --------------------------------------------------------------------------------