├── .env.example ├── .gitignore ├── .prettierrc.yaml ├── .vscode └── settings.json ├── package.json ├── .github └── workflows │ └── main.yaml ├── index.js ├── services └── puppeteer.service.js ├── main.mustache └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | OPEN_WEATHER_MAP_KEY= 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | **/.DS_STORE 3 | .env 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | printWidth: 110 2 | tabWidth: 2 3 | useTabs: false 4 | semi: true 5 | singleQuote: true 6 | quoteProps: 'consistent' 7 | jsxSingleQuote: false 8 | trailingComma: 'es5' 9 | bracketSpacing: true 10 | jsxBracketSameLine: false 11 | arrowParens: 'avoid' 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "titleBar.activeForeground": "#61dafb", 4 | "titleBar.inactiveForeground": "#53b7d3", 5 | "titleBar.activeBackground": "#20232a", 6 | "titleBar.inactiveBackground": "#0a0e16" 7 | }, 8 | "editor.formatOnSave": true, 9 | "files.insertFinalNewline": true, 10 | "editor.tabSize": 2 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thmsgbrt", 3 | "version": "1.0.0", 4 | "description": "A dynamic README.md for your GitHub Profile, using Actions, Javascript and Mustache.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/thmsgbrt/thmsgbrt.git" 12 | }, 13 | "keywords": [ 14 | "readme.md", 15 | "github", 16 | "actions", 17 | "dynamic", 18 | "javascript" 19 | ], 20 | "author": "Thomas Guibert", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/thmsgbrt/thmsgbrt/issues" 24 | }, 25 | "homepage": "https://github.com/thmsgbrt/thmsgbrt#readme", 26 | "dependencies": { 27 | "dotenv": "^8.2.0", 28 | "mustache": "^4.0.1", 29 | "node-fetch": "^2.6.1", 30 | "puppeteer": "^5.1.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: README build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | schedule: 8 | - cron: '0 */3 * * *' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v1 17 | - name: setup node 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '13.x' 21 | - name: cache 22 | uses: actions/cache@v1 23 | with: 24 | path: node_modules 25 | key: ${{ runner.os }}-js-${{ hashFiles('package-lock.json') }} 26 | - name: Install dependencies 27 | run: npm install 28 | - name: Generate README file 29 | run: node index.js 30 | env: 31 | OPEN_WEATHER_MAP_KEY: ${{secrets.OPEN_WEATHER_MAP_KEY}} 32 | - name: Push new README.md 33 | uses: mikeal/publish-to-github-action@master 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const Mustache = require('mustache'); 3 | const fetch = require('node-fetch'); 4 | const fs = require('fs'); 5 | const puppeteerService = require('./services/puppeteer.service'); 6 | 7 | const MUSTACHE_MAIN_DIR = './main.mustache'; 8 | 9 | let DATA = { 10 | refresh_date: new Date().toLocaleDateString('en-GB', { 11 | weekday: 'long', 12 | month: 'long', 13 | day: 'numeric', 14 | hour: 'numeric', 15 | minute: 'numeric', 16 | timeZoneName: 'short', 17 | timeZone: 'Europe/Stockholm', 18 | }), 19 | }; 20 | 21 | async function setWeatherInformation() { 22 | await fetch( 23 | `https://api.openweathermap.org/data/2.5/weather?q=stockholm&appid=${process.env.OPEN_WEATHER_MAP_KEY}&units=metric` 24 | ) 25 | .then(r => r.json()) 26 | .then(r => { 27 | DATA.city_temperature = Math.round(r.main.temp); 28 | DATA.city_weather = r.weather[0].description; 29 | DATA.city_weather_icon = r.weather[0].icon; 30 | DATA.sun_rise = new Date(r.sys.sunrise * 1000).toLocaleString('en-GB', { 31 | hour: '2-digit', 32 | minute: '2-digit', 33 | timeZone: 'Europe/Stockholm', 34 | }); 35 | DATA.sun_set = new Date(r.sys.sunset * 1000).toLocaleString('en-GB', { 36 | hour: '2-digit', 37 | minute: '2-digit', 38 | timeZone: 'Europe/Stockholm', 39 | }); 40 | }); 41 | } 42 | 43 | async function setInstagramPosts() { 44 | const instagramImages = await puppeteerService.getLatestInstagramPostsFromAccount('visitstockholm', 3); 45 | DATA.img1 = instagramImages[0]; 46 | DATA.img2 = instagramImages[1]; 47 | DATA.img3 = instagramImages[2]; 48 | } 49 | 50 | async function generateReadMe() { 51 | await fs.readFile(MUSTACHE_MAIN_DIR, (err, data) => { 52 | if (err) throw err; 53 | const output = Mustache.render(data.toString(), DATA); 54 | fs.writeFileSync('README.md', output); 55 | }); 56 | } 57 | 58 | async function action() { 59 | /** 60 | * Fetch Weather 61 | */ 62 | await setWeatherInformation(); 63 | 64 | /** 65 | * Get pictures 66 | */ 67 | await setInstagramPosts(); 68 | 69 | /** 70 | * Generate README 71 | */ 72 | await generateReadMe(); 73 | 74 | /** 75 | * Fermeture de la boutique 👋 76 | */ 77 | await puppeteerService.close(); 78 | } 79 | 80 | action(); 81 | -------------------------------------------------------------------------------- /services/puppeteer.service.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | class PuppeteerService { 3 | browser; 4 | page; 5 | 6 | async init() { 7 | this.browser = await puppeteer.launch({ 8 | args: [ 9 | '--no-sandbox', 10 | '--disable-setuid-sandbox', 11 | '--disable-infobars', 12 | '--window-position=0,0', 13 | '--ignore-certifcate-errors', 14 | '--ignore-certifcate-errors-spki-list', 15 | '--incognito', 16 | '--proxy-server=http=194.67.37.90:3128', 17 | // '--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3312.0 Safari/537.36"', // 18 | ], 19 | // headless: false, 20 | }); 21 | } 22 | 23 | /** 24 | * 25 | * @param {string} url 26 | */ 27 | async goToPage(url) { 28 | if (!this.browser) { 29 | await this.init(); 30 | } 31 | this.page = await this.browser.newPage(); 32 | 33 | await this.page.setExtraHTTPHeaders({ 34 | 'Accept-Language': 'en-US', 35 | }); 36 | 37 | await this.page.goto(url, { 38 | waitUntil: `networkidle0`, 39 | }); 40 | } 41 | 42 | async close() { 43 | await this.page.close(); 44 | await this.browser.close(); 45 | } 46 | 47 | /** 48 | * 49 | * @param {string} acc Account to crawl 50 | * @param {number} n Qty of image to fetch 51 | */ 52 | async getLatestInstagramPostsFromAccount(acc, n) { 53 | try { 54 | const page = `https://dumpor.com/v/${acc}`; 55 | await this.goToPage(page); 56 | let previousHeight; 57 | 58 | previousHeight = await this.page.evaluate(`document.body.scrollHeight`); 59 | await this.page.evaluate(`window.scrollTo(0, document.body.scrollHeight)`); 60 | // 🔽 Doesn't seem to be needed 61 | // await this.page.waitForFunction(`document.body.scrollHeight > ${previousHeight}`); 62 | await this.page.waitFor(1000); 63 | 64 | const nodes = await this.page.evaluate(() => { 65 | const images = document.querySelectorAll(`.content__img`); 66 | return [].map.call(images, img => img.src); 67 | }); 68 | 69 | return nodes.slice(0, 3); 70 | } catch (error) { 71 | console.log('Error', error); 72 | process.exit(); 73 | } 74 | } 75 | 76 | // async getLatestMediumPublications(acc, n) { 77 | // const page = `https://medium.com/${acc}`; 78 | 79 | // await this.goToPage(page); 80 | 81 | // console.log('PP', page); 82 | // let previousHeight; 83 | 84 | // try { 85 | // previousHeight = await this.page.evaluate(`document.body.scrollHeight`); 86 | // console.log('MED1'); 87 | // await this.page.evaluate(`window.scrollTo(0, document.body.scrollHeight)`); 88 | // console.log('MED2', previousHeight); 89 | // await this.page.waitForFunction(`document.body.scrollHeight > ${previousHeight}`); 90 | // console.log('MED3'); 91 | // await this.page.waitFor(1000); 92 | // console.log('MED4'); 93 | 94 | // const nodes = await this.page.evaluate(() => { 95 | // const posts = document.querySelectorAll('.fs.ft.fu.fv.fw.z.c'); 96 | // return [].map.call(posts); 97 | // }); 98 | // console.log('POSTS', nodes); 99 | // return; 100 | // } catch (error) { 101 | // console.log('Error', error); 102 | // process.exit(); 103 | // } 104 | // } 105 | } 106 | 107 | const puppeteerService = new PuppeteerService(); 108 | 109 | module.exports = puppeteerService; 110 | -------------------------------------------------------------------------------- /main.mustache: -------------------------------------------------------------------------------- 1 |
Welcome to my page! I'm Thomas, Fullstack developer from
Lorient, France, currently living in
Stockholm, Sweden.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
| 🎁 Projects | 38 |⭐ Stars | 39 |📚 Forks | 40 |🛎 Issues | 41 |📬 Pull requests | 42 |
| React PullToRefresh component | 47 |||||
| Typescript & React Chrome Extension Starter | 54 |||||
| NodeJs Express TypeScript GraphQL Starter | 61 |
Above are the last 3 pictures posted by
@visitstockholm!
Currently, the weather is: {{city_temperature}}°C, {{city_weather}}Today, the sun rises at {{sun_rise}} and sets at {{sun_set}}.
This README file is generated every 3 hours!Last refresh: {{refresh_date}}
Create your own here!
Welcome to my page! I'm Thomas, Fullstack developer from
Lorient, France, currently living in
Stockholm, Sweden.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
| 🎁 Projects | 38 |⭐ Stars | 39 |📚 Forks | 40 |🛎 Issues | 41 |📬 Pull requests | 42 |
| React PullToRefresh component | 47 |||||
| Typescript & React Chrome Extension Starter | 54 |||||
| NodeJs Express TypeScript GraphQL Starter | 61 |
Above are the last 3 pictures posted by
@visitstockholm!
Currently, the weather is: 11°C, overcast cloudsToday, the sun rises at 06:22 and sets at 19:01.
This README file is generated every 3 hours!Last refresh: Sunday, 18 September, 23:06 CEST
Create your own here!