@sebbo2002/iubh-campus-sync 4.0.3 → 5.0.0-develop.1

Sign up to get free protection for your applications and to get access to all the features.
package/dist/sync.js CHANGED
@@ -1,9 +1,9 @@
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+`
1
+ var l=(u,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(u,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 d,dirname as T,extname as U,basename as H,resolve as k,relative as R}from"path";var p=class u{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:d(this.cwd,"Infos & Organisatorisches"),1:d(this.cwd,"Abgeschlossene Module")},this.path=d(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(d(a,i));yield F(r);let o=d(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,d(i,e.name));if(e.name!==".iubh-campus-sync-folder"||!e.isFile())return null;let s=d(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=d(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 u.getConflictFreeFileName(t)})}static getConflictFreeFileName(t){return l(this,null,function*(){let i=U(t),a=t;for(let n=1;m(a);n++)a=d(T(t),H(t,i)+"-"+n+i);return a})}};import _ from"puppeteer";var h=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 f=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(C=>Y(C.name,C.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(C=>b.on("finish",C)),D(e.body,b)]),c})}};var g=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 f(this.myCampus,o)}).filter(Boolean);return e.url=this.url+e.url,new h(e,s)});return yield t.close(),n})}};var y=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 g(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 w=class u{static run(t){return l(this,null,function*(){yield new u(t).run()})}constructor(t){this.username=t.username,this.password=t.password,this.database=t.database,this.myCampus=new y}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
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
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
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)});
8
+ `)}}})}};var v=class u{static run(){return l(this,null,function*(){yield new u({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 w.run({username:this.options.username,password:this.options.password,database:this.database})})}};v.run().catch(u=>{console.log(u),process.exit(1)});
9
9
  //# sourceMappingURL=sync.js.map
package/dist/sync.js.map CHANGED
@@ -1 +1 @@
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"]}
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","_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","_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","_Sync","__async","options","Database","SyncCampus","error"]}
package/package.json CHANGED
@@ -12,34 +12,34 @@
12
12
  "cheerio": "^1.0.0-rc.10",
13
13
  "es-cookie": "^1.4.0",
14
14
  "node-fetch": "^3.3.1",
15
- "puppeteer": "^20.5.0"
15
+ "puppeteer": "^20.7.3"
16
16
  },
17
17
  "description": "Syncs data between a local folder and IUBH's MyCampus",
18
18
  "devDependencies": {
19
19
  "@qiwi/semantic-release-gh-pages-plugin": "^5.2.5",
20
- "@sebbo2002/semantic-release-docker": "^3.0.0",
20
+ "@sebbo2002/semantic-release-docker": "^4.0.0",
21
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": "^10.0.3",
24
+ "@semantic-release/npm": "^10.0.4",
25
25
  "@types/mocha": "^10.0.1",
26
26
  "@types/node": "^20.2.5",
27
- "@typescript-eslint/eslint-plugin": "^5.59.8",
28
- "@typescript-eslint/parser": "^5.59.8",
29
- "eslint": "^8.41.0",
30
- "eslint-plugin-jsonc": "^2.8.0",
27
+ "@typescript-eslint/eslint-plugin": "^5.60.0",
28
+ "@typescript-eslint/parser": "^5.60.0",
29
+ "eslint": "^8.42.0",
30
+ "eslint-plugin-jsonc": "^2.9.0",
31
31
  "esm": "^3.2.25",
32
32
  "license-checker": "^25.0.1",
33
33
  "mochawesome": "^7.1.3",
34
34
  "semantic-release-license": "^1.0.3",
35
35
  "source-map-support": "^0.5.21",
36
36
  "ts-node": "^10.9.1",
37
- "tsup": "^6.7.0",
37
+ "tsup": "^7.0.0",
38
38
  "typedoc": "^0.24.8",
39
39
  "typescript": "^5.1.3"
40
40
  },
41
41
  "engines": {
42
- "node": "^14.13.1 || >=16.0.0"
42
+ "node": "^16.0.0 || >=18.0.0"
43
43
  },
44
44
  "files": [
45
45
  "/dist"
@@ -62,5 +62,5 @@
62
62
  "test": "echo \"No test cases provided.\""
63
63
  },
64
64
  "type": "module",
65
- "version": "4.0.3"
65
+ "version": "5.0.0-develop.1"
66
66
  }