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

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.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
- "@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",
26
+ "@types/node": "^20.3.1",
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.2"
66
66
  }