Skip to content

Commit

Permalink
feat(scripts): scrape and save shows from vogue
Browse files Browse the repository at this point in the history
This patch adds a Scrapy Python script to scrape show data and reviews
from Vogue. Note that this approach only gets the first nine looks for
each show. Vogue implements some JS-only pagination and thus Scrapy is
unable to "load more" looks. I will look into a JS-based scraping
solution that e.g. runs a headless browser, but for now, this works OK.

This patch then adds a Node.js script that will import all those shows
(that are saved from Vogue.com to JSON via Scrapy) to my Postgres
database via the Prisma ORM.

Closes: NC-644
  • Loading branch information
nicholaschiang committed Jul 25, 2023
1 parent 8c59494 commit 986e27d
Show file tree
Hide file tree
Showing 8 changed files with 94,405 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"validate": "run-p \"test --run\" lint typecheck test:e2e:run",
"scripts": "run-s 'scripts:*'",
"scripts:build": "tsc -p scripts/node",
"scripts:run": "node scripts/node/out/show.js | pino-pretty"
"scripts:run": "node scripts/node/out/vogue.js | pino-pretty"
},
"eslintIgnore": [
"/node_modules",
Expand Down
83,024 changes: 83,024 additions & 0 deletions public/data/vogue/shows.json

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ $ source .venv/bin/activate
$ cd scripts
$ scrapy crawl aritzia -L INFO -O json/aritzia.json
```

## Targets

### Vogue

To save show data from Vogue, simply edit the `spiders/vogue.py` spider `start_urls` to point to the latest fashion shows list page.
Then, (assuming you've already run `poetry install` to install the required Python dependencies) run the spider to download the show data to a `shows.json` file:

```
$ scrapy crawl vogue -O shows.json
```

To import that JSON file into Postgres, simply move it to `public/data/vogue/shows.json` (or, alternatively, you can edit the `node/save/vogue.ts` file's `PATH` constant to point to the relative path of `shows.json`) and then run the save script:

```
$ tsc -p scripts/node
$ node scripts/node/out/vogue.js | pino-pretty
```
3 changes: 2 additions & 1 deletion scripts/node/save/shows/hermes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const looks = Array(NUM_LOOKS)
const designer: Prisma.UserCreateInput = {
name: 'Véronique Nichanian',
url: 'https://fr.wikipedia.org/wiki/V%C3%A9ronique_Nichanian',
avatar: 'https://static.nicholas.engineering/designers/veronique-nichanian/avatar.jpg',
avatar:
'https://static.nicholas.engineering/designers/veronique-nichanian/avatar.jpg',
description: `
<p>
Véronique Nichanian was born on May 3, 1954 in Boulogne-Billancourt.In 1976,
Expand Down
154 changes: 154 additions & 0 deletions scripts/node/save/vogue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Script to save the data scraped via `scripts/python/spiders/vogue.py` to the
// Postgres database via the Prisma ORM for type safe data transformations.

import fs from 'fs'
import path from 'path'

import { type Prisma, Sex, SeasonName, PrismaClient } from '@prisma/client'
import ProgressBar from 'progress'

import example from './vogue/resort-2024.json'

const PATH = '../../../public/data/vogue/shows.json'

type Show = (typeof example)[number]

const vogue: Prisma.PublicationCreateInput = {
name: 'Vogue',
avatar: 'https://www.vogue.com/verso/static/vogue/assets/us/logo.svg',
}

const prisma = new PrismaClient({
datasources: { db: { url: process.env.DATABASE_URL } },
})

export async function save() {
const json = fs.readFileSync(path.resolve(__dirname, PATH), 'utf8')
const shows = JSON.parse(json) as Show[]
const bar = new ProgressBar(
'saving vogue shows [:bar] :rate/sps :current/:total :percent :etas',
{
complete: '=',
incomplete: ' ',
width: 20,
total: shows.length,
},
)
/* eslint-disable-next-line no-restricted-syntax */
for await (const show of shows) {
await prisma.show.create({ data: getData(show) })
bar.tick()
}
}

void save()

//////////////////////////////////////////////////////////////////

function caps(sentence: string): string {
return sentence
.split(' ')
.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
.join(' ')
}

function getSeason(show: Show) {
// e.g. "SPRING 2023 READY-TO-WEAR", "RESORT 2024"
const [season, yr] = show.season.split(' ')
let name: SeasonName
switch (season) {
case 'SPRING':
name = SeasonName.SPRING
break
case 'RESORT':
name = SeasonName.RESORT
break
case 'FALL':
name = SeasonName.FALL
break
default:
throw new Error(`Unknown season: ${season}`)
}
const year = Number(yr)
if (Number.isNaN(year)) throw new Error(`Invalid year: ${year}`)
return { name, year }
}

function getData(show: Show) {
const season: Prisma.SeasonCreateInput = getSeason(show)
let review: Prisma.ReviewCreateWithoutShowInput | undefined
if (show.author_name && show.author_url)
review = {
title: show.title,
subtitle: null,
score: null,
summary: null,
content: show.content.join('\n'),
url: show.url,
date: show.date ? new Date(show.date) : null,
author: {
connectOrCreate: {
where: { name: show.author_name },
create: {
name: show.author_name,
url: show.author_url,
avatar: show.author_avatar,
},
},
},
publication: {
connectOrCreate: { where: { name: vogue.name }, create: vogue },
},
}
let name = `${show.brand} ${caps(show.season)}`
if (show.title.includes('Menswear')) name += ' Menswear'

// TODO Vogue will list the same collection twice (once under "Menswear" and
// again without "Menswear") if that collection is unisex. I can fix this by
// looking for collections that have the same review content and other data,
// and then merge those collections under the same unisex name.
const collection: Prisma.CollectionCreateInput = {
name,
sex: show.title.includes('Menswear') ? Sex.MAN : Sex.WOMAN,
season: {
connectOrCreate: {
where: { name_year: { name: season.name, year: season.year } },
create: season,
},
},
}
const looks: Prisma.LookCreateWithoutShowInput[] = show.looks.map((look) => {
const image: Prisma.ImageCreateWithoutLookInput = { url: look.src }
return {
number: look.number,
image: { connectOrCreate: { where: { url: image.url }, create: image } },
}
})
const brand: Prisma.BrandCreateInput = {
name: show.brand,
description: null,
tier: null,
avatar: null,
url: null,
}
const data: Prisma.ShowCreateInput = {
name,
url: show.url,
description: null,
criticReviewSummary: null,
consumerReviewSummary: null,
date: show.date ? new Date(show.date) : null,
location: null,
reviews: review ? { create: review } : undefined,
season: {
connectOrCreate: {
where: { name_year: { name: season.name, year: season.year } },
create: season,
},
},
collections: { connectOrCreate: { where: { name }, create: collection } },
looks: { create: looks },
brands: { connectOrCreate: { where: { name: brand.name }, create: brand } },
}
return data
}
70 changes: 70 additions & 0 deletions scripts/node/save/vogue/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c",
"title": "A.L.C. Resort 2024 Collection | Vogue",
"content": [
"<p>Polish was the guiding idea behind Andrea Lieberman’s resort collection. “Whether it’s a holiday party or events, we are bringing a more sleek feeling to it, which is always kind of my first love and where I started,” Lieberman said. This was reflected in details as granular as the baguette-shaped rhinestones embellishing the satin dresses and the rectangular sequins on the compact knit dresses. Sharp edges, clean lines—but not simple.</p>",
"<p>Lieberman has always prided herself on making real clothing with a few special details to make it stand out. This season, those core items include miles-long flared trousers paired with a black cropped blazer, several duchesse satin dresses and skirts with bijoux embellishments, strapless silhouettes in smart black going out tops and a raspberry ruffled mini dress. Nothing groundbreaking, but the collection is infinitely wearable and, yes, sleek.</p>"
],
"brand": "A.L.C.",
"season": "RESORT 2024",
"author_name": "Sarah Spellings",
"author_url": "https://www.vogue.com/contributor/sarah-spellings",
"author_avatar": "https://assets.vogue.com/photos/63e28ecedb8fe3439bbc31b5/1:1/w_90%2Cc_limit/undefined",
"date": "June 11, 2023",
"looks": [
{
"number": 1,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#1",
"src": "https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_2560%2Cc_limit/00001-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_120,c_limit/00001-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_240,c_limit/00001-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_320,c_limit/00001-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_640,c_limit/00001-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_960,c_limit/00001-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_1280,c_limit/00001-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_1600,c_limit/00001-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_1920,c_limit/00001-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435ab117ea73b4611c52e/master/w_2240,c_limit/00001-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 2,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#2",
"src": "https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_2560%2Cc_limit/00002-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_120,c_limit/00002-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_240,c_limit/00002-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_320,c_limit/00002-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_640,c_limit/00002-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_960,c_limit/00002-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_1280,c_limit/00002-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_1600,c_limit/00002-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_1920,c_limit/00002-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435abbbb79c09d48cdad9/master/w_2240,c_limit/00002-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 3,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#3",
"src": "https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_2560%2Cc_limit/00003-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_120,c_limit/00003-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_240,c_limit/00003-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_320,c_limit/00003-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_640,c_limit/00003-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_960,c_limit/00003-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_1280,c_limit/00003-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_1600,c_limit/00003-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_1920,c_limit/00003-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435acc943a2672e3e7d37/master/w_2240,c_limit/00003-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 4,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#4",
"src": "https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_2560%2Cc_limit/00004-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_120,c_limit/00004-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_240,c_limit/00004-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_320,c_limit/00004-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_640,c_limit/00004-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_960,c_limit/00004-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_1280,c_limit/00004-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_1600,c_limit/00004-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_1920,c_limit/00004-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435aee1122c2ab0dca909/master/w_2240,c_limit/00004-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 5,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#5",
"src": "https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_2560%2Cc_limit/00005-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_120,c_limit/00005-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_240,c_limit/00005-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_320,c_limit/00005-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_640,c_limit/00005-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_960,c_limit/00005-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_1280,c_limit/00005-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_1600,c_limit/00005-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_1920,c_limit/00005-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435b00def6fd42956e337/master/w_2240,c_limit/00005-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 6,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#6",
"src": "https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_2560%2Cc_limit/00006-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_120,c_limit/00006-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_240,c_limit/00006-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_320,c_limit/00006-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_640,c_limit/00006-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_960,c_limit/00006-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_1280,c_limit/00006-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_1600,c_limit/00006-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_1920,c_limit/00006-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435afed9500b6de5d4f87/master/w_2240,c_limit/00006-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 7,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#7",
"src": "https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_2560%2Cc_limit/00007-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_120,c_limit/00007-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_240,c_limit/00007-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_320,c_limit/00007-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_640,c_limit/00007-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_960,c_limit/00007-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_1280,c_limit/00007-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_1600,c_limit/00007-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_1920,c_limit/00007-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435b1bbb79c09d48cdadb/master/w_2240,c_limit/00007-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 8,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#8",
"src": "https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_2560%2Cc_limit/00008-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_120,c_limit/00008-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_240,c_limit/00008-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_320,c_limit/00008-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_640,c_limit/00008-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_960,c_limit/00008-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_1280,c_limit/00008-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_1600,c_limit/00008-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_1920,c_limit/00008-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435b4dc90cc14d7b8ffe5/master/w_2240,c_limit/00008-alc-resort-2024-credit-brand.jpg 2240w"
},
{
"number": 9,
"url": "https://www.vogue.com/fashion-shows/resort-2024/a-l-c/slideshow/collection#9",
"src": "https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_2560%2Cc_limit/00009-alc-resort-2024-credit-brand.jpg",
"srcset": "https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_120,c_limit/00009-alc-resort-2024-credit-brand.jpg 120w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_240,c_limit/00009-alc-resort-2024-credit-brand.jpg 240w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_320,c_limit/00009-alc-resort-2024-credit-brand.jpg 320w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_640,c_limit/00009-alc-resort-2024-credit-brand.jpg 640w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_960,c_limit/00009-alc-resort-2024-credit-brand.jpg 960w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_1280,c_limit/00009-alc-resort-2024-credit-brand.jpg 1280w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_1600,c_limit/00009-alc-resort-2024-credit-brand.jpg 1600w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_1920,c_limit/00009-alc-resort-2024-credit-brand.jpg 1920w, https://assets.vogue.com/photos/648435b2bbb79c09d48cdadd/master/w_2240,c_limit/00009-alc-resort-2024-credit-brand.jpg 2240w"
}
]
}
Loading

0 comments on commit 986e27d

Please sign in to comment.