@sebbo2002/iubh-campus-sync 4.0.0-develop.6 → 4.0.1-develop.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sync.d.ts CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+
2
+ export { }
package/dist/sync.js CHANGED
@@ -1,45 +1,9 @@
1
- 'use strict';
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- import Database from './database.js';
12
- import SyncCampus from './sync-campus.js';
13
- class Sync {
14
- static run() {
15
- return __awaiter(this, void 0, void 0, function* () {
16
- const sync = new Sync({
17
- cwd: process.env.SYNC_PATH || process.cwd(),
18
- username: process.env.SYNC_USERNAME || '',
19
- password: process.env.SYNC_PASSWORD || ''
20
- });
21
- yield sync.run();
22
- });
23
- }
24
- constructor(options) {
25
- this.database = new Database(options.cwd);
26
- this.options = options;
27
- if (!this.options.username || !this.options.password) {
28
- throw new Error('Username or password empty, please check environment variables.');
29
- }
30
- }
31
- run() {
32
- return __awaiter(this, void 0, void 0, function* () {
33
- yield SyncCampus.run({
34
- username: this.options.username,
35
- password: this.options.password,
36
- database: this.database
37
- });
38
- });
39
- }
40
- }
41
- Sync.run().catch((error) => {
42
- console.log(error);
43
- process.exit(1);
44
- });
1
+ var l=(d,t,i)=>new Promise((a,n)=>{var e=o=>{try{r(i.next(o))}catch(c){n(c)}},s=o=>{try{r(i.throw(o))}catch(c){n(c)}},r=o=>o.done?a(o.value):Promise.resolve(o.value).then(e,s);r((i=i.apply(d,t)).next())});import{existsSync as m,readFileSync as $,writeFileSync as L,createReadStream as I}from"fs";import{mkdir as F,readdir as P,readFile as N,writeFile as x}from"fs/promises";import{createHash as B}from"crypto";import{join as u,dirname as T,extname as U,basename as H,resolve as k,relative as R}from"path";var p=class{constructor(t){if(this.cwd=t,!m(t))throw new Error(`Unable to load database: Working directory (${t}) does not exist!`);this.paths={[0]:this.cwd,[2]:u(this.cwd,"Infos & Organisatorisches"),[1]:u(this.cwd,"Abgeschlossene Module")},this.path=u(t,".iubh-campus-sync.db"),this.activities=[],m(this.path)?this.load():this.saveSync()}load(){let t=JSON.parse($(this.path,{encoding:"utf8"}));if(t.version!==1)throw new Error("Invalid db version: unable to continue.");Array.isArray(t.activities)&&t.activities.forEach(i=>this.activities.push({id:i[0],fingerprint:i[1],hash:i[2],path:k(this.cwd,i[3])}))}toJSON(){return{version:1,activities:this.activities.map(t=>[t.id,t.fingerprint,t.hash,R(this.cwd,t.path)])}}save(){return l(this,null,function*(){yield x(this.path,JSON.stringify(this.toJSON(),null," "))})}saveSync(){L(this.path,JSON.stringify(this.toJSON(),null," "))}findOrCreateFolder(n,e){return l(this,arguments,function*(t,i,a=this.cwd){let s=yield this.findFolder(t);if(s)return s;m(a)||(yield F(a));let r=yield this.getConflictFreeFileName(u(a,i));yield F(r);let o=u(r,".iubh-campus-sync-folder");return yield x(o,t+`
2
+ `),r})}findFolder(t,i=null){return l(this,null,function*(){if(!i)return(yield Promise.all(Object.values(this.paths).map(s=>this.findFolder(t,s)))).find(Boolean)||null;if(!m(i))return null;let a=yield P(i,{withFileTypes:!0});return(yield Promise.all(a.map(e=>l(this,null,function*(){if(e.isDirectory())return this.findFolder(t,u(i,e.name));if(e.name!==".iubh-campus-sync-folder"||!e.isFile())return null;let s=u(i,e.name);return(yield N(s,{encoding:"utf8"})).split(`
3
+ `)[0].trim()===t?i:null})))).find(Boolean)||null})}getLocalActivityInfo(t){return l(this,null,function*(){let i=this.activities.find(r=>r.id===t.id);if(!i)return{entryExists:!1,fileExists:!1,filePath:null,changedOnRemote:null,changedLocally:null};let a={entryExists:!0,fileExists:!1,filePath:null,changedOnRemote:!!(t.fingerprint()&&i.fingerprint!==t.fingerprint()),changedLocally:null},n=!1,e=i.path;if((!e||!m(e))&&(e=yield this.searchFileByHash(i.hash),n=!0),!e)return a;yield this.updateLocalActivityFile(t,e),a.fileExists=!0,a.filePath=e,a.changedLocally=!1;let s=yield this.getHashByFile(e);if(i.hash!==s&&(a.changedLocally=!0,!n)){let r=yield this.searchFileByHash(i.hash);r&&(a.filePath=r,a.changedLocally=!1,yield this.updateLocalActivityFile(t,r))}return a})}searchFileByHash(t,i){return l(this,null,function*(){if(!i){let n=Object.values(this.paths);for(let e in n){let s=n[e],r=yield this.searchFileByHash(t,s);if(r)return r}return null}if(!m(i))return null;let a=yield P(i,{withFileTypes:!0});for(let n in a){let e=a[n],s=u(i,e.name);if(e.isDirectory()){let o=yield this.searchFileByHash(t,s);if(o)return o}if(!e.isFile()||e.name.substr(0,1)===".")continue;if((yield this.getHashByFile(s))===t)return s}return null})}getHashByFile(t){return l(this,null,function*(){let i=B("sha1");i.setEncoding("hex");let a=I(t),n=new Promise(e=>{a.on("end",()=>{i.end(),e(i.read().toString())})});return a.pipe(i),n})}updateLocalActivityFile(t,i,a){return l(this,null,function*(){let n=this.activities.find(s=>s.id===t.id),e=t.fingerprint();a||(a=yield this.getHashByFile(i)),n?(n.fingerprint=e,n.hash=a,n.path=i):this.activities.push({id:t.id,fingerprint:e,hash:a,path:i}),yield this.save()})}getConflictFreeFileName(t){return l(this,null,function*(){return p.getConflictFreeFileName(t)})}static getConflictFreeFileName(t){return l(this,null,function*(){let i=U(t),a=t;for(let n=1;m(a);n++)a=u(T(t),H(t,i)+"-"+n+i);return a})}};import _ from"puppeteer";var f=class{constructor(t,i){this.data=t,this.activities=i}get id(){return this.data.id}get url(){return this.data.url}get name(){return this.data.name}};import j from"cheerio";import W from"node-fetch";import{join as J}from"path";import{promisify as q}from"util";import{pipeline as z}from"stream";import{createWriteStream as V}from"fs";import{encode as Y}from"es-cookie";var g=class{constructor(t,i){this.myCampus=t,this.data=i,this.$=j.load(i.html)}get id(){return this.data.id}get url(){return this.data.url}get name(){return this.data.name}get type(){return this.data.type}toJSON(){return Object.assign({},this.data)}fingerprint(){var t;return this.type==="resource"?(t=this.$(".resourcelinkdetails"))==null?void 0:t.text():null}isDownloadable(){var t;return this.type==="resource"?!!((t=this.$("a"))!=null&&t.attr("href")):!1}download(t){return l(this,null,function*(){var i;if(this.type==="resource"){let a=(i=this.$("a"))==null?void 0:i.attr("href");if(!a)throw new Error("Unable to download file: URL not found.");return this.downloadWithCookies(t,a)}throw new Error(`Unable to download ${this.type}: not implemented yet.`)})}downloadWithCookies(t,i){return l(this,null,function*(){if(!this.myCampus.page)throw new Error("Unable to get browser cookies: Is the page initialized?");let n={Cookie:(yield this.myCampus.page.cookies(i)).map(v=>Y(v.name,v.value,{})).join("; ")},e=yield W(i,{headers:n});if(console.log(`> Response: ${e.status} ${e.statusText}`),!e.ok)throw new Error(`Unexpected response: ${e.statusText}`);if(e.body===null)throw new Error("Unexpected response: body is empty");let s=e.headers.get("content-type");if(s!=null&&s.startsWith("text/html;"))throw console.log("> HTML Output:",yield e.text()),new Error("Unexpected response: server replied with html");let r=e.headers.get("content-disposition");if(!(r!=null&&r.startsWith("attachment; filename=")))throw console.log("> Content-Disposition:",r),new Error("Unexpected response: server replied without attachement");let o=r.substr(21).replace(/[a-z\d_\-, /]+/i,"").trim(),c=J(t,o),D=q(z),b=V(c);return yield Promise.all([new Promise(v=>b.on("finish",v)),D(e.body,b)]),c})}};var y=class{constructor(t,i){this.myCampus=t,this.data=i}get id(){return this.data.id}get name(){return this.data.name||null}get status(){return this.data.current?0:this.data.info?2:1}get url(){return this.data.url}toString(){return`MyCampusCourse<${this.id}>`}getSections(){return l(this,null,function*(){if(!this.myCampus.browser)throw new Error("Unable to fetch course details: please run start() first!");let t=yield this.myCampus.browser.newPage();yield t.goto(this.url);try{yield t.waitForSelector("#region-main ul.topics")}catch(e){return[]}let i=yield t.$$eval("ul.topics li.section",e=>e.map(s=>{var r;return{id:s.id,url:"#"+s.id,name:((r=s.querySelector(".sectionname"))==null?void 0:r.textContent)||s.id,activities:Array.from(s.querySelectorAll(".section li.activity")).map(o=>o.id)}})),a=yield t.$$eval("ul.topics ul.section li.activity",e=>e.map(s=>{var o,c;let r=(s.getAttribute("class")||"").split(" ").filter(Boolean);return{id:s.id,url:"#"+s.id,name:((c=(o=s.querySelector(".instancename"))==null?void 0:o.innerText)==null?void 0:c.split(`
4
+ `)[0])||null,classes:r,type:r.length>=2?r[1]:null,html:s.innerHTML}})),n=i.map(e=>{let s=e.activities.map(r=>{let o=a.find(c=>c.id===r);if(o)return o.url=this.url+o.url,new g(this.myCampus,o)}).filter(Boolean);return e.url=this.url+e.url,new f(e,s)});return yield t.close(),n})}};var w=class{constructor(){this.screenshotTime=new Date().getTime();this.screenshotIndex=0}start(t,i){return l(this,null,function*(){this.browser=yield _.launch(),this.page=yield this.browser.newPage(),yield this.page.goto("https://mycampus.iubh.de/my/");let a=yield this.page.waitForSelector("#username");if(!a)throw new Error("Unable to login: not able to find username field");yield a.type(t);let n=yield this.page.waitForSelector("#password");if(!n)throw new Error("Unable to login: not able to find password field");yield n.type(i),yield n.press("Enter");try{yield this.page.waitForSelector("#page-my-index")}catch(e){yield this.page.reload({waitUntil:["networkidle0","domcontentloaded"]}),yield this.page.waitForSelector("#page-my-index")}})}stop(){return l(this,null,function*(){this.browser&&(yield this.browser.close(),delete this.browser,delete this.page)})}screenshot(){return l(this,arguments,function*(t=this.page){t&&(yield t.screenshot({path:this.screenshotTime+"-"+this.screenshotIndex.toString().padStart(3,"0")+".png"}))})}getCourses(){return l(this,null,function*(){if(!this.page)throw new Error("Unable to get courses: please run start() first!");yield this.page.goto("https://mycampus.iubh.de/my/"),yield this.page.waitForSelector(".courselist"),yield this.page.waitForSelector(".courselist");let t=yield this.page.$$eval("#courses-active .shortname",n=>n.map(e=>e.textContent||"").filter(Boolean)),i=yield this.page.$$eval("#courses-intro .shortname",n=>n.map(e=>e.textContent||"").filter(Boolean));return(yield this.page.$$eval(".courseitem",n=>n.map(e=>{var r,o;let s={url:e.getAttribute("href"),id:(r=e.querySelector(".shortname"))==null?void 0:r.textContent,name:(o=e.querySelector(".fullname"))==null?void 0:o.textContent};if(!s.id)throw new Error("Course ID not found.");if(!s.url)throw new Error("Course URL not found.");return s}))).map(n=>new y(this,{id:n.id,name:n.name||void 0,current:n.id?t.includes(n.id):!1,info:n.id?i.includes(n.id):!1,url:n.url}))})}getOrgaDocuments(){return l(this,null,function*(){})}};import{join as S,dirname as G,basename as E,extname as M}from"path";import{rename as A}from"fs/promises";var h=class{static run(t){return l(this,null,function*(){yield new h(t).run()})}constructor(t){this.username=t.username,this.password=t.password,this.database=t.database,this.myCampus=new w}run(){return l(this,null,function*(){yield this.myCampus.start(this.username,this.password);try{yield this.syncCourses(),yield this.myCampus.stop()}catch(t){throw yield this.myCampus.screenshot(),yield this.myCampus.stop(),t}})}syncCourses(){return l(this,null,function*(){let t=yield this.myCampus.getCourses();for(let i in t){let a=t[i];if(["id=1844","id=2567","id=2566","id=4893","id=1208","id=2565","id=6157"].find(s=>a.url.endsWith(s)))continue;let n=this.database.paths[a.status],e=yield this.database.findOrCreateFolder("course-"+a.id,a.name||a.id,n);try{yield this.syncCourse(a,e)}catch(s){console.log(`Unable to sync course ${a.url}:`),console.log(s instanceof Error?s.stack:s)}}})}syncCourse(t,i){return l(this,null,function*(){let a=yield t.getSections();for(let n in a){let e=a[n];if(!e.activities.find(r=>r.isDownloadable()))continue;let s=yield this.database.findOrCreateFolder("course-"+t.id+"/section-"+e.id,e.name||t.id,i);yield this.syncSection(t,e,s)}})}syncSection(t,i,a){return l(this,null,function*(){for(let n in i.activities){let e=i.activities[n];if(!e.isDownloadable())continue;let s=yield this.database.getLocalActivityInfo(e);if(!s.entryExists||!s.fileExists){console.log(`${t.name||t.id} / ${i.name||i.id} / ${e.name||e.id} (${e.id})`),console.log("> File does not exist, download it\u2026");let r=yield e.download(a);if(!e.name){yield this.database.updateLocalActivityFile(e,r),console.log("> Done, file saved at"+r+`
5
+ `);continue}let o=e.name.replace(/[^a-z0-9-_äüöß.a ()\[]/gi,"_").replace(/"/g,"")+M(r).replace(/[^a-z0-9.]/gi,""),c=yield this.database.getConflictFreeFileName(S(a,o));console.log(`> Download of ${E(r)} complete`),console.log(`> Rename file to ${o}`),yield A(r,c),yield this.database.updateLocalActivityFile(e,c),console.log(`> Done!
6
+ `)}else if(s.changedOnRemote&&s.changedLocally&&s.filePath){console.log(`${t.name||t.id} / ${i.name||i.id} / ${e.name||e.id}`),console.log("> File changed on MyCampus and locally, rename local one and download update\u2026");let r=M(s.filePath),o=yield this.database.getConflictFreeFileName(S(G(s.filePath),E(s.filePath,r)+".local"+r));yield A(s.filePath,o),console.log("> File renamed to",o);let c=yield e.download(a);yield this.database.updateLocalActivityFile(e,c),console.log("> Downloaded new file at "+c+`
7
+ `)}else if(s.changedOnRemote){console.log(`${t.name||t.id} / ${i.name||i.id} / ${e.name||e.id}`),console.log("> File changed on MyCampus, update it\u2026");let r=yield e.download(a);yield this.database.updateLocalActivityFile(e,r),console.log("> Done, file saved at "+r+`
8
+ `)}}})}};var C=class{static run(){return l(this,null,function*(){yield new C({cwd:process.env.SYNC_PATH||process.cwd(),username:process.env.SYNC_USERNAME||"",password:process.env.SYNC_PASSWORD||""}).run()})}constructor(t){if(this.database=new p(t.cwd),this.options=t,!this.options.username||!this.options.password)throw new Error("Username or password empty, please check environment variables.")}run(){return l(this,null,function*(){yield h.run({username:this.options.username,password:this.options.password,database:this.database})})}};C.run().catch(d=>{console.log(d),process.exit(1)});
45
9
  //# sourceMappingURL=sync.js.map
package/dist/sync.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;AAGb,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAE1C,MAAM,IAAI;IACN,MAAM,CAAO,GAAG;;YACZ,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC;gBAClB,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE;gBAC3C,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE;gBACzC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE;aAC5C,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,CAAC;KAAA;IAKD,YAAY,OAAoB;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,IAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YACjD,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;SACtF;IACL,CAAC;IAEK,GAAG;;YACL,MAAM,UAAU,CAAC,GAAG,CAAC;gBACjB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;aAC1B,CAAC,CAAC;QACP,CAAC;KAAA;CACJ;AAED,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;IAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"sources":["../src/database.ts","../src/campus.ts","../src/campus-section.ts","../src/campus-activity.ts","../src/campus-course.ts","../src/sync-campus.ts","../src/sync.ts"],"sourcesContent":["import {existsSync, readFileSync, writeFileSync, createReadStream} from 'fs';\nimport {mkdir, readdir, readFile, writeFile} from 'fs/promises';\nimport {createHash} from 'crypto';\nimport {join, dirname, extname, basename, resolve, relative} from 'path';\nimport {MyCampusCourseStatus} from './types.js';\nimport MyCampusActivity from './campus-activity.js';\n\nexport interface DatabaseActivityEntry {\n id: string;\n fingerprint: string | null;\n hash: string;\n path: string;\n}\n\nexport interface DatabaseActivityInfo {\n entryExists: boolean;\n fileExists: boolean;\n filePath: string | null;\n changedOnRemote: boolean | null;\n changedLocally: boolean | null;\n}\n\nexport default class Database {\n private readonly path: string;\n\n readonly cwd: string;\n readonly activities: DatabaseActivityEntry[];\n readonly paths: { [MyCampusCourseStatus.ACTIVE]: string, [MyCampusCourseStatus.INFO]: string, [MyCampusCourseStatus.COMPLETED]: string };\n\n constructor(cwd: string) {\n this.cwd = cwd;\n if (!existsSync(cwd)) {\n throw new Error(`Unable to load database: Working directory (${cwd}) does not exist!`);\n }\n\n this.paths = {\n [MyCampusCourseStatus.ACTIVE]: this.cwd,\n [MyCampusCourseStatus.INFO]: join(this.cwd, 'Infos & Organisatorisches'),\n [MyCampusCourseStatus.COMPLETED]: join(this.cwd, 'Abgeschlossene Module')\n };\n\n this.path = join(cwd, '.iubh-campus-sync.db');\n this.activities = [];\n\n if (existsSync(this.path)) {\n this.load();\n }\n else {\n this.saveSync();\n }\n }\n\n load(): void {\n const json = JSON.parse(readFileSync(this.path, {encoding: 'utf8'}));\n if(json.version !== 1) {\n throw new Error('Invalid db version: unable to continue.');\n }\n\n if(Array.isArray(json.activities)) {\n json.activities.forEach((activity: string[]) => this.activities.push({\n id: activity[0],\n fingerprint: activity[1],\n hash: activity[2],\n path: resolve(this.cwd, activity[3])\n }));\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n 'version': 1,\n 'activities': this.activities.map(activitiy => ([\n activitiy.id,\n activitiy.fingerprint,\n activitiy.hash,\n relative(this.cwd, activitiy.path)\n ]))\n };\n }\n\n async save(): Promise<void> {\n await writeFile(this.path, JSON.stringify(this.toJSON(), null, ' '));\n }\n\n saveSync(): void {\n writeFileSync(this.path, JSON.stringify(this.toJSON(), null, ' '));\n }\n\n async findOrCreateFolder(id: string, name: string, folder: string = this.cwd): Promise<string> {\n const existingFolder = await this.findFolder(id);\n if (existingFolder) {\n return existingFolder;\n }\n if (!existsSync(folder)) {\n await mkdir(folder);\n }\n\n const folderPath = await this.getConflictFreeFileName(join(folder, name));\n await mkdir(folderPath);\n\n const idFilePath = join(folderPath, '.iubh-campus-sync-folder');\n await writeFile(idFilePath, id + '\\n');\n return folderPath;\n }\n\n async findFolder(id: string, folder: string | null = null): Promise<string | null> {\n if (!folder) {\n const results = await Promise.all(Object.values(this.paths).map(path => this.findFolder(id, path)));\n return results.find(Boolean) || null;\n }\n if (!existsSync(folder)) {\n return null;\n }\n\n const files = await readdir(folder, {withFileTypes: true});\n const results = await Promise.all(files.map(async file => {\n if(file.isDirectory()) {\n return this.findFolder(id, join(folder, file.name));\n }\n if (file.name !== '.iubh-campus-sync-folder' || !file.isFile()) {\n return null;\n }\n\n const path = join(folder, file.name);\n const content = await readFile(path, {encoding: 'utf8'});\n const savedId = content.split('\\n')[0].trim();\n if (savedId === id) {\n return folder;\n }\n\n return null;\n }));\n\n return results.find(Boolean) || null;\n }\n\n async getLocalActivityInfo(activity: MyCampusActivity): Promise<DatabaseActivityInfo> {\n const entry = this.activities.find(entry => entry.id === activity.id);\n if(!entry) {\n return {\n entryExists: false,\n fileExists: false,\n filePath: null,\n changedOnRemote: null,\n changedLocally: null\n };\n }\n\n const result: DatabaseActivityInfo = {\n entryExists: true,\n fileExists: false,\n filePath: null,\n changedOnRemote: !!(activity.fingerprint() && entry.fingerprint !== activity.fingerprint()),\n changedLocally: null\n };\n\n // file exists?\n let foundByHash = false;\n let filePath: string|null = entry.path;\n if(!filePath || !existsSync(filePath)) {\n // no? is the file somewhere else?\n filePath = await this.searchFileByHash(entry.hash);\n foundByHash = true;\n }\n if(!filePath) {\n return result;\n }\n\n await this.updateLocalActivityFile(activity, filePath);\n result.fileExists = true;\n result.filePath = filePath;\n result.changedLocally = false;\n\n // file fingerprint matches?\n const actualHash = await this.getHashByFile(filePath);\n if(entry.hash !== actualHash) {\n result.changedLocally = true;\n\n if(!foundByHash) {\n const newFilePath = await this.searchFileByHash(entry.hash);\n if(newFilePath) {\n result.filePath = newFilePath;\n result.changedLocally = false;\n await this.updateLocalActivityFile(activity, newFilePath);\n }\n }\n }\n\n return result;\n }\n\n async searchFileByHash(hash: string, path?: string): Promise<string|null> {\n if (!path) {\n const paths = Object.values(this.paths);\n for(const i in paths) {\n const path = paths[i];\n const result = await this.searchFileByHash(hash, path);\n if(result) {\n return result;\n }\n }\n\n return null;\n }\n if (!existsSync(path)) {\n return null;\n }\n\n const files = await readdir(path, {withFileTypes: true});\n for(const i in files) {\n const file = files[i];\n const filePath = join(path, file.name);\n if(file.isDirectory()) {\n const r = await this.searchFileByHash(hash, filePath);\n if(r) {\n return r;\n }\n }\n if(!file.isFile() || file.name.substr(0, 1) === '.') {\n continue;\n }\n\n const fileHash = await this.getHashByFile(filePath);\n if(fileHash === hash) {\n return filePath;\n }\n }\n\n return null;\n }\n\n async getHashByFile(path: string): Promise<string> {\n const hash = createHash('sha1');\n hash.setEncoding('hex');\n\n const fd = createReadStream(path);\n const promise = new Promise(resolve => {\n fd.on('end', () => {\n hash.end();\n resolve(hash.read().toString());\n });\n }) as Promise<string>;\n\n fd.pipe(hash);\n return promise;\n }\n\n async updateLocalActivityFile(activity: MyCampusActivity, path: string, hash?: string): Promise<void> {\n const entry = this.activities.find(entry => entry.id === activity.id);\n const fingerprint = activity.fingerprint();\n if(!hash) {\n hash = await this.getHashByFile(path);\n }\n\n if(!entry) {\n this.activities.push({\n id: activity.id,\n fingerprint,\n hash,\n path\n });\n }else{\n entry.fingerprint = fingerprint;\n entry.hash = hash;\n entry.path = path;\n }\n\n await this.save();\n }\n\n async getConflictFreeFileName(filePath: string): Promise<string> {\n return Database.getConflictFreeFileName(filePath);\n }\n\n static async getConflictFreeFileName(filePath: string): Promise<string> {\n const extension = extname(filePath);\n let newPath = filePath;\n\n for(let i = 1; existsSync(newPath); i++) {\n newPath = join(dirname(filePath), basename(filePath, extension) + '-' + i + extension);\n }\n\n return newPath;\n }\n}\n","import puppeteer, { Browser, Page } from 'puppeteer';\nimport MyCampusCourse from './campus-course.js';\n\nexport default class MyCampus {\n public browser: Browser | undefined;\n public page: Page | undefined;\n\n private screenshotTime = new Date().getTime();\n private screenshotIndex = 0;\n\n async start(username: string, password: string): Promise<void> {\n this.browser = await puppeteer.launch();\n this.page = await this.browser.newPage();\n await this.page.goto('https://mycampus.iubh.de/my/');\n\n const $usernameInput = await this.page.waitForSelector('#username');\n if(!$usernameInput) {\n throw new Error('Unable to login: not able to find username field');\n }\n\n await $usernameInput.type(username);\n\n const $passwordInput = await this.page.waitForSelector('#password');\n if(!$passwordInput) {\n throw new Error('Unable to login: not able to find password field');\n }\n\n await $passwordInput.type(password);\n await $passwordInput.press('Enter');\n\n try {\n await this.page.waitForSelector('#page-my-index');\n }\n catch(error) {\n await this.page.reload({\n waitUntil: ['networkidle0', 'domcontentloaded']\n });\n\n await this.page.waitForSelector('#page-my-index');\n }\n }\n\n async stop (): Promise<void> {\n if(this.browser) {\n await this.browser.close();\n delete this.browser;\n delete this.page;\n }\n }\n\n async screenshot (page: Page | undefined = this.page): Promise<void> {\n if(page) {\n await page.screenshot({\n path: this.screenshotTime + '-' + this.screenshotIndex.toString().padStart(3, '0') + '.png'\n });\n }\n }\n\n async getCourses(): Promise<MyCampusCourse[]> {\n if(!this.page) {\n throw new Error('Unable to get courses: please run start() first!');\n }\n\n await this.page.goto('https://mycampus.iubh.de/my/');\n await this.page.waitForSelector('.courselist');\n\n await this.page.waitForSelector('.courselist');\n\n const activeCourses = await this.page.$$eval('#courses-active .shortname', cs => cs.map(c =>\n c.textContent || '').filter(Boolean)\n );\n const infoCourses = await this.page.$$eval('#courses-intro .shortname', cs => cs.map(c =>\n c.textContent || '').filter(Boolean)\n );\n\n const courses = await this.page.$$eval('.courseitem', items => items.map(item => {\n const data = {\n url: item.getAttribute('href'),\n id: item.querySelector('.shortname')?.textContent,\n name: item.querySelector('.fullname')?.textContent\n };\n\n if(!data.id) {\n throw new Error('Course ID not found.');\n }\n if(!data.url) {\n throw new Error('Course URL not found.');\n }\n\n return data;\n }));\n\n return courses.map(course => {\n return new MyCampusCourse(this, {\n id: course.id as string,\n name: course.name || undefined,\n current: course.id ? activeCourses.includes(course.id) : false,\n info: course.id ? infoCourses.includes(course.id) : false,\n url: course.url as string\n });\n });\n }\n\n async getOrgaDocuments(): Promise<void> {\n // @todo\n }\n}\n","import MyCampusActivity from './campus-activity.js';\n\nexport interface MyCampusSectionConstructorData {\n id: string;\n url: string;\n name: string | null;\n}\n\nexport default class MyCampusSection {\n private readonly data: MyCampusSectionConstructorData;\n public readonly activities: MyCampusActivity[];\n\n constructor(data: MyCampusSectionConstructorData, activities: MyCampusActivity[]) {\n this.data = data;\n this.activities = activities;\n }\n\n get id(): string {\n return this.data.id;\n }\n\n get url(): string {\n return this.data.url;\n }\n\n get name(): string | null {\n return this.data.name;\n }\n}\n","import MyCampus from './campus.js';\nimport cheerio from 'cheerio';\nimport fetch from 'node-fetch';\nimport {join} from 'path';\nimport {promisify} from 'util';\nimport {pipeline} from 'stream';\nimport {createWriteStream} from 'fs';\nimport {encode} from 'es-cookie';\n\nexport interface MyCampusActivityConstructorData {\n id: string;\n url: string;\n name: string | null;\n classes: string[];\n type: string | null;\n html: string;\n}\n\nexport default class MyCampusActivity {\n private readonly myCampus: MyCampus;\n private readonly data: MyCampusActivityConstructorData;\n private readonly $;\n\n constructor(myCampus: MyCampus, data: MyCampusActivityConstructorData) {\n this.myCampus = myCampus;\n this.data = data;\n this.$ = cheerio.load(data.html);\n }\n\n get id(): string {\n return this.data.id;\n }\n\n get url(): string {\n return this.data.url;\n }\n\n get name(): string | null {\n return this.data.name;\n }\n\n get type(): string | null {\n return this.data.type;\n }\n\n toJSON(): MyCampusActivityConstructorData {\n return Object.assign({}, this.data);\n }\n\n fingerprint(): string | null {\n if (this.type === 'resource') {\n return this.$('.resourcelinkdetails')?.text();\n }\n\n return null;\n }\n\n isDownloadable(): boolean {\n if (this.type === 'resource') {\n return !!this.$('a')?.attr('href');\n }\n\n return false;\n }\n\n async download(path: string): Promise<string> {\n if (this.type === 'resource') {\n const url = this.$('a')?.attr('href');\n if (!url) {\n throw new Error('Unable to download file: URL not found.');\n }\n\n return this.downloadWithCookies(path, url);\n }\n\n throw new Error(`Unable to download ${this.type}: not implemented yet.`);\n }\n\n private async downloadWithCookies(path: string, url: string): Promise<string> {\n if (!this.myCampus.page) {\n throw new Error('Unable to get browser cookies: Is the page initialized?');\n }\n\n const cookies = await this.myCampus.page.cookies(url);\n const headers = {\n Cookie: cookies\n .map(cookie => encode(cookie.name, cookie.value, {}))\n .join('; ')\n };\n\n const response = await fetch(url, {headers});\n console.log(`> Response: ${response.status} ${response.statusText}`);\n\n if (!response.ok) {\n throw new Error(`Unexpected response: ${response.statusText}`);\n }\n if (response.body === null) {\n throw new Error('Unexpected response: body is empty');\n }\n\n const contentType = response.headers.get('content-type');\n if(contentType?.startsWith('text/html;')) {\n console.log('> HTML Output:', await response.text());\n throw new Error('Unexpected response: server replied with html');\n }\n\n const disposition = response.headers.get('content-disposition');\n if(!disposition?.startsWith('attachment; filename=')) {\n console.log('> Content-Disposition:', disposition);\n throw new Error('Unexpected response: server replied without attachement');\n }\n\n const fileName = disposition.substr(21).replace(/[a-z\\d_\\-, /]+/i, '').trim();\n const filePath = join(path, fileName);\n const streamPipeline = promisify(pipeline);\n const writeStream = createWriteStream(filePath);\n await Promise.all([\n new Promise(cb => writeStream.on('finish', cb)),\n streamPipeline(response.body, writeStream)\n ]);\n\n return filePath;\n }\n}\n","import {MyCampusCourseStatus} from './types.js';\nimport MyCampus from './campus.js';\nimport MyCampusSection from './campus-section.js';\nimport MyCampusActivity from './campus-activity.js';\n\nexport interface MyCampusCourseConstructorData {\n id: string;\n name?: string;\n current: boolean;\n info: boolean;\n url: string;\n}\n\nexport default class MyCampusCourse {\n private myCampus: MyCampus;\n private data: MyCampusCourseConstructorData;\n\n constructor(myCampus: MyCampus, data: MyCampusCourseConstructorData) {\n this.myCampus = myCampus;\n this.data = data;\n }\n\n get id(): string {\n return this.data.id;\n }\n\n get name(): string | null {\n return this.data.name || null;\n }\n\n get status(): MyCampusCourseStatus {\n if (this.data.current) {\n return MyCampusCourseStatus.ACTIVE;\n }\n else if (this.data.info) {\n return MyCampusCourseStatus.INFO;\n }\n else {\n return MyCampusCourseStatus.COMPLETED;\n }\n }\n\n get url(): string {\n return this.data.url;\n }\n\n toString(): string {\n return `MyCampusCourse<${this.id}>`;\n }\n\n async getSections(): Promise<MyCampusSection[]> {\n if (!this.myCampus.browser) {\n throw new Error('Unable to fetch course details: please run start() first!');\n }\n\n const page = await this.myCampus.browser.newPage();\n await page.goto(this.url);\n\n try {\n await page.waitForSelector('#region-main ul.topics');\n }\n catch(error) {\n return [];\n }\n\n const rawSections = await page.$$eval('ul.topics li.section', sections => sections.map(section => ({\n id: section.id,\n url: '#' + section.id,\n name: section.querySelector('.sectionname')?.textContent || section.id,\n activities: Array.from(section.querySelectorAll('.section li.activity'))\n .map(activity => activity.id)\n })));\n\n const rawActivities = await page.$$eval('ul.topics ul.section li.activity', activities => activities.map(activity => {\n const classes = (activity.getAttribute('class') || '').split(' ').filter(Boolean);\n return {\n id: activity.id,\n url: '#' + activity.id,\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n name: activity.querySelector('.instancename')?.innerText?.split('\\n')[0] || null,\n\n classes,\n type: classes.length >= 2 ? classes[1] : null,\n html: activity.innerHTML\n };\n }));\n\n const sections = rawSections.map(rawSection => {\n const activities = rawSection.activities.map(activityId => {\n const rawActivity = rawActivities.find(rawActivity => rawActivity.id === activityId);\n if(rawActivity) {\n rawActivity.url = this.url + rawActivity.url;\n return new MyCampusActivity(this.myCampus, rawActivity);\n }\n\n return undefined;\n }).filter(Boolean) as MyCampusActivity[];\n\n rawSection.url = this.url + rawSection.url;\n return new MyCampusSection(rawSection, activities);\n });\n\n await page.close();\n return sections;\n }\n}\n","import Database from './database.js';\nimport MyCampus from './campus.js';\n\nimport {join, dirname, basename, extname} from 'path';\nimport {rename} from 'fs/promises';\nimport MyCampusCourse from './campus-course.js';\nimport MyCampusSection from './campus-section.js';\n\nexport interface SyncCampusOptions {\n username: string;\n password: string;\n database: Database\n}\n\nexport default class SyncCampus {\n static async run(options: SyncCampusOptions): Promise<void> {\n const syncCampus = new SyncCampus(options);\n await syncCampus.run();\n }\n\n private readonly username: string;\n private readonly password: string;\n private readonly database: Database;\n private readonly myCampus: MyCampus;\n\n constructor(options: SyncCampusOptions) {\n this.username = options.username;\n this.password = options.password;\n this.database = options.database;\n this.myCampus = new MyCampus();\n\n }\n\n async run(): Promise<void> {\n await this.myCampus.start(this.username, this.password);\n\n try {\n await this.syncCourses();\n await this.myCampus.stop();\n }\n catch(error: unknown) {\n await this.myCampus.screenshot();\n await this.myCampus.stop();\n throw error;\n }\n }\n\n async syncCourses(): Promise<void> {\n const courses = await this.myCampus.getCourses();\n for(const i in courses) {\n const course = courses[i];\n if(['id=1844', 'id=2567', 'id=2566', 'id=4893', 'id=1208', 'id=2565', 'id=6157'].find(a => course.url.endsWith(a))) {\n continue;\n }\n\n const defaultParentFolder = this.database.paths[course.status];\n const folder = await this.database.findOrCreateFolder(\n 'course-' + course.id,\n course.name || course.id,\n defaultParentFolder\n );\n\n try {\n await this.syncCourse(course, folder);\n }\n catch(error) {\n console.log(`Unable to sync course ${course.url}:`);\n console.log(error instanceof Error ? error.stack : error);\n }\n }\n }\n\n async syncCourse(course: MyCampusCourse, folder: string): Promise<void> {\n const sections = await course.getSections();\n for(const i in sections) {\n const section = sections[i];\n if(!section.activities.find(a => a.isDownloadable())) {\n continue;\n }\n\n const sectionFolder = await this.database.findOrCreateFolder(\n 'course-' + course.id + '/section-' + section.id,\n section.name || course.id,\n folder\n );\n\n await this.syncSection(course, section, sectionFolder);\n }\n }\n\n async syncSection(course: MyCampusCourse, section: MyCampusSection, folder: string): Promise<void> {\n for(const i in section.activities) {\n const activity = section.activities[i];\n if(!activity.isDownloadable()) {\n continue;\n }\n\n const activityInfo = await this.database.getLocalActivityInfo(activity);\n if(!activityInfo.entryExists || !activityInfo.fileExists) {\n console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id} (${activity.id})`);\n console.log('> File does not exist, download it…');\n\n const originalFilePath = await activity.download(folder);\n if(!activity.name) {\n await this.database.updateLocalActivityFile(activity, originalFilePath);\n console.log('> Done, file saved at' + originalFilePath + '\\n');\n continue;\n }\n\n const niceFileName = activity.name\n .replace(/[^a-z0-9-_äüöß.a ()\\[]/gi, '_')\n .replace(/\"/g, '') + extname(originalFilePath).replace(/[^a-z0-9.]/gi, '');\n\n const niceFilePath = await this.database.getConflictFreeFileName(join(folder, niceFileName));\n console.log(`> Download of ${basename(originalFilePath)} complete`);\n console.log(`> Rename file to ${niceFileName}`);\n await rename(originalFilePath, niceFilePath);\n\n await this.database.updateLocalActivityFile(activity, niceFilePath);\n console.log('> Done!\\n');\n }\n else if(activityInfo.changedOnRemote && activityInfo.changedLocally && activityInfo.filePath) {\n console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id}`);\n console.log('> File changed on MyCampus and locally, rename local one and download update…');\n\n const fileExt = extname(activityInfo.filePath);\n const newNameForLocalFile = await this.database.getConflictFreeFileName(join(\n dirname(activityInfo.filePath),\n basename(activityInfo.filePath, fileExt) + '.local' + fileExt\n ));\n\n await rename(activityInfo.filePath, newNameForLocalFile);\n console.log('> File renamed to', newNameForLocalFile);\n\n const filePath = await activity.download(folder);\n await this.database.updateLocalActivityFile(activity, filePath);\n console.log('> Downloaded new file at ' + filePath + '\\n');\n }\n else if(activityInfo.changedOnRemote) {\n console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id}`);\n console.log('> File changed on MyCampus, update it…');\n\n const filePath = await activity.download(folder);\n await this.database.updateLocalActivityFile(activity, filePath);\n\n console.log('> Done, file saved at ' + filePath + '\\n');\n }\n }\n }\n}\n","'use strict';\n\nimport {SyncOptions} from './types.js';\nimport Database from './database.js';\nimport SyncCampus from './sync-campus.js';\n\nclass Sync {\n static async run (): Promise<void> {\n const sync = new Sync({\n cwd: process.env.SYNC_PATH || process.cwd(),\n username: process.env.SYNC_USERNAME || '',\n password: process.env.SYNC_PASSWORD || ''\n });\n\n await sync.run();\n }\n\n private readonly database: Database;\n private readonly options: SyncOptions;\n\n constructor(options: SyncOptions) {\n this.database = new Database(options.cwd);\n this.options = options;\n\n if(!this.options.username || !this.options.password) {\n throw new Error('Username or password empty, please check environment variables.');\n }\n }\n\n async run (): Promise<void> {\n await SyncCampus.run({\n username: this.options.username,\n password: this.options.password,\n database: this.database\n });\n }\n}\n\nSync.run().catch((error: Error) => {\n console.log(error);\n process.exit(1);\n});\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","createReadStream","mkdir","readdir","readFile","writeFile","createHash","join","dirname","extname","basename","resolve","relative","Database","cwd","existsSync","join","json","readFileSync","activity","resolve","activitiy","relative","__async","writeFile","writeFileSync","_0","_1","id","name","folder","existingFolder","mkdir","folderPath","idFilePath","path","files","readdir","file","readFile","entry","result","foundByHash","filePath","actualHash","newFilePath","hash","paths","i","r","createHash","fd","createReadStream","promise","fingerprint","extension","extname","newPath","dirname","basename","puppeteer","MyCampusSection","data","activities","cheerio","fetch","join","promisify","pipeline","createWriteStream","encode","MyCampusActivity","myCampus","data","cheerio","_a","path","__async","url","headers","cookie","encode","response","fetch","contentType","disposition","fileName","filePath","join","streamPipeline","promisify","pipeline","writeStream","createWriteStream","cb","MyCampusCourse","myCampus","data","__async","page","error","rawSections","sections","section","_a","activity","rawActivities","activities","_b","classes","rawSection","activityId","rawActivity","MyCampusActivity","MyCampusSection","MyCampus","username","password","__async","puppeteer","$usernameInput","$passwordInput","error","page","activeCourses","cs","c","infoCourses","items","item","_a","_b","data","course","MyCampusCourse","join","dirname","basename","extname","rename","SyncCampus","options","__async","MyCampus","error","courses","course","a","defaultParentFolder","folder","sections","i","section","sectionFolder","activity","activityInfo","originalFilePath","niceFileName","extname","niceFilePath","join","basename","rename","fileExt","newNameForLocalFile","dirname","filePath","Sync","__async","options","Database","SyncCampus","error"]}
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "url": "https://github.com/sebbo2002/iubh-campus-sync/issues"
9
9
  },
10
10
  "dependencies": {
11
- "@types/node-fetch": "^2.6.2",
11
+ "@types/node-fetch": "^2.6.3",
12
12
  "cheerio": "^1.0.0-rc.10",
13
13
  "es-cookie": "^1.4.0",
14
14
  "node-fetch": "^3.3.1",
@@ -18,31 +18,29 @@
18
18
  "devDependencies": {
19
19
  "@qiwi/semantic-release-gh-pages-plugin": "^5.2.5",
20
20
  "@sebbo2002/semantic-release-docker": "^2.1.0",
21
- "@semantic-release/changelog": "^6.0.2",
21
+ "@semantic-release/changelog": "^6.0.3",
22
22
  "@semantic-release/exec": "^6.0.3",
23
23
  "@semantic-release/git": "^10.0.1",
24
- "@semantic-release/npm": "^9.0.2",
24
+ "@semantic-release/npm": "^10.0.2",
25
25
  "@types/mocha": "^10.0.1",
26
26
  "@types/node": "^18.15.3",
27
27
  "@typescript-eslint/eslint-plugin": "^5.54.1",
28
28
  "@typescript-eslint/parser": "^5.54.1",
29
- "c8": "^7.13.0",
30
29
  "eslint": "^8.36.0",
31
30
  "eslint-plugin-jsonc": "^2.7.0",
32
31
  "esm": "^3.2.25",
33
32
  "license-checker": "^25.0.1",
34
- "mocha": "^10.2.0",
35
33
  "mochawesome": "^7.1.3",
36
34
  "semantic-release-license": "^1.0.3",
37
35
  "source-map-support": "^0.5.21",
38
36
  "ts-node": "^10.9.1",
37
+ "tsup": "^6.7.0",
39
38
  "typedoc": "^0.23.28",
40
39
  "typescript": "^5.0.2"
41
40
  },
42
41
  "engines": {
43
42
  "node": "^14.13.1 || >=16.0.0"
44
43
  },
45
- "exports": "./dist/index.js",
46
44
  "files": [
47
45
  "/dist"
48
46
  ],
@@ -54,7 +52,7 @@
54
52
  "url": "https://github.com/sebbo2002/iubh-campus-sync.git"
55
53
  },
56
54
  "scripts": {
57
- "build": "tsc",
55
+ "build": "tsup",
58
56
  "build-all": "./.github/workflows/build.sh",
59
57
  "coverage": "echo \"No test cases provided.\"",
60
58
  "develop": "ts-node ./src/sync.ts",
@@ -64,5 +62,5 @@
64
62
  "test": "echo \"No test cases provided.\""
65
63
  },
66
64
  "type": "module",
67
- "version": "4.0.0-develop.6"
65
+ "version": "4.0.1-develop.1"
68
66
  }
@@ -1,24 +0,0 @@
1
- import MyCampus from './campus.js';
2
- export interface MyCampusActivityConstructorData {
3
- id: string;
4
- url: string;
5
- name: string | null;
6
- classes: string[];
7
- type: string | null;
8
- html: string;
9
- }
10
- export default class MyCampusActivity {
11
- private readonly myCampus;
12
- private readonly data;
13
- private readonly $;
14
- constructor(myCampus: MyCampus, data: MyCampusActivityConstructorData);
15
- get id(): string;
16
- get url(): string;
17
- get name(): string | null;
18
- get type(): string | null;
19
- toJSON(): MyCampusActivityConstructorData;
20
- fingerprint(): string | null;
21
- isDownloadable(): boolean;
22
- download(path: string): Promise<string>;
23
- private downloadWithCookies;
24
- }
@@ -1,106 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import cheerio from 'cheerio';
11
- import fetch from 'node-fetch';
12
- import { join } from 'path';
13
- import { promisify } from 'util';
14
- import { pipeline } from 'stream';
15
- import { createWriteStream } from 'fs';
16
- import { encode } from 'es-cookie';
17
- export default class MyCampusActivity {
18
- constructor(myCampus, data) {
19
- this.myCampus = myCampus;
20
- this.data = data;
21
- this.$ = cheerio.load(data.html);
22
- }
23
- get id() {
24
- return this.data.id;
25
- }
26
- get url() {
27
- return this.data.url;
28
- }
29
- get name() {
30
- return this.data.name;
31
- }
32
- get type() {
33
- return this.data.type;
34
- }
35
- toJSON() {
36
- return Object.assign({}, this.data);
37
- }
38
- fingerprint() {
39
- var _a;
40
- if (this.type === 'resource') {
41
- return (_a = this.$('.resourcelinkdetails')) === null || _a === void 0 ? void 0 : _a.text();
42
- }
43
- return null;
44
- }
45
- isDownloadable() {
46
- var _a;
47
- if (this.type === 'resource') {
48
- return !!((_a = this.$('a')) === null || _a === void 0 ? void 0 : _a.attr('href'));
49
- }
50
- return false;
51
- }
52
- download(path) {
53
- var _a;
54
- return __awaiter(this, void 0, void 0, function* () {
55
- if (this.type === 'resource') {
56
- const url = (_a = this.$('a')) === null || _a === void 0 ? void 0 : _a.attr('href');
57
- if (!url) {
58
- throw new Error('Unable to download file: URL not found.');
59
- }
60
- return this.downloadWithCookies(path, url);
61
- }
62
- throw new Error(`Unable to download ${this.type}: not implemented yet.`);
63
- });
64
- }
65
- downloadWithCookies(path, url) {
66
- return __awaiter(this, void 0, void 0, function* () {
67
- if (!this.myCampus.page) {
68
- throw new Error('Unable to get browser cookies: Is the page initialized?');
69
- }
70
- const cookies = yield this.myCampus.page.cookies(url);
71
- const headers = {
72
- Cookie: cookies
73
- .map(cookie => encode(cookie.name, cookie.value, {}))
74
- .join('; ')
75
- };
76
- const response = yield fetch(url, { headers });
77
- console.log(`> Response: ${response.status} ${response.statusText}`);
78
- if (!response.ok) {
79
- throw new Error(`Unexpected response: ${response.statusText}`);
80
- }
81
- if (response.body === null) {
82
- throw new Error('Unexpected response: body is empty');
83
- }
84
- const contentType = response.headers.get('content-type');
85
- if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('text/html;')) {
86
- console.log('> HTML Output:', yield response.text());
87
- throw new Error('Unexpected response: server replied with html');
88
- }
89
- const disposition = response.headers.get('content-disposition');
90
- if (!(disposition === null || disposition === void 0 ? void 0 : disposition.startsWith('attachment; filename='))) {
91
- console.log('> Content-Disposition:', disposition);
92
- throw new Error('Unexpected response: server replied without attachement');
93
- }
94
- const fileName = disposition.substr(21).replace(/[a-z\d_\-, /]+/i, '').trim();
95
- const filePath = join(path, fileName);
96
- const streamPipeline = promisify(pipeline);
97
- const writeStream = createWriteStream(filePath);
98
- yield Promise.all([
99
- new Promise(cb => writeStream.on('finish', cb)),
100
- streamPipeline(response.body, writeStream)
101
- ]);
102
- return filePath;
103
- });
104
- }
105
- }
106
- //# sourceMappingURL=campus-activity.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campus-activity.js","sourceRoot":"","sources":["../src/campus-activity.ts"],"names":[],"mappings":"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"}
@@ -1,21 +0,0 @@
1
- import { MyCampusCourseStatus } from './types.js';
2
- import MyCampus from './campus.js';
3
- import MyCampusSection from './campus-section.js';
4
- export interface MyCampusCourseConstructorData {
5
- id: string;
6
- name?: string;
7
- current: boolean;
8
- info: boolean;
9
- url: string;
10
- }
11
- export default class MyCampusCourse {
12
- private myCampus;
13
- private data;
14
- constructor(myCampus: MyCampus, data: MyCampusCourseConstructorData);
15
- get id(): string;
16
- get name(): string | null;
17
- get status(): MyCampusCourseStatus;
18
- get url(): string;
19
- toString(): string;
20
- getSections(): Promise<MyCampusSection[]>;
21
- }
@@ -1,95 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { MyCampusCourseStatus } from './types.js';
11
- import MyCampusSection from './campus-section.js';
12
- import MyCampusActivity from './campus-activity.js';
13
- export default class MyCampusCourse {
14
- constructor(myCampus, data) {
15
- this.myCampus = myCampus;
16
- this.data = data;
17
- }
18
- get id() {
19
- return this.data.id;
20
- }
21
- get name() {
22
- return this.data.name || null;
23
- }
24
- get status() {
25
- if (this.data.current) {
26
- return MyCampusCourseStatus.ACTIVE;
27
- }
28
- else if (this.data.info) {
29
- return MyCampusCourseStatus.INFO;
30
- }
31
- else {
32
- return MyCampusCourseStatus.COMPLETED;
33
- }
34
- }
35
- get url() {
36
- return this.data.url;
37
- }
38
- toString() {
39
- return `MyCampusCourse<${this.id}>`;
40
- }
41
- getSections() {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- if (!this.myCampus.browser) {
44
- throw new Error('Unable to fetch course details: please run start() first!');
45
- }
46
- const page = yield this.myCampus.browser.newPage();
47
- yield page.goto(this.url);
48
- try {
49
- yield page.waitForSelector('#region-main ul.topics');
50
- }
51
- catch (error) {
52
- return [];
53
- }
54
- const rawSections = yield page.$$eval('ul.topics li.section', sections => sections.map(section => {
55
- var _a;
56
- return ({
57
- id: section.id,
58
- url: '#' + section.id,
59
- name: ((_a = section.querySelector('.sectionname')) === null || _a === void 0 ? void 0 : _a.textContent) || section.id,
60
- activities: Array.from(section.querySelectorAll('.section li.activity'))
61
- .map(activity => activity.id)
62
- });
63
- }));
64
- const rawActivities = yield page.$$eval('ul.topics ul.section li.activity', activities => activities.map(activity => {
65
- var _a, _b;
66
- const classes = (activity.getAttribute('class') || '').split(' ').filter(Boolean);
67
- return {
68
- id: activity.id,
69
- url: '#' + activity.id,
70
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
71
- // @ts-ignore
72
- name: ((_b = (_a = activity.querySelector('.instancename')) === null || _a === void 0 ? void 0 : _a.innerText) === null || _b === void 0 ? void 0 : _b.split('\n')[0]) || null,
73
- classes,
74
- type: classes.length >= 2 ? classes[1] : null,
75
- html: activity.innerHTML
76
- };
77
- }));
78
- const sections = rawSections.map(rawSection => {
79
- const activities = rawSection.activities.map(activityId => {
80
- const rawActivity = rawActivities.find(rawActivity => rawActivity.id === activityId);
81
- if (rawActivity) {
82
- rawActivity.url = this.url + rawActivity.url;
83
- return new MyCampusActivity(this.myCampus, rawActivity);
84
- }
85
- return undefined;
86
- }).filter(Boolean);
87
- rawSection.url = this.url + rawSection.url;
88
- return new MyCampusSection(rawSection, activities);
89
- });
90
- yield page.close();
91
- return sections;
92
- });
93
- }
94
- }
95
- //# sourceMappingURL=campus-course.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campus-course.js","sourceRoot":"","sources":["../src/campus-course.ts"],"names":[],"mappings":"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"}
@@ -1,14 +0,0 @@
1
- import MyCampusActivity from './campus-activity.js';
2
- export interface MyCampusSectionConstructorData {
3
- id: string;
4
- url: string;
5
- name: string | null;
6
- }
7
- export default class MyCampusSection {
8
- private readonly data;
9
- readonly activities: MyCampusActivity[];
10
- constructor(data: MyCampusSectionConstructorData, activities: MyCampusActivity[]);
11
- get id(): string;
12
- get url(): string;
13
- get name(): string | null;
14
- }
@@ -1,16 +0,0 @@
1
- export default class MyCampusSection {
2
- constructor(data, activities) {
3
- this.data = data;
4
- this.activities = activities;
5
- }
6
- get id() {
7
- return this.data.id;
8
- }
9
- get url() {
10
- return this.data.url;
11
- }
12
- get name() {
13
- return this.data.name;
14
- }
15
- }
16
- //# sourceMappingURL=campus-section.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campus-section.js","sourceRoot":"","sources":["../src/campus-section.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,OAAO,eAAe;IAIhC,YAAY,IAAoC,EAAE,UAA8B;QAC5E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,CAAC;IAED,IAAI,EAAE;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,GAAG;QACH,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACzB,CAAC;IAED,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAC1B,CAAC;CACJ"}
package/dist/campus.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import { Browser, Page } from 'puppeteer';
2
- import MyCampusCourse from './campus-course.js';
3
- export default class MyCampus {
4
- browser: Browser | undefined;
5
- page: Page | undefined;
6
- private screenshotTime;
7
- private screenshotIndex;
8
- start(username: string, password: string): Promise<void>;
9
- stop(): Promise<void>;
10
- screenshot(page?: Page | undefined): Promise<void>;
11
- getCourses(): Promise<MyCampusCourse[]>;
12
- getOrgaDocuments(): Promise<void>;
13
- }
package/dist/campus.js DELETED
@@ -1,104 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import puppeteer from 'puppeteer';
11
- import MyCampusCourse from './campus-course.js';
12
- export default class MyCampus {
13
- constructor() {
14
- this.screenshotTime = new Date().getTime();
15
- this.screenshotIndex = 0;
16
- }
17
- start(username, password) {
18
- return __awaiter(this, void 0, void 0, function* () {
19
- this.browser = yield puppeteer.launch();
20
- this.page = yield this.browser.newPage();
21
- yield this.page.goto('https://mycampus.iubh.de/my/');
22
- const $usernameInput = yield this.page.waitForSelector('#username');
23
- if (!$usernameInput) {
24
- throw new Error('Unable to login: not able to find username field');
25
- }
26
- yield $usernameInput.type(username);
27
- const $passwordInput = yield this.page.waitForSelector('#password');
28
- if (!$passwordInput) {
29
- throw new Error('Unable to login: not able to find password field');
30
- }
31
- yield $passwordInput.type(password);
32
- yield $passwordInput.press('Enter');
33
- try {
34
- yield this.page.waitForSelector('#page-my-index');
35
- }
36
- catch (error) {
37
- yield this.page.reload({
38
- waitUntil: ['networkidle0', 'domcontentloaded']
39
- });
40
- yield this.page.waitForSelector('#page-my-index');
41
- }
42
- });
43
- }
44
- stop() {
45
- return __awaiter(this, void 0, void 0, function* () {
46
- if (this.browser) {
47
- yield this.browser.close();
48
- delete this.browser;
49
- delete this.page;
50
- }
51
- });
52
- }
53
- screenshot(page = this.page) {
54
- return __awaiter(this, void 0, void 0, function* () {
55
- if (page) {
56
- yield page.screenshot({
57
- path: this.screenshotTime + '-' + this.screenshotIndex.toString().padStart(3, '0') + '.png'
58
- });
59
- }
60
- });
61
- }
62
- getCourses() {
63
- return __awaiter(this, void 0, void 0, function* () {
64
- if (!this.page) {
65
- throw new Error('Unable to get courses: please run start() first!');
66
- }
67
- yield this.page.goto('https://mycampus.iubh.de/my/');
68
- yield this.page.waitForSelector('.courselist');
69
- yield this.page.waitForSelector('.courselist');
70
- const activeCourses = yield this.page.$$eval('#courses-active .shortname', cs => cs.map(c => c.textContent || '').filter(Boolean));
71
- const infoCourses = yield this.page.$$eval('#courses-intro .shortname', cs => cs.map(c => c.textContent || '').filter(Boolean));
72
- const courses = yield this.page.$$eval('.courseitem', items => items.map(item => {
73
- var _a, _b;
74
- const data = {
75
- url: item.getAttribute('href'),
76
- id: (_a = item.querySelector('.shortname')) === null || _a === void 0 ? void 0 : _a.textContent,
77
- name: (_b = item.querySelector('.fullname')) === null || _b === void 0 ? void 0 : _b.textContent
78
- };
79
- if (!data.id) {
80
- throw new Error('Course ID not found.');
81
- }
82
- if (!data.url) {
83
- throw new Error('Course URL not found.');
84
- }
85
- return data;
86
- }));
87
- return courses.map(course => {
88
- return new MyCampusCourse(this, {
89
- id: course.id,
90
- name: course.name || undefined,
91
- current: course.id ? activeCourses.includes(course.id) : false,
92
- info: course.id ? infoCourses.includes(course.id) : false,
93
- url: course.url
94
- });
95
- });
96
- });
97
- }
98
- getOrgaDocuments() {
99
- return __awaiter(this, void 0, void 0, function* () {
100
- // @todo
101
- });
102
- }
103
- }
104
- //# sourceMappingURL=campus.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campus.js","sourceRoot":"","sources":["../src/campus.ts"],"names":[],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- import { MyCampusCourseStatus } from './types.js';
2
- import MyCampusActivity from './campus-activity.js';
3
- export interface DatabaseActivityEntry {
4
- id: string;
5
- fingerprint: string | null;
6
- hash: string;
7
- path: string;
8
- }
9
- export interface DatabaseActivityInfo {
10
- entryExists: boolean;
11
- fileExists: boolean;
12
- filePath: string | null;
13
- changedOnRemote: boolean | null;
14
- changedLocally: boolean | null;
15
- }
16
- export default class Database {
17
- private readonly path;
18
- readonly cwd: string;
19
- readonly activities: DatabaseActivityEntry[];
20
- readonly paths: {
21
- [MyCampusCourseStatus.ACTIVE]: string;
22
- [MyCampusCourseStatus.INFO]: string;
23
- [MyCampusCourseStatus.COMPLETED]: string;
24
- };
25
- constructor(cwd: string);
26
- load(): void;
27
- toJSON(): Record<string, unknown>;
28
- save(): Promise<void>;
29
- saveSync(): void;
30
- findOrCreateFolder(id: string, name: string, folder?: string): Promise<string>;
31
- findFolder(id: string, folder?: string | null): Promise<string | null>;
32
- getLocalActivityInfo(activity: MyCampusActivity): Promise<DatabaseActivityInfo>;
33
- searchFileByHash(hash: string, path?: string): Promise<string | null>;
34
- getHashByFile(path: string): Promise<string>;
35
- updateLocalActivityFile(activity: MyCampusActivity, path: string, hash?: string): Promise<void>;
36
- getConflictFreeFileName(filePath: string): Promise<string>;
37
- static getConflictFreeFileName(filePath: string): Promise<string>;
38
- }
package/dist/database.js DELETED
@@ -1,253 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { existsSync, readFileSync, writeFileSync, createReadStream } from 'fs';
11
- import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
12
- import { createHash } from 'crypto';
13
- import { join, dirname, extname, basename, resolve, relative } from 'path';
14
- import { MyCampusCourseStatus } from './types.js';
15
- export default class Database {
16
- constructor(cwd) {
17
- this.cwd = cwd;
18
- if (!existsSync(cwd)) {
19
- throw new Error(`Unable to load database: Working directory (${cwd}) does not exist!`);
20
- }
21
- this.paths = {
22
- [MyCampusCourseStatus.ACTIVE]: this.cwd,
23
- [MyCampusCourseStatus.INFO]: join(this.cwd, 'Infos & Organisatorisches'),
24
- [MyCampusCourseStatus.COMPLETED]: join(this.cwd, 'Abgeschlossene Module')
25
- };
26
- this.path = join(cwd, '.iubh-campus-sync.db');
27
- this.activities = [];
28
- if (existsSync(this.path)) {
29
- this.load();
30
- }
31
- else {
32
- this.saveSync();
33
- }
34
- }
35
- load() {
36
- const json = JSON.parse(readFileSync(this.path, { encoding: 'utf8' }));
37
- if (json.version !== 1) {
38
- throw new Error('Invalid db version: unable to continue.');
39
- }
40
- if (Array.isArray(json.activities)) {
41
- json.activities.forEach((activity) => this.activities.push({
42
- id: activity[0],
43
- fingerprint: activity[1],
44
- hash: activity[2],
45
- path: resolve(this.cwd, activity[3])
46
- }));
47
- }
48
- }
49
- toJSON() {
50
- return {
51
- 'version': 1,
52
- 'activities': this.activities.map(activitiy => ([
53
- activitiy.id,
54
- activitiy.fingerprint,
55
- activitiy.hash,
56
- relative(this.cwd, activitiy.path)
57
- ]))
58
- };
59
- }
60
- save() {
61
- return __awaiter(this, void 0, void 0, function* () {
62
- yield writeFile(this.path, JSON.stringify(this.toJSON(), null, ' '));
63
- });
64
- }
65
- saveSync() {
66
- writeFileSync(this.path, JSON.stringify(this.toJSON(), null, ' '));
67
- }
68
- findOrCreateFolder(id, name, folder = this.cwd) {
69
- return __awaiter(this, void 0, void 0, function* () {
70
- const existingFolder = yield this.findFolder(id);
71
- if (existingFolder) {
72
- return existingFolder;
73
- }
74
- if (!existsSync(folder)) {
75
- yield mkdir(folder);
76
- }
77
- const folderPath = yield this.getConflictFreeFileName(join(folder, name));
78
- yield mkdir(folderPath);
79
- const idFilePath = join(folderPath, '.iubh-campus-sync-folder');
80
- yield writeFile(idFilePath, id + '\n');
81
- return folderPath;
82
- });
83
- }
84
- findFolder(id, folder = null) {
85
- return __awaiter(this, void 0, void 0, function* () {
86
- if (!folder) {
87
- const results = yield Promise.all(Object.values(this.paths).map(path => this.findFolder(id, path)));
88
- return results.find(Boolean) || null;
89
- }
90
- if (!existsSync(folder)) {
91
- return null;
92
- }
93
- const files = yield readdir(folder, { withFileTypes: true });
94
- const results = yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () {
95
- if (file.isDirectory()) {
96
- return this.findFolder(id, join(folder, file.name));
97
- }
98
- if (file.name !== '.iubh-campus-sync-folder' || !file.isFile()) {
99
- return null;
100
- }
101
- const path = join(folder, file.name);
102
- const content = yield readFile(path, { encoding: 'utf8' });
103
- const savedId = content.split('\n')[0].trim();
104
- if (savedId === id) {
105
- return folder;
106
- }
107
- return null;
108
- })));
109
- return results.find(Boolean) || null;
110
- });
111
- }
112
- getLocalActivityInfo(activity) {
113
- return __awaiter(this, void 0, void 0, function* () {
114
- const entry = this.activities.find(entry => entry.id === activity.id);
115
- if (!entry) {
116
- return {
117
- entryExists: false,
118
- fileExists: false,
119
- filePath: null,
120
- changedOnRemote: null,
121
- changedLocally: null
122
- };
123
- }
124
- const result = {
125
- entryExists: true,
126
- fileExists: false,
127
- filePath: null,
128
- changedOnRemote: !!(activity.fingerprint() && entry.fingerprint !== activity.fingerprint()),
129
- changedLocally: null
130
- };
131
- // file exists?
132
- let foundByHash = false;
133
- let filePath = entry.path;
134
- if (!filePath || !existsSync(filePath)) {
135
- // no? is the file somewhere else?
136
- filePath = yield this.searchFileByHash(entry.hash);
137
- foundByHash = true;
138
- }
139
- if (!filePath) {
140
- return result;
141
- }
142
- yield this.updateLocalActivityFile(activity, filePath);
143
- result.fileExists = true;
144
- result.filePath = filePath;
145
- result.changedLocally = false;
146
- // file fingerprint matches?
147
- const actualHash = yield this.getHashByFile(filePath);
148
- if (entry.hash !== actualHash) {
149
- result.changedLocally = true;
150
- if (!foundByHash) {
151
- const newFilePath = yield this.searchFileByHash(entry.hash);
152
- if (newFilePath) {
153
- result.filePath = newFilePath;
154
- result.changedLocally = false;
155
- yield this.updateLocalActivityFile(activity, newFilePath);
156
- }
157
- }
158
- }
159
- return result;
160
- });
161
- }
162
- searchFileByHash(hash, path) {
163
- return __awaiter(this, void 0, void 0, function* () {
164
- if (!path) {
165
- const paths = Object.values(this.paths);
166
- for (const i in paths) {
167
- const path = paths[i];
168
- const result = yield this.searchFileByHash(hash, path);
169
- if (result) {
170
- return result;
171
- }
172
- }
173
- return null;
174
- }
175
- if (!existsSync(path)) {
176
- return null;
177
- }
178
- const files = yield readdir(path, { withFileTypes: true });
179
- for (const i in files) {
180
- const file = files[i];
181
- const filePath = join(path, file.name);
182
- if (file.isDirectory()) {
183
- const r = yield this.searchFileByHash(hash, filePath);
184
- if (r) {
185
- return r;
186
- }
187
- }
188
- if (!file.isFile() || file.name.substr(0, 1) === '.') {
189
- continue;
190
- }
191
- const fileHash = yield this.getHashByFile(filePath);
192
- if (fileHash === hash) {
193
- return filePath;
194
- }
195
- }
196
- return null;
197
- });
198
- }
199
- getHashByFile(path) {
200
- return __awaiter(this, void 0, void 0, function* () {
201
- const hash = createHash('sha1');
202
- hash.setEncoding('hex');
203
- const fd = createReadStream(path);
204
- const promise = new Promise(resolve => {
205
- fd.on('end', () => {
206
- hash.end();
207
- resolve(hash.read().toString());
208
- });
209
- });
210
- fd.pipe(hash);
211
- return promise;
212
- });
213
- }
214
- updateLocalActivityFile(activity, path, hash) {
215
- return __awaiter(this, void 0, void 0, function* () {
216
- const entry = this.activities.find(entry => entry.id === activity.id);
217
- const fingerprint = activity.fingerprint();
218
- if (!hash) {
219
- hash = yield this.getHashByFile(path);
220
- }
221
- if (!entry) {
222
- this.activities.push({
223
- id: activity.id,
224
- fingerprint,
225
- hash,
226
- path
227
- });
228
- }
229
- else {
230
- entry.fingerprint = fingerprint;
231
- entry.hash = hash;
232
- entry.path = path;
233
- }
234
- yield this.save();
235
- });
236
- }
237
- getConflictFreeFileName(filePath) {
238
- return __awaiter(this, void 0, void 0, function* () {
239
- return Database.getConflictFreeFileName(filePath);
240
- });
241
- }
242
- static getConflictFreeFileName(filePath) {
243
- return __awaiter(this, void 0, void 0, function* () {
244
- const extension = extname(filePath);
245
- let newPath = filePath;
246
- for (let i = 1; existsSync(newPath); i++) {
247
- newPath = join(dirname(filePath), basename(filePath, extension) + '-' + i + extension);
248
- }
249
- return newPath;
250
- });
251
- }
252
- }
253
- //# sourceMappingURL=database.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAC,MAAM,IAAI,CAAC;AAC7E,OAAO,EAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AAChE,OAAO,EAAC,UAAU,EAAC,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAC,MAAM,MAAM,CAAC;AACzE,OAAO,EAAC,oBAAoB,EAAC,MAAM,YAAY,CAAC;AAkBhD,MAAM,CAAC,OAAO,OAAO,QAAQ;IAOzB,YAAY,GAAW;QACnB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YAClB,MAAM,IAAI,KAAK,CAAC,+CAA+C,GAAG,mBAAmB,CAAC,CAAC;SAC1F;QAED,IAAI,CAAC,KAAK,GAAG;YACT,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG;YACvC,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,2BAA2B,CAAC;YACxE,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC;SAC5E,CAAC;QAEF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QAErB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACvB,IAAI,CAAC,IAAI,EAAE,CAAC;SACf;aACI;YACD,IAAI,CAAC,QAAQ,EAAE,CAAC;SACnB;IACL,CAAC;IAED,IAAI;QACA,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAAC,CAAC;QACrE,IAAG,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE;YACnB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;SAC9D;QAED,IAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;YAC/B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,QAAkB,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBACjE,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACf,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACxB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACjB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;aACvC,CAAC,CAAC,CAAC;SACP;IACL,CAAC;IAED,MAAM;QACF,OAAO;YACH,SAAS,EAAE,CAAC;YACZ,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBAC5C,SAAS,CAAC,EAAE;gBACZ,SAAS,CAAC,WAAW;gBACrB,SAAS,CAAC,IAAI;gBACd,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,CAAC;aACrC,CAAC,CAAC;SACN,CAAC;IACN,CAAC;IAEK,IAAI;;YACN,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1E,CAAC;KAAA;IAED,QAAQ;QACJ,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC;IAEK,kBAAkB,CAAC,EAAU,EAAE,IAAY,EAAE,SAAiB,IAAI,CAAC,GAAG;;YACxE,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACjD,IAAI,cAAc,EAAE;gBAChB,OAAO,cAAc,CAAC;aACzB;YACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;gBACrB,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;aACvB;YAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1E,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;YAExB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,0BAA0B,CAAC,CAAC;YAChE,MAAM,SAAS,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;YACvC,OAAO,UAAU,CAAC;QACtB,CAAC;KAAA;IAEK,UAAU,CAAC,EAAU,EAAE,SAAwB,IAAI;;YACrD,IAAI,CAAC,MAAM,EAAE;gBACT,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;gBACpG,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;aACxC;YACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;gBACrB,OAAO,IAAI,CAAC;aACf;YAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,EAAC,aAAa,EAAE,IAAI,EAAC,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAM,IAAI,EAAC,EAAE;gBACrD,IAAG,IAAI,CAAC,WAAW,EAAE,EAAE;oBACnB,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;iBACvD;gBACD,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;oBAC5D,OAAO,IAAI,CAAC;iBACf;gBAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC9C,IAAI,OAAO,KAAK,EAAE,EAAE;oBAChB,OAAO,MAAM,CAAC;iBACjB;gBAED,OAAO,IAAI,CAAC;YAChB,CAAC,CAAA,CAAC,CAAC,CAAC;YAEJ,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QACzC,CAAC;KAAA;IAEK,oBAAoB,CAAC,QAA0B;;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtE,IAAG,CAAC,KAAK,EAAE;gBACP,OAAO;oBACH,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,KAAK;oBACjB,QAAQ,EAAE,IAAI;oBACd,eAAe,EAAE,IAAI;oBACrB,cAAc,EAAE,IAAI;iBACvB,CAAC;aACL;YAED,MAAM,MAAM,GAAyB;gBACjC,WAAW,EAAE,IAAI;gBACjB,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,IAAI;gBACd,eAAe,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC3F,cAAc,EAAE,IAAI;aACvB,CAAC;YAEF,eAAe;YACf,IAAI,WAAW,GAAG,KAAK,CAAC;YACxB,IAAI,QAAQ,GAAgB,KAAK,CAAC,IAAI,CAAC;YACvC,IAAG,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;gBACnC,kCAAkC;gBAClC,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnD,WAAW,GAAG,IAAI,CAAC;aACtB;YACD,IAAG,CAAC,QAAQ,EAAE;gBACV,OAAO,MAAM,CAAC;aACjB;YAED,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACvD,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;YACzB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC3B,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC;YAE9B,4BAA4B;YAC5B,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YACtD,IAAG,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;gBAC1B,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;gBAE7B,IAAG,CAAC,WAAW,EAAE;oBACb,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC5D,IAAG,WAAW,EAAE;wBACZ,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC;wBAC9B,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC;wBAC9B,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;qBAC7D;iBACJ;aACJ;YAED,OAAO,MAAM,CAAC;QAClB,CAAC;KAAA;IAEK,gBAAgB,CAAC,IAAY,EAAE,IAAa;;YAC9C,IAAI,CAAC,IAAI,EAAE;gBACP,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxC,KAAI,MAAM,CAAC,IAAI,KAAK,EAAE;oBAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACvD,IAAG,MAAM,EAAE;wBACP,OAAO,MAAM,CAAC;qBACjB;iBACJ;gBAED,OAAO,IAAI,CAAC;aACf;YACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;gBACnB,OAAO,IAAI,CAAC;aACf;YAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAC,aAAa,EAAE,IAAI,EAAC,CAAC,CAAC;YACzD,KAAI,MAAM,CAAC,IAAI,KAAK,EAAE;gBAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAG,IAAI,CAAC,WAAW,EAAE,EAAE;oBACnB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;oBACtD,IAAG,CAAC,EAAE;wBACF,OAAO,CAAC,CAAC;qBACZ;iBACJ;gBACD,IAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,EAAE;oBACjD,SAAS;iBACZ;gBAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACpD,IAAG,QAAQ,KAAK,IAAI,EAAE;oBAClB,OAAO,QAAQ,CAAC;iBACnB;aACJ;YAED,OAAO,IAAI,CAAC;QAChB,CAAC;KAAA;IAEK,aAAa,CAAC,IAAY;;YAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAExB,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;gBAClC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACd,IAAI,CAAC,GAAG,EAAE,CAAC;oBACX,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACpC,CAAC,CAAC,CAAC;YACP,CAAC,CAAoB,CAAC;YAEtB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,OAAO,CAAC;QACnB,CAAC;KAAA;IAEK,uBAAuB,CAAC,QAA0B,EAAE,IAAY,EAAE,IAAa;;YACjF,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtE,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC3C,IAAG,CAAC,IAAI,EAAE;gBACN,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;aACzC;YAED,IAAG,CAAC,KAAK,EAAE;gBACP,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;oBACjB,EAAE,EAAE,QAAQ,CAAC,EAAE;oBACf,WAAW;oBACX,IAAI;oBACJ,IAAI;iBACP,CAAC,CAAC;aACN;iBAAI;gBACD,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;gBAChC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;gBAClB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;aACrB;YAED,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,CAAC;KAAA;IAEK,uBAAuB,CAAC,QAAgB;;YAC1C,OAAO,QAAQ,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACtD,CAAC;KAAA;IAED,MAAM,CAAO,uBAAuB,CAAC,QAAgB;;YACjD,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,OAAO,GAAG,QAAQ,CAAC;YAEvB,KAAI,IAAI,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE;gBACrC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;aAC1F;YAED,OAAO,OAAO,CAAC;QACnB,CAAC;KAAA;CACJ"}
@@ -1,20 +0,0 @@
1
- import Database from './database.js';
2
- import MyCampusCourse from './campus-course.js';
3
- import MyCampusSection from './campus-section.js';
4
- export interface SyncCampusOptions {
5
- username: string;
6
- password: string;
7
- database: Database;
8
- }
9
- export default class SyncCampus {
10
- static run(options: SyncCampusOptions): Promise<void>;
11
- private readonly username;
12
- private readonly password;
13
- private readonly database;
14
- private readonly myCampus;
15
- constructor(options: SyncCampusOptions);
16
- run(): Promise<void>;
17
- syncCourses(): Promise<void>;
18
- syncCourse(course: MyCampusCourse, folder: string): Promise<void>;
19
- syncSection(course: MyCampusCourse, section: MyCampusSection, folder: string): Promise<void>;
20
- }
@@ -1,122 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import MyCampus from './campus.js';
11
- import { join, dirname, basename, extname } from 'path';
12
- import { rename } from 'fs/promises';
13
- export default class SyncCampus {
14
- static run(options) {
15
- return __awaiter(this, void 0, void 0, function* () {
16
- const syncCampus = new SyncCampus(options);
17
- yield syncCampus.run();
18
- });
19
- }
20
- constructor(options) {
21
- this.username = options.username;
22
- this.password = options.password;
23
- this.database = options.database;
24
- this.myCampus = new MyCampus();
25
- }
26
- run() {
27
- return __awaiter(this, void 0, void 0, function* () {
28
- yield this.myCampus.start(this.username, this.password);
29
- try {
30
- yield this.syncCourses();
31
- yield this.myCampus.stop();
32
- }
33
- catch (error) {
34
- yield this.myCampus.screenshot();
35
- yield this.myCampus.stop();
36
- throw error;
37
- }
38
- });
39
- }
40
- syncCourses() {
41
- return __awaiter(this, void 0, void 0, function* () {
42
- const courses = yield this.myCampus.getCourses();
43
- for (const i in courses) {
44
- const course = courses[i];
45
- if (['id=1844', 'id=2567', 'id=2566', 'id=4893', 'id=1208', 'id=2565', 'id=6157'].find(a => course.url.endsWith(a))) {
46
- continue;
47
- }
48
- const defaultParentFolder = this.database.paths[course.status];
49
- const folder = yield this.database.findOrCreateFolder('course-' + course.id, course.name || course.id, defaultParentFolder);
50
- try {
51
- yield this.syncCourse(course, folder);
52
- }
53
- catch (error) {
54
- console.log(`Unable to sync course ${course.url}:`);
55
- console.log(error instanceof Error ? error.stack : error);
56
- }
57
- }
58
- });
59
- }
60
- syncCourse(course, folder) {
61
- return __awaiter(this, void 0, void 0, function* () {
62
- const sections = yield course.getSections();
63
- for (const i in sections) {
64
- const section = sections[i];
65
- if (!section.activities.find(a => a.isDownloadable())) {
66
- continue;
67
- }
68
- const sectionFolder = yield this.database.findOrCreateFolder('course-' + course.id + '/section-' + section.id, section.name || course.id, folder);
69
- yield this.syncSection(course, section, sectionFolder);
70
- }
71
- });
72
- }
73
- syncSection(course, section, folder) {
74
- return __awaiter(this, void 0, void 0, function* () {
75
- for (const i in section.activities) {
76
- const activity = section.activities[i];
77
- if (!activity.isDownloadable()) {
78
- continue;
79
- }
80
- const activityInfo = yield this.database.getLocalActivityInfo(activity);
81
- if (!activityInfo.entryExists || !activityInfo.fileExists) {
82
- console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id} (${activity.id})`);
83
- console.log('> File does not exist, download it…');
84
- const originalFilePath = yield activity.download(folder);
85
- if (!activity.name) {
86
- yield this.database.updateLocalActivityFile(activity, originalFilePath);
87
- console.log('> Done, file saved at' + originalFilePath + '\n');
88
- continue;
89
- }
90
- const niceFileName = activity.name
91
- .replace(/[^a-z0-9-_äüöß.a ()\[]/gi, '_')
92
- .replace(/"/g, '') + extname(originalFilePath).replace(/[^a-z0-9.]/gi, '');
93
- const niceFilePath = yield this.database.getConflictFreeFileName(join(folder, niceFileName));
94
- console.log(`> Download of ${basename(originalFilePath)} complete`);
95
- console.log(`> Rename file to ${niceFileName}`);
96
- yield rename(originalFilePath, niceFilePath);
97
- yield this.database.updateLocalActivityFile(activity, niceFilePath);
98
- console.log('> Done!\n');
99
- }
100
- else if (activityInfo.changedOnRemote && activityInfo.changedLocally && activityInfo.filePath) {
101
- console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id}`);
102
- console.log('> File changed on MyCampus and locally, rename local one and download update…');
103
- const fileExt = extname(activityInfo.filePath);
104
- const newNameForLocalFile = yield this.database.getConflictFreeFileName(join(dirname(activityInfo.filePath), basename(activityInfo.filePath, fileExt) + '.local' + fileExt));
105
- yield rename(activityInfo.filePath, newNameForLocalFile);
106
- console.log('> File renamed to', newNameForLocalFile);
107
- const filePath = yield activity.download(folder);
108
- yield this.database.updateLocalActivityFile(activity, filePath);
109
- console.log('> Downloaded new file at ' + filePath + '\n');
110
- }
111
- else if (activityInfo.changedOnRemote) {
112
- console.log(`${course.name || course.id} / ${section.name || section.id} / ${activity.name || activity.id}`);
113
- console.log('> File changed on MyCampus, update it…');
114
- const filePath = yield activity.download(folder);
115
- yield this.database.updateLocalActivityFile(activity, filePath);
116
- console.log('> Done, file saved at ' + filePath + '\n');
117
- }
118
- }
119
- });
120
- }
121
- }
122
- //# sourceMappingURL=sync-campus.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync-campus.js","sourceRoot":"","sources":["../src/sync-campus.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export declare enum MyCampusCourseStatus {
2
- ACTIVE = 0,
3
- COMPLETED = 1,
4
- INFO = 2
5
- }
6
- export interface SyncOptions {
7
- cwd: string;
8
- username: string;
9
- password: string;
10
- }
package/dist/types.js DELETED
@@ -1,7 +0,0 @@
1
- export var MyCampusCourseStatus;
2
- (function (MyCampusCourseStatus) {
3
- MyCampusCourseStatus[MyCampusCourseStatus["ACTIVE"] = 0] = "ACTIVE";
4
- MyCampusCourseStatus[MyCampusCourseStatus["COMPLETED"] = 1] = "COMPLETED";
5
- MyCampusCourseStatus[MyCampusCourseStatus["INFO"] = 2] = "INFO";
6
- })(MyCampusCourseStatus || (MyCampusCourseStatus = {}));
7
- //# sourceMappingURL=types.js.map
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,oBAIX;AAJD,WAAY,oBAAoB;IAC5B,mEAAM,CAAA;IACN,yEAAS,CAAA;IACT,+DAAI,CAAA;AACR,CAAC,EAJW,oBAAoB,KAApB,oBAAoB,QAI/B"}