REMASTER UPDATE

This commit is contained in:
Stefano Haagmans 2022-05-13 23:04:01 +02:00
commit 29068cb163
245 changed files with 21516 additions and 11620 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = tab
tab_size = 4

24
.gitattributes vendored Normal file
View file

@ -0,0 +1,24 @@
* text=auto
*.bash text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.css text diff=css
*.html text diff=html
*.js text
*.json text eol=lf
*.py text diff=python
*.sh text eol=lf
*.ico binary
*.jpg binary
*.jpeg binary
*.png binary
*.svg text
*.webp binary
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
*.woff2 binary

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
web/atlas.json @placeAtlas/archival-team

43
.gitignore vendored
View file

@ -1,17 +1,54 @@
# Secrets
**/**/credentials
credentials
praw
# Temporary files
temp_atlas.json
manual_atlas.json
manual_atlas.txt
temp.json
temp.js
allCharacters.txt
combined.js
# Excluded files
users.html
oldusers.html
web/_js/minified.js
allCharacters.txt
combined.js
*.DS_Store
.vscode/
web/atlas-before-ids-migration.json
tools/read-ids-temp.txt
# Build files
node_modules/
/package.json
/package-lock.json
/yarn.lock
dist*/
# Cache folders
*.pyc
tools/read-ids-temp.txt
.cache
.parcel-cache
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
pip-log.txt
pip-delete-this-directory.txt
# Enviroment folders
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

View file

@ -1,22 +1,22 @@
# Contributing
This project is open source. You may contribute to the project by submitting a Pull Request on the GitHub repo.
This project is open source. You may contribute to the project by submitting a Pull Request on the GitHub repo or send your submissions through Reddit. Other than than, you cau use Discord or Reddit for help.
## Map Contributions
<h3><b>WE ONLY ACCEPT NEW CONTRIBUTIONS ON REDDIT</b></h3>
**WE ONLY ACCEPT NEW CONTRIBUTIONS ON REDDIT!**
To contribute to the map, we require a certain format for artwork region and labels. This can be generated on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw) on the website.
### Reddit Submission
### Reddit
1. Follow the instructions on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw), then click "Post Direct to Reddit".
1. Follow the instructions on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw), then post it to Reddit by clicking "Post Direct to Reddit".
2. Flair your post with the "New Entry" tag.
3. A moderator will accept your contribution shortly.
<!--
### GitHub Submission
### GitHub
1. Create a fork of our repo.
2. Enter your data into the `web/atlas.json` file, with the correct format and ID number.
@ -26,10 +26,18 @@ To contribute to the map, we require a certain format for artwork region and lab
## Map Edits
### Reddit
1. Click "Edit" on a entry and follow the instructions there, then post it to Reddit by clicking "Post Direct to Reddit".
2. Flair your post with the "Edit Entry" tag.
3. A moderator will accept your contribution shortly.
### GitHub
1. Create a fork of our repo.
2. Enter your data into the `web/atlas.json` file, with the correct format and ID number.
3. Create a Pull Request against the `/cleanup` branch.
3. Create a Pull Request against the `cleanup` branch.
## Cleaning Contributions
If you spot a duplicate, please PR against `/cleanup`. To help find duplicates, append `?mode=overlap` to the url: [`https://place-atlas.stefanocoding.me?mode=overlap`](https://place-atlas.stefanocoding.me?mode=overlap).
If you spot a duplicate, please PR against the `cleanup` branch. To help find duplicates, [use the Overlap mode](https://place-atlas.stefanocoding.me?mode=overlap).

View file

@ -6,18 +6,21 @@
[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/placeAtlas2?color=%23FF4500&label=r%2FplaceAtlas2&logo=reddit&logoColor=white)](https://www.reddit.com/r/placeAtlas2/)
[![Website](https://img.shields.io/static/v1?label=website&message=place-atlas.stefanocoding.me&color=blue)](https://place-atlas.stefanocoding.me/)
# The 2022 Place Atlas
# The 2022 r/place Atlas
The /r/place Atlas is a project aiming to catalog all the artworks created during Reddit's 2022 /r/place event.
This project was created by Roland Rytz and is licensed under the GNU Affero General Public License v3.0.
The 2022 r/place Atlas is a project aiming to catalog all the artworks created during Reddit's 2022 r/place event.
---
This project was established by Roland Rytz for the event in 2017, and further developed by Place Atlas team and contributors.
*You can check out the website by clicking [here](https://place-atlas.stefanocoding.me/).*
This project is licensed under the GNU Affero General Public License v3.0.
If you don't know GitHub and wanted to submit new entries or request changes to existing ones, please visit [/r/placeAtlas2](https://www.reddit.com/r/placeAtlas2/).
You can check out the website by clicking [here](https://place-atlas.stefanocoding.me/).
## How To Contribute
If you don't know GitHub and wanted to submit new entries or request changes to existing ones, please visit [r/placeAtlas2](https://www.reddit.com/r/placeAtlas2/).
## Contributing
This project is open source. You may contribute to the project by submitting a Pull Request on the GitHub repo or send your submissions through Reddit. Other than than, you cau use Discord or Reddit for help.
### Map Contributions
@ -25,15 +28,15 @@ If you don't know GitHub and wanted to submit new entries or request changes to
To contribute to the map, we require a certain format for artwork region and labels. This can be generated on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw) on the website.
#### Reddit Submission
#### Reddit
1. Follow the instructions on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw), then click "Post Direct to Reddit".
1. Follow the instructions on the [contributing page](https://place-atlas.stefanocoding.me/index.html?mode=draw), then post it to Reddit by clicking "Post Direct to Reddit".
2. Flair your post with the "New Entry" tag.
3. A moderator will accept your contribution shortly.
<!--
#### GitHub Submission
#### GitHub
1. Create a fork of our repo.
2. Enter your data into the `web/_js/atlas.js` file, with the correct format and ID number.
@ -43,10 +46,18 @@ To contribute to the map, we require a certain format for artwork region and lab
### Map Edits
#### Reddit
1. Click "Edit" on a entry and follow the instructions there, then post it to Reddit by clicking "Post Direct to Reddit".
2. Flair your post with the "Edit Entry" tag.
3. A moderator will accept your contribution shortly.
#### GitHub
1. Create a fork of our repo.
2. Enter your data into the `web/atlas.json` file, with the correct format and ID number.
3. Create a Pull Request against the `cleanup` branch.
### Cleaning Contributions
If you spot a duplicate, please PR against the `cleanup` branch. To help find duplicates, append `?mode=overlap` to the url: [`https://place-atlas.stefanocoding.me?mode=overlap`](https://place-atlas.stefanocoding.me?mode=overlap).
If you spot a duplicate, please PR against the `cleanup` branch. To help find duplicates, [use the Overlap mode](https://place-atlas.stefanocoding.me?mode=overlap).

View file

View file

@ -1,10 +1,6 @@
[[headers]]
for = "/*"
[headers.values]
Access-Control-Allow-Origin = "*"
[build]
publish = "dist/"
command = "FILE=tools/ci/build-prod.sh; rm -rf dist/; if [ -f $FILE ]; then bash $FILE; else cp -r web/ dist/; fi"
[[headers]]
for = "/_img/place/*.png"
[headers.values]
# 28 days
cache-control = "public, max-age=604800"
[build.environment]
PYTHON_VERSION = "3.8"

192
tools/calculate_center.py Normal file
View file

@ -0,0 +1,192 @@
"""
From https://github.com/Twista/python-polylabel/,
which is in turn implemented from https://github.com/mapbox/polylabel
"""
from math import sqrt, log10
import time
from typing import Tuple, List
# Python3
from queue import PriorityQueue
from math import inf
Point = Tuple[float, float]
Polygon = List[Point]
SQRT2 = sqrt(2)
def _point_to_polygon_distance(x: float, y: float, polygon: Polygon) -> float:
inside: bool = False
min_distance_squared: float = inf
previous: Point = polygon[-1]
for current in polygon:
if ((current[1] > y) != (previous[1] > y) and
(x < (previous[0] - current[0]) * (y - current[1]) / (previous[1] - current[1]) + current[0])):
inside = not inside
min_distance_squared = min(min_distance_squared, _get_segment_distance_squared(x, y, current, previous))
previous = current
result: float = sqrt(min_distance_squared)
if not inside:
return -result
return result
def _get_segment_distance_squared(px: float, py: float, point_a: Point, point_b: Point) -> float:
x: float = point_a[0]
y: float = point_a[1]
dx: float = point_b[0] - x
dy: float = point_b[1] - y
if dx != 0 or dy != 0:
t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy)
if t > 1:
x = point_b[0]
y = point_b[1]
elif t > 0:
x += dx * t
y += dy * t
dx = px - x
dy = py - y
return dx * dx + dy * dy
class Cell(object):
def __init__(self, x: float, y: float, h: float, polygon: Polygon, centroid: Point):
self.h: float = h
self.y: float = y
self.x: float = x
min_dist = _point_to_polygon_distance(x, y, polygon)
self.min_dist: float = min_dist
self.center_dist: float = (centroid[0] - x) ** 2 + (centroid[1] - y) ** 2
self.max = self.min_dist + self.h * SQRT2
self.weight = -self.center_dist - self.max
def __lt__(self, other):
return self.max < other.max
def __lte__(self, other):
return self.max <= other.max
def __gt__(self, other):
return self.max > other.max
def __gte__(self, other):
return self.max >= other.max
def __eq__(self, other):
return self.max == other.max
def _get_centroid(polygon: Polygon) -> Point:
area: float = 0
x: float = 0
y: float = 0
previous: Point = polygon[-1]
for current in polygon:
f: float = current[0] * previous[1] - previous[0] * current[1]
x += (current[0] + previous[0]) * f
y += (current[1] + previous[1]) * f
area += f * 3
previous =current
if area == 0:
return (polygon[0][0], polygon[0][1])
return (x / area, y / area)
def _get_centroid_cell(polygon: Polygon, centroid: Point) -> Cell:
return Cell(centroid[0], centroid[1], 0, polygon, centroid)
def polylabel(polygon: Polygon, precision: float=0.5, debug: bool=False):
# find bounding box
first_item: Point = polygon[0]
min_x: float = first_item[0]
min_y: float = first_item[1]
max_x: float = first_item[0]
max_y: float = first_item[1]
for p in polygon:
if p[0] < min_x:
min_x = p[0]
if p[1] < min_y:
min_y = p[1]
if p[0] > max_x:
max_x = p[0]
if p[1] > max_y:
max_y = p[1]
width: float = max_x - min_x
height: float = max_y - min_y
cell_size: float = min(width, height)
h: float = cell_size / 2.0
cell_queue: PriorityQueue[Tuple[float, int, Cell]] = PriorityQueue()
if cell_size == 0:
return [(max_x - min_x) / 2, (max_y - min_y) / 2]
centroid: Point = _get_centroid(polygon)
# cover polygon with initial cells
x: float = min_x
while x < max_x:
y: float = min_y
while y < max_y:
c: Cell = Cell(x + h, y + h, h, polygon, centroid)
y += cell_size
cell_queue.put((c.weight, time.time(), c))
x += cell_size
best_cell: Cell = _get_centroid_cell(polygon, centroid)
bbox_cell: Cell = Cell(min_x + width / 2, min_y + height / 2, 0, polygon, centroid)
if bbox_cell.min_dist > best_cell.min_dist:
best_cell = bbox_cell
# how much closer is an point allowed to be to the border,
# while having a shorter distance to the centroid
threshold: float = log10(cell_size) / 3.0
num_of_probes = cell_queue.qsize()
while not cell_queue.empty():
_, __, cell = cell_queue.get()
# update if either the cell is further from the edge,
# or if it is sufficiently similary far from the edge,
# but closer to the centroid
if (cell.min_dist > best_cell.min_dist
or (
cell.center_dist < best_cell.center_dist
and cell.min_dist > best_cell.min_dist - threshold
)
):
best_cell = cell
if debug:
print(f'found best {round(cell.min_dist, 4)};{round(sqrt(cell.center_dist), 4)} after {num_of_probes} probes')
if cell.max - best_cell.min_dist <= precision:
continue
h = cell.h / 2
c = Cell(cell.x - h, cell.y - h, h, polygon, centroid)
cell_queue.put((c.weight, time.time(), c))
c = Cell(cell.x + h, cell.y - h, h, polygon, centroid)
cell_queue.put((c.weight, time.time(), c))
c = Cell(cell.x - h, cell.y + h, h, polygon, centroid)
cell_queue.put((c.weight, time.time(), c))
c = Cell(cell.x + h, cell.y + h, h, polygon, centroid)
cell_queue.put((c.weight, time.time(), c))
num_of_probes += 4
if debug:
print(f'num probes: {num_of_probes}')
print(f'best distance: {best_cell.min_dist}')
return [best_cell.x, best_cell.y]

4
tools/ci/.parcelrc Normal file
View file

@ -0,0 +1,4 @@
{
"extends": "@parcel/config-default",
"namers": ["parcel-namer-rewrite", "..."]
}

25
tools/ci/build-prod.sh Normal file
View file

@ -0,0 +1,25 @@
# This command should be run on CI/Netlify enviroment!
# If you really wanted to run it, run it on the root.
rm -rf dist-temp
rm -rf dist
rm -rf .parcel-cache
cp -r web/ dist-temp/
cp tools/ci/postcss.config.js ./
cp tools/ci/package.json ./
cp tools/ci/.parcelrc ./
npm i
python tools/ci/cdn-to-local.py
npx parcel build dist-temp/index.html dist-temp/**.html --dist-dir "dist" --no-source-maps --no-content-hash
rm -rf dist-temp
rm -rf postcss.config.js
rm -rf .parcelrc
cp -r web/_img/ dist/
cp web/atlas.json dist/
cp web/*.txt dist/
cp web/_headers dist/
cp web/favicon.ico dist/

49
tools/ci/cdn-to-local.py Normal file
View file

@ -0,0 +1,49 @@
import glob
import string
import re
import hashlib
import os
import urllib.request
cdns = []
def join_rel_path(path1, path2):
path = os.path.join(path1, path2)
path = re.sub(r"\/[^\/]+?\/\.", "", path)
return path
for name in glob.glob("web/**.html"):
with open(name, 'r', encoding='utf-8') as file:
file_string = file.read()
urls = re.findall('"(https:\/\/cdn.jsdelivr.net\/(.+?))"', file_string)
for url_groups in urls:
url: string = url_groups[0]
os.makedirs("dist-temp/cdn/" + hashlib.md5(url.encode()).hexdigest(), exist_ok=True)
new_url = "cdn/" + hashlib.md5(url.encode()).hexdigest() + "/" + os.path.basename(url)
print(url)
urllib.request.urlretrieve(url, "dist-temp/" + new_url)
file_string = file_string.replace(url, new_url)
cdns.append((url, new_url, hashlib.md5(url.encode()).hexdigest()))
file_string = file_string.replace("crossorigin=\"anonymous\"", "")
# print(file_string).replace("\?.+$", "")
name = name.replace('web/', 'dist-temp/')
with open(name, 'w', encoding='utf-8') as file:
file.write(file_string)
for cdn in cdns:
parent_url, parent_new_url, hash = cdn
name = "dist-temp/" + parent_new_url
with open(name, 'r', encoding='utf-8') as file:
file_string = file.read()
urls = re.findall('\("(.\/(.+?))"\)', file_string)
for url_groups in urls:
url_orig = url_groups[0]
url: string = join_rel_path(parent_url, url_groups[0])
url = re.sub("\?.+$", "", url)
os.makedirs("dist-temp/cdn/" + hashlib.md5(url.encode()).hexdigest(), exist_ok=True)
new_url = "cdn/" + hashlib.md5(url.encode()).hexdigest() + "/" + os.path.basename(url)
print(url)
urllib.request.urlretrieve(url, "dist-temp/" + new_url)
file_string = file_string.replace(url_orig, new_url.replace("cdn/", "../"))
with open(name, 'w', encoding='utf-8') as file:
file.write(file_string)

30
tools/ci/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"devDependencies": {
"@fullhuman/postcss-purgecss": "^4.1.3",
"@parcel/packager-raw-url": "^2.5.0",
"@parcel/transformer-jsonld": "^2.5.0",
"@parcel/transformer-webmanifest": "^2.5.0",
"parcel-namer-rewrite": "^2.0.0-rc.2",
"parcel": "^2.5.0",
"postcss": "^8.4.12"
},
"parcel-namer-rewrite": {
"rules": {
"^(icon-.+)\\.(svg|png|gif|jpg|jpeg)": "_img/pwa/$1.$2",
"(.*)\\.(svg|png|gif|jpg|jpeg)": "_img/$1.$2",
"(.*)\\.(css|woff2?)": "_css/$1{.hash}.$2",
"(.*)\\.(js)": "_js/$1{.hash}.$2"
}
},
"browserslist": [
">= 0.5%",
"last 2 major versions",
"not dead",
"Chrome >= 60",
"Firefox >= 60",
"Firefox ESR",
"iOS >= 12",
"Safari >= 12",
"not Explorer <= 11"
]
}

View file

@ -0,0 +1,22 @@
const purgecss = require("@fullhuman/postcss-purgecss");
const plugins = [];
if (process.env.NODE_ENV === "production") {
plugins.push(
purgecss({
content: [
'./dist-temp/*.html',
'./dist-temp/**/*.html',
'./dist-temp/*.js',
'./dist-temp/**/*.js',
'./dist-temp/*.svg',
'./dist-temp/**/*.svg'
]
})
);
}
module.exports = {
plugins: plugins
};

View file

@ -1,350 +1,382 @@
#!/usr/bin/python
import re
import json
"""
Examples:
1. - /r/place
- r/place
2. /rplace
3. - https://www.reddit.com/r/place
- www.reddit.com/r/place
- reddit.com/r/place
UNUSED AND FAULTY
4. - https://place.reddit.com
- place.reddit.com
5. - [https://place.reddit.com](https://place.reddit.com)
- [place.reddit.com](https://place.reddit.com)
"""
FS_REGEX = {
"commatization": r'( *(,+ +|,+ |,+)| +)(and|&|;)( *(,+ +|,+ |,+)| +)|, *$| +',
"pattern1": r'\/*[rR]\/([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/$)?',
"pattern2": r'^\/*[rR](?!\/)([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/$)?',
"pattern3": r'(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/r\/([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/[^" ]*)*',
"pattern1user": r'\/*(?:u|user)\/([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/$)?',
"pattern2user": r'^\/*(?:u|user)(?!\/)([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/$)?',
"pattern3user": r'(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/(?:u|user)\/([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/[^" ]*)*',
# "pattern4": r'(?:https?:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*',
# "pattern5": r'\[(?:https?:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*\]\((?:https:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*\)"',
}
VALIDATE_REGEX = {
"subreddit": r'^ *\/?r\/([A-Za-z0-9][A-Za-z0-9_]{1,20}) *(, *\/?r\/([A-Za-z0-9][A-Za-z0-9_]{1,20}) *)*$|^$',
"website": r'^https?://[^\s/$.?#].[^\s]*$|^$'
}
CL_REGEX = r'\[(.+?)\]\((.+?)\)'
CWTS_REGEX = {
"url": r'^(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/r\/([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/)$',
"subreddit": r'^\/*[rR]\/([A-Za-z0-9][A-Za-z0-9_]{1,20})\/?$'
}
CSTW_REGEX = {
"website": r'^https?://[^\s/$.?#].[^\s]*$',
"user": r'^\/*u\/([A-Za-z0-9][A-Za-z0-9_]{1,20})$'
}
# r/... to /r/...
SUBREDDIT_TEMPLATE = r"/r/\1"
USER_TEMPLATE = r"/u/\1"
def format_subreddit(entry: dict):
"""
Fix formatting of the value on "subreddit".
"""
if not "subreddit" in entry or not entry['subreddit']:
return entry
subredditLink = entry["subreddit"]
subredditLink = re.sub(FS_REGEX["commatization"], ', ', subredditLink)
subredditLink = re.sub(FS_REGEX["pattern3"], SUBREDDIT_TEMPLATE, subredditLink)
subredditLink = re.sub(FS_REGEX["pattern1"], SUBREDDIT_TEMPLATE, subredditLink)
subredditLink = re.sub(FS_REGEX["pattern2"], SUBREDDIT_TEMPLATE, subredditLink)
subredditLink = re.sub(FS_REGEX["pattern3user"], USER_TEMPLATE, subredditLink)
subredditLink = re.sub(FS_REGEX["pattern1user"], USER_TEMPLATE, subredditLink)
subredditLink = re.sub(FS_REGEX["pattern2user"], USER_TEMPLATE, subredditLink)
if not subredditLink:
return entry
entry["subreddit"] = subredditLink
return entry
def collapse_links(entry: dict):
if "website" in entry and entry['website']:
website = entry["website"];
if re.search(CL_REGEX, website):
match = re.search(CL_REGEX, website)
if match.group(1) == match.group(2):
website = match.group(2)
entry["website"] = website
if "subreddit" in entry and entry['subreddit']:
subreddit = entry["subreddit"];
if re.search(CL_REGEX, subreddit):
match = re.search(CL_REGEX, subreddit)
if match.group(1) == match.group(2):
subreddit = match.group(2)
entry["subreddit"] = subreddit
return entry
def remove_extras(entry: dict):
"""
Removing unnecessary extra characters and converts select characters.
"""
if "subreddit" in entry and entry["subreddit"]:
# if not entry["subreddit"].startswith('/r/'):
# entry["subreddit"] = re.sub(r'^(.*)(?=\/r\/)', r'', entry["subreddit"])
entry["subreddit"] = re.sub(r'[.,]+$', r'', entry["subreddit"])
for key in entry:
if not entry[key] or not isinstance(entry[key], str):
continue
# Leading and trailing spaces
entry[key] = entry[key].strip()
# Double characters
entry[key] = re.sub(r' {2,}(?!\n)', r' ', entry[key])
entry[key] = re.sub(r' {3,}\n', r' ', entry[key])
entry[key] = re.sub(r'\n{3,}', r'\n\n', entry[key])
entry[key] = re.sub(r'r\/{2,}', r'r\/', entry[key])
entry[key] = re.sub(r',{2,}', r',', entry[key])
# Smart quotation marks
entry[key] = re.sub(r'[\u201c\u201d]', '"', entry[key])
entry[key] = re.sub(r'[\u2018\u2019]', "'", entry[key])
# Psuedo-empty strings
if entry[key] in ["n/a", "N/A", "na", "NA", "-", "null", "none", "None"]:
entry[key] = ""
return entry
def remove_duplicate_points(entry: dict):
"""
Removes points from paths that occur twice after each other
"""
path: list = entry['path']
previous: list = path[0]
for i in range(len(path)-1, -1, -1):
current: list = path[i]
if current == previous:
path.pop(i)
previous = current
return entry
def fix_r_caps(entry: dict):
"""
Fixes capitalization of /r/. (/R/place -> /r/place)
"""
if not "description" in entry or not entry['description']:
return entry
entry["description"] = re.sub(r'([^\w]|^)\/R\/', '\1/r/', entry["description"])
entry["description"] = re.sub(r'([^\w]|^)R\/', '\1r/', entry["description"])
return entry
def fix_no_protocol_urls(entry: dict):
"""
Fixes URLs with no protocol by adding "https://" protocol.
"""
if not "website" in entry or not entry['website']:
return entry
if not entry["website"].startswith("http"):
entry["website"] = "https://" + entry["website"]
return entry
def convert_website_to_subreddit(entry: dict):
"""
Converts the subreddit link on "website" to "subreddit" if possible.
"""
if not "website" in entry or not entry['website']:
return entry
if re.match(CWTS_REGEX["url"], entry["website"]):
new_subreddit = re.sub(CWTS_REGEX["url"], SUBREDDIT_TEMPLATE, entry["website"])
if (new_subreddit.lower() == entry["subreddit"].lower()):
entry["website"] = ""
elif not "subreddit" in entry or entry['subreddit'] == "":
entry["subreddit"] = new_subreddit
entry["website"] = ""
elif re.match(CWTS_REGEX["subreddit"], entry["website"]):
new_subreddit = re.sub(CWTS_REGEX["subreddit"], SUBREDDIT_TEMPLATE, entry["website"])
if (new_subreddit.lower() == entry["subreddit"].lower()):
entry["website"] = ""
elif not "subreddit" in entry or entry['subreddit'] == "":
entry["subreddit"] = new_subreddit
entry["website"] = ""
return entry
def convert_subreddit_to_website(entry: dict):
"""
Converts the links on "subreddit" to a "website" if needed. This also supports Reddit users (/u/reddit).
"""
if not "subreddit" in entry or not entry['subreddit']:
return entry
if re.match(CSTW_REGEX["website"], entry["subreddit"]):
if (entry["website"].lower() == entry["subreddit"].lower()):
entry["subreddit"] = ""
elif not "website" in entry or entry['website'] == "":
entry["website"] = entry["subreddit"]
entry["subreddit"] = ""
elif re.match(CSTW_REGEX["user"], entry["subreddit"]):
if not "website" in entry or entry['website'] == "":
username = re.match(CSTW_REGEX["user"], entry["subreddit"]).group(1)
entry["website"] = "https://www.reddit.com/user/" + username
entry["subreddit"] = ""
return entry
def calculate_center(path: list):
"""
Caluclates the center of a polygon
adapted from /web/_js/draw.js:calucalteCenter()
"""
area = 0
x = 0
y = 0
for i in range(len(path)):
point1 = path[i]
point2 = path[i-1 if i != 0 else len(path)-1]
f = point1[0] * point2[1] - point2[0] * point1[1]
area += f
x += (point1[0] + point2[0]) * f
y += (point1[1] + point2[1]) * f
area *= 3
if area != 0:
return [x // area + 0.5, y // area + 0.5]
else:
# get the center of a straight line
max_x = max(i[0] for i in path)
min_x = min(i[0] for i in path)
max_y = max(i[1] for i in path)
min_y = min(i[1] for i in path)
return [(max_x + min_x) // 2 + 0.5, (max_y + min_y) // 2 + 0.5]
def update_center(entry: dict):
"""
checks if the center of a entry is up to date, and updates it if it's either missing or outdated
"""
if 'path' not in entry:
return entry
path = entry['path']
if len(path) > 1:
calculated_center = calculate_center(path)
if 'center' not in entry or entry['center'] != calculated_center:
entry['center'] = calculated_center
return entry
def validate(entry: dict):
"""
Validates the entry. Catch errors and tell warnings related to the entry.
Status code key:
0: All valid, no problems
1: Informational logs that may be ignored
2: Warnings that may effect user experience when interacting with the entry
3: Errors that make the entry inaccessible or broken.
"""
return_status = 0
if (not "id" in entry or (not entry['id'] and not entry['id'] == 0)):
print(f"Wait, no id here! How did this happened? {entry}")
return_status = 3
entry['id'] = '[MISSING_ID]'
if not ("path" in entry and isinstance(entry["path"], list) and len(entry["path"]) > 0):
print(f"Entry {entry['id']} has no points!")
return_status = 3
elif len(entry["path"]) < 3:
print(f"Entry {entry['id']} only has {len(entry['path'])} point(s)!")
return_status = 3
for key in entry:
if key in VALIDATE_REGEX and not re.match(VALIDATE_REGEX[key], entry[key]):
if return_status < 2: return_status = 2
print(f"{key} of entry {entry['id']} is still invalid! {entry[key]}")
return return_status
def per_line_entries(entries: list):
"""
Returns a string of all the entries, with every entry in one line.
"""
out = "[\n"
for entry in entries:
if entry:
out += json.dumps(entry, ensure_ascii=False) + ",\n"
out = out[:-2] + "\n]"
return out
def format_all(entry: dict, silent=False):
"""
Format using all the available formatters.
Outputs a tuple containing the entry and the validation status code.
Status code key:
0: All valid, no problems
1: Informational logs that may be ignored
2: Warnings that may effect user experience when interacting with the entry
3: Errors that make the entry inaccessible or broken.
"""
def print_(*args, **kwargs):
if not silent:
print(*args, **kwargs)
print_("Fixing r/ capitalization...")
entry = fix_r_caps(entry)
print_("Fix formatting of subreddit...")
entry = format_subreddit(entry)
print_("Collapsing Markdown links...")
entry = collapse_links(entry)
print_("Converting website links to subreddit (if possible)...")
entry = convert_website_to_subreddit(entry)
print_("Converting subreddit links to website (if needed)...")
entry = convert_subreddit_to_website(entry)
print_("Fixing links without protocol...")
entry = fix_no_protocol_urls(entry)
print_("Removing extras...")
entry = remove_extras(entry)
print_("Removing duplicate points...")
entry = remove_duplicate_points(entry)
print_("Updating center...")
entry = update_center(entry)
print_("Validating...")
status_code = validate(entry)
print_("Completed!")
return ( entry, status_code )
if __name__ == '__main__':
def go(path):
print(f"Formatting {path}...")
with open(path, "r+", encoding='UTF-8') as f1:
entries = json.loads(f1.read())
for i in range(len(entries)):
entry_formatted, validation_status = format_all(entries[i], True)
if validation_status > 2:
print(f"Entry {entry_formatted['id']} will be removed! {json.dumps(entry_formatted)}")
entries[i] = None
else:
entries[i] = entry_formatted
if not (i % 500):
print(f"{i} checked.")
print(f"{len(entries)} checked.")
with open(path, "w", encoding='UTF-8') as f2:
f2.write(per_line_entries(entries))
print("Writing completed. All done.")
go("../web/atlas.json")
#!/usr/bin/python
import re
import json
import traceback
"""
Examples:
1. - /r/place
- r/place
2. /rplace
3. - https://www.reddit.com/r/place
- www.reddit.com/r/place
- reddit.com/r/place
UNUSED AND FAULTY
4. - https://place.reddit.com
- place.reddit.com
5. - [https://place.reddit.com](https://place.reddit.com)
- [place.reddit.com](https://place.reddit.com)
"""
FS_REGEX = {
"commatization": r'[,;& ]+(?:and)?[,;& ]*?',
"pattern1": r'\/*[rR]\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/$)?',
"pattern2": r'^\/*[rR](?!\/)([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/$)?',
"pattern3": r'(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/r\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/[^" ]*)*',
"pattern1user": r'\/*(?:u|user)\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/$)?',
"pattern2user": r'^\/*(?:u|user)(?!\/)([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/$)?',
"pattern3user": r'(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/(?:u|user)\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/[^" ]*)*',
"pattern1new": r'(?:(?:(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com)?\/)?[rR]\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/[^" ]*)*'
# "pattern4": r'(?:https?:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*',
# "pattern5": r'\[(?:https?:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*\]\((?:https:\/\/)?(?!^www\.)(.+)\.reddit\.com(?:\/[^"]*)*\)"',
}
VALIDATE_REGEX = {
"subreddit": r'^ *\/?r\/([A-Za-z0-9][A-Za-z0-9_]{2,20}) *(, *\/?r\/([A-Za-z0-9][A-Za-z0-9_]{2,20}) *)*$|^$',
"website": r'^https?://[^\s/$.?#].[^\s]*$|^$'
}
CL_REGEX = r'\[(.+?)\]\((.+?)\)'
CWTS_REGEX = {
"url": r'^(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com\/r\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/)?$',
"subreddit": r'^\/*[rR]\/([A-Za-z0-9][A-Za-z0-9_]{2,20})\/?$'
}
CSTW_REGEX = {
"website": r'^https?://[^\s/$.?#].[^\s]*$',
"user": r'^\/*u\/([A-Za-z0-9][A-Za-z0-9_]{2,20})$'
}
CWTD_REGEX = r'^(?:(?:https?:\/\/)?(?:www\.)?(?:(?:discord)?\.?gg|discord(?:app)?\.com\/invite)\/)?([^\s/]+?)$'
# r/... to /r/...
SUBREDDIT_TEMPLATE = r"/r/\1"
USER_TEMPLATE = r"/u/\1"
def format_subreddit(entry: dict):
"""
Fix formatting of the value on "subreddit".
"""
if "links" in entry and "subreddit" in entry["links"]:
for i in range(len(entry["links"]["subreddit"])):
subredditLink = entry["links"]["subreddit"][i]
subredditLink = re.sub(FS_REGEX["pattern3"], r"\1", subredditLink)
subredditLink = re.sub(FS_REGEX["pattern1new"], r"\1", subredditLink)
entry["links"]["subreddit"][i] = subredditLink
return entry
def collapse_links(entry: dict):
"""
Collapses Markdown links.
"""
if "links" in entry and "website" in entry["links"]:
for i in range(len(entry["links"]["website"])):
website = entry["links"]["website"][i]
if re.search(CL_REGEX, website):
match = re.search(CL_REGEX, website)
if match.group(1) == match.group(2):
website = match.group(2)
entry["links"]["website"][i] = website
if "links" in entry and "subreddit" in entry["links"]:
for i in range(len(entry["links"]["subreddit"])):
subreddit = entry["links"]["subreddit"][i]
if re.search(CL_REGEX, subreddit):
match = re.search(CL_REGEX, subreddit)
if match.group(1) == match.group(2):
subreddit = match.group(2)
entry["links"]["subreddit"][i] = subreddit
return entry
def remove_extras(entry: dict):
"""
Removing unnecessary extra characters and converts select characters.
"""
if "subreddit" in entry and entry["subreddit"]:
entry["subreddit"] = re.sub(r'[.,]+$', r'', entry["subreddit"])
for key in entry:
if not entry[key] or not isinstance(entry[key], str):
continue
# Leading and trailing spaces
entry[key] = entry[key].strip()
# Double characters
entry[key] = re.sub(r' {2,}(?!\n)', r' ', entry[key])
entry[key] = re.sub(r' {3,}\n', r' ', entry[key])
entry[key] = re.sub(r'\n{3,}', r'\n\n', entry[key])
entry[key] = re.sub(r'r\/{2,}', r'r\/', entry[key])
entry[key] = re.sub(r',{2,}', r',', entry[key])
# Smart quotation marks
entry[key] = re.sub(r'[\u201c\u201d]', '"', entry[key])
entry[key] = re.sub(r'[\u2018\u2019]', "'", entry[key])
# Psuedo-empty strings
if entry[key] in ["n/a", "N/A", "na", "NA", "-", "null", "none", "None"]:
entry[key] = ""
return entry
def remove_duplicate_points(entry: dict):
"""
Removes points from paths that occur twice after each other
"""
if not "path" in entry:
return entry
for key in entry['path']:
path: list = entry['path'][key]
previous: list = path[0]
for i in range(len(path)-1, -1, -1):
current: list = path[i]
if current == previous:
path.pop(i)
previous = current
return entry
def fix_r_caps(entry: dict):
"""
Fixes capitalization of /r/. (/R/place -> /r/place)
"""
if not "description" in entry or not entry['description']:
return entry
entry["description"] = re.sub(r'([^\w]|^)\/R\/', '\1/r/', entry["description"])
entry["description"] = re.sub(r'([^\w]|^)R\/', '\1r/', entry["description"])
return entry
def fix_no_protocol_urls(entry: dict):
"""
Fixes URLs with no protocol by adding "https://" protocol.
"""
if "links" in entry and "website" in entry['links']:
for i in range(len(entry["links"]["website"])):
if entry["links"]["website"][i] and not entry["links"]["website"][i].startswith("http"):
entry["links"]["website"][i] = "https://" + entry["website"]
return entry
def convert_website_to_subreddit(entry: dict):
"""
Converts the subreddit link on "website" to "subreddit" if possible.
"""
if "links" in entry and "website" in entry["links"]:
for i in range(len(entry["links"]["website"])):
if re.match(CWTS_REGEX["url"], entry["links"]["website"][i]):
new_subreddit = re.sub(CWTS_REGEX["url"], r"\1", entry["links"]["website"][i])
if not "subreddit" in entry["links"]:
entry["links"]["subreddit"] = []
if new_subreddit in entry["links"]["subreddit"]:
entry["links"]["website"][i] = ""
elif not "subreddit" in entry["links"] or len(entry["links"]["subreddit"]) == 0:
entry["links"]["subreddit"].append(new_subreddit)
entry["links"]["website"][i] = ""
elif re.match(CWTS_REGEX["subreddit"], entry["links"]["website"][i]):
new_subreddit = re.sub(CWTS_REGEX["subreddit"], r"\1", entry["links"]["website"][i])
if not "subreddit" in entry["links"]:
entry["links"]["subreddit"] = []
if new_subreddit in entry["links"]["subreddit"]:
entry["links"]["website"][i] = ""
elif not "subreddit" in entry["links"] or len(entry["links"]["subreddit"]) == 0:
entry["links"]["subreddit"].append(new_subreddit)
entry["links"]["website"][i] = ""
return entry
def convert_website_to_discord(entry: dict):
"""
Converts the Discord link on "website" to "discord" if possible.
"""
if "links" in entry and "website" in entry["links"]:
for i in range(len(entry["links"]["website"])):
if re.match(CWTD_REGEX, entry["links"]["website"][i]):
new_discord = re.match(CWTD_REGEX, entry["links"]["website"][i])[1]
if not "discord" in entry["links"]:
entry["links"]["discord"] = []
if new_discord in entry["links"]["discord"]:
entry["links"]["website"][i] = ""
elif not "discord" in entry["links"] or len(entry["links"]["discord"]) == 0:
entry["links"]["discord"].append(new_discord)
entry["links"]["website"][i] = ""
return entry
def convert_subreddit_to_website(entry: dict):
"""
Converts the links on "subreddit" to a "website" if needed. This also supports Reddit users (/u/reddit).
"""
if "links" in entry and "subreddit" in entry["links"]:
for i in range(len(entry["links"]["subreddit"])):
if re.match(CSTW_REGEX["website"], entry["links"]["subreddit"][i]):
if "website" in entry["links"] and entry["links"]["subreddit"][i] in entry["links"]["website"]:
entry["links"]["subreddit"][i] = ""
elif not "website" in entry["links"] or len(entry["website"]) == 0:
if not "website" in entry["links"]:
entry["links"]["website"] = []
entry["website"].append(entry["links"]["subreddit"][i])
entry["links"]["subreddit"][i] = ""
elif re.match(CSTW_REGEX["user"], entry["links"]["subreddit"][i]):
if not "website" in entry["links"] or len(entry["website"]) == 0:
username = re.match(CSTW_REGEX["user"], entry["links"]["subreddit"][i]).group(1)
if not "website" in entry["links"]:
entry["links"]["website"] = []
entry["website"].append("https://www.reddit.com/user/" + username)
entry["links"]["subreddit"][i] = ""
return entry
def remove_empty_and_similar(entry: dict):
"""
Removes empty items on lists, usually from the past formattings.
"""
if "links" in entry:
keys = list(entry["links"])
for key in keys:
small = list(map(lambda x: x.lower(), entry["links"][key]))
entry["links"][key] = [x for x in entry["links"][key] if x and x.lower() in small]
if len(entry["links"][key]) == 0: del entry["links"][key]
return entry
def validate(entry: dict):
"""
Validates the entry. Catch errors and tell warnings related to the entry.
Status code key:
0: All valid, no problems
1: Informational logs that may be ignored
2: Warnings that may effect user experience when interacting with the entry
3: Errors that make the entry inaccessible or broken.
"""
return_status = 0
if (not "id" in entry or (not entry['id'] and not entry['id'] == 0)):
print(f"Wait, no id here! How did this happened? {entry}")
return_status = 3
entry['id'] = '[MISSING_ID]'
if "path" in entry:
for key in entry['path']:
path = entry['path'][key]
if len(path) == 0:
print(f"Period {key} of entry {entry['id']} has no points!")
return_status = 3
elif len(path) < 3:
print(f"Period {key} of entry {entry['id']} only has {len(entry['path'])} point(s)!")
return_status = 3
else:
print(f"Entry {entry['id']} has no path at all!")
return_status = 3
for key in entry:
if key in VALIDATE_REGEX and not re.match(VALIDATE_REGEX[key], entry[key]):
if return_status < 2: return_status = 2
print(f"{key} of entry {entry['id']} is still invalid! {entry[key]}")
return return_status
def per_line_entries(entries: list):
"""
Returns a string of all the entries, with every entry in one line.
"""
out = "[\n"
for entry in entries:
if entry:
out += json.dumps(entry, ensure_ascii=False) + ",\n"
out = out[:-2] + "\n]"
return out
def format_all(entry: dict, silent=False):
"""
Format using all the available formatters.
Outputs a tuple containing the entry and the validation status code.
Status code key:
0: All valid, no problems
1: Informational logs that may be ignored
2: Warnings that may effect user experience when interacting with the entry
3: Errors that make the entry inaccessible or broken.
"""
def print_(*args, **kwargs):
if not silent:
print(*args, **kwargs)
print_("Fixing r/ capitalization...")
entry = fix_r_caps(entry)
print_("Fix formatting of subreddit...")
entry = format_subreddit(entry)
print_("Collapsing Markdown links...")
entry = collapse_links(entry)
print_("Converting website links to subreddit (if possible)...")
entry = convert_website_to_subreddit(entry)
print_("Converting website links to Discord...")
entry = convert_website_to_discord(entry)
print_("Converting subreddit links to website (if needed)...")
entry = convert_subreddit_to_website(entry)
print_("Fixing links without protocol...")
entry = fix_no_protocol_urls(entry)
print_("Removing extras...")
entry = remove_extras(entry)
print_("Removing duplicate points...")
entry = remove_duplicate_points(entry)
print_("Remove empty items...")
entry = remove_empty_and_similar(entry)
print_("Validating...")
status_code = validate(entry)
print_("Completed!")
return ( entry, status_code )
if __name__ == '__main__':
def go(path):
print(f"Formatting {path}...")
with open(path, "r+", encoding='UTF-8') as f1:
entries = json.loads(f1.read())
for i in range(len(entries)):
try:
entry_formatted, validation_status = format_all(entries[i], True)
if validation_status > 2:
print(f"Entry {entry_formatted['id']} will be removed! {json.dumps(entry_formatted)}")
entries[i] = None
else:
entries[i] = entry_formatted
except Exception:
print(f"Exception occured when formatting ID {entries[i]['id']}")
print(traceback.format_exc())
if not (i % 200):
print(f"{i} checked.")
print(f"{len(entries)} checked. Writing...")
with open(path, "w", encoding='utf-8', newline='\n') as f2:
f2.write(per_line_entries(entries))
print("Writing completed. All done.")
go("../web/atlas.json")

51
tools/merge_out.py Normal file
View file

@ -0,0 +1,51 @@
import json
from formatter import per_line_entries
out_ids = []
out_dupe_ids = []
out_edited_added_ids = []
atlas_ids = []
with open('temp_atlas.json', 'r', encoding='utf-8') as out_file:
out_json = json.loads(out_file.read())
with open('../web/atlas.json', 'r', encoding='utf-8') as atlas_file:
atlas_json = json.loads(atlas_file.read())
for entry in atlas_json:
atlas_ids.append(entry['id'])
for entry in out_json:
if (entry['id'] in out_ids):
print(f"Entry {entry['id']} has duplicates! Please resolve this conflict. This will be excluded from the merge.")
out_dupe_ids.append(entry['id'])
out_ids.append(entry['id'])
for entry in out_json:
if entry['id'] in out_dupe_ids:
continue
if 'edit' in entry and entry['edit']:
index = next((i for i, item in enumerate(atlas_json) if item["id"] == entry['id']), None)
assert index != None, "Edit failed! ID not found on Atlas."
print(f"Edited {atlas_json[index]['id']} with {entry['edit']}")
if 'edit' in entry:
out_edited_added_ids.append(entry['edit'])
del entry['edit']
atlas_json[index] = entry
else:
print(f"Added {entry['id']}.")
atlas_json.append(entry)
print('Writing...')
with open('../web/atlas.json', 'w', encoding='utf-8') as atlas_file:
atlas_file.write(per_line_entries(atlas_json))
with open('../data/read-ids.txt', 'a', encoding='utf-8') as read_ids_file:
with open('read-ids-temp.txt', 'r', encoding='utf-8') as read_ids_temp_file:
read_ids_file.writelines(read_ids_temp_file.readlines())
print('All done.')

View file

@ -0,0 +1,109 @@
#!/usr/bin/python
"""
Migrator script from old atlas format to remastered atlas format.
- center and path: single -> time-specific
- website and subreddit: single strings -> links object
- submitted_by removed
"""
import re
import json
END_IMAGE = 166
INIT_CANVAS_RANGE = (1, END_IMAGE)
EXPANSION_1_RANGE = (56, END_IMAGE)
EXPANSION_2_RANGE = (109, END_IMAGE)
COMMATIZATION = re.compile(r'[,;& ]+(?:and)?[,;& ]*?')
FS_REGEX = re.compile(r'(?:(?:(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com)?\/)?[rR]\/([A-Za-z0-9][A-Za-z0-9_]{2,20})(?:\/[^" ]*)*')
def migrate_atlas_format(entry: dict):
new_entry = {
"id": "",
"name": "",
"description": "",
"links": {},
"path": {},
"center": {}
}
center = entry['center']
path = entry['path']
if isinstance(center, list):
# Use the center to figure out which canvas expansion the entry is in.
if center[1] > 1000:
time_range = EXPANSION_2_RANGE
elif center[0] > 1000:
time_range = EXPANSION_1_RANGE
else:
time_range = INIT_CANVAS_RANGE
time_key = '%d-%d, T' % time_range
new_entry = {
**new_entry,
"center": {
time_key: center
},
"path": {
time_key: path
}
}
del entry['center']
del entry['path']
if "website" in entry:
if isinstance(entry["website"], str) and entry["website"]:
new_entry['links']['website'] = [entry['website']]
del entry['website']
if "subreddit" in entry:
if isinstance(entry["subreddit"], str) and entry["subreddit"]:
new_entry['links']['subreddit'] = list(map(lambda x: FS_REGEX.sub(r"\1", x), COMMATIZATION.split(entry['subreddit'])))
del entry['subreddit']
toreturn = {
**new_entry,
**entry
}
return toreturn
def per_line_entries(entries: list):
"""
Returns a string of all the entries, with every entry in one line.
"""
out = "[\n"
for entry in entries:
if entry:
out += json.dumps(entry, ensure_ascii=False) + ",\n"
out = out[:-2] + "\n]"
return out
if __name__ == '__main__':
def go(path):
print(f"Formatting {path}...")
with open(path, "r+", encoding='UTF-8') as f1:
entries = json.loads(f1.read())
for i in range(len(entries)):
entry_formatted = migrate_atlas_format(entries[i])
entries[i] = entry_formatted
if not (i % 1000):
print(f"{i} checked.")
print(f"{len(entries)} checked. Writing...")
with open(path, "w", encoding='utf-8', newline='\n') as f2:
f2.write(per_line_entries(entries))
print("Writing completed. All done.")
go("../web/atlas.json")

View file

@ -1,14 +1,41 @@
"""
Auth setup
1. Head to https://www.reddit.com/prefs/apps
2. Click "create another app"
3. Give it a name and description
4. Select "script"
5. Redirect to http://localhost:8080
6. Create file "credentials" with the format below
[ID] <- Under "personal use script"
[Secret]
[Username] <- Must be a mod, don't do this if you │
[Password] <- don't know what you are doing. │
7. Run Script
Running Script
1. Input the next ID to use
2. Manually resolve errors in manual_atlas.json
3 a. Use merge_out.py, or...
b. a. Copy temp_atlas.json entries into web/_js/atlas.js (mind the edits!)
b. Copy read-ids-temp.txt IDs into data/read-ids.txt
5. Create a pull request
"""
import praw
import json
import time
import re
import os
import traceback
from formatter import format_all
from migrate_atlas_format import migrate_atlas_format
outfile = open('temp_atlas.json', 'w', encoding='utf-8')
editidsfile = open('read-ids-temp.txt', 'w')
failfile = open('manual_atlas.json', 'w', encoding='utf-8')
OUT_FILE = open('temp_atlas.json', 'w', encoding='utf-8')
READ_IDS_FILE = open('read-ids-temp.txt', 'w')
FAIL_FILE = open('manual_atlas.txt', 'w', encoding='utf-8')
OUT_FILE_LINES = ['[\n', ']\n']
with open('credentials', 'r') as file:
credentials = file.readlines()
@ -32,7 +59,7 @@
existing_ids = []
with open('../data/edit-ids.txt', 'r') as edit_ids_file:
with open('../data/read-ids.txt', 'r') as edit_ids_file:
for id in [x.strip() for x in edit_ids_file.readlines()]:
existing_ids.append(id)
@ -48,31 +75,7 @@ def set_flair(submission, flair):
successcount = 0
totalcount = 0
outfile.write("[\n")
for submission in reddit.subreddit('placeAtlas2').new(limit=2000):
"""
Auth setup
1. Head to https://www.reddit.com/prefs/apps
2. Click "create another app"
3. Give it a name and description
4. Select "script"
5. Redirect to http://localhost:8080
6. Create file "credentials" with the format below.
[ID] <- Under "personal use script"
[Secret]
[Username] <- Must be a mod, don't do this if you │
[Password] <- don't know what you are doing. │
7. Run Script
Running Script
1. Input the next ID to use
2. Manually resolve errors in manual_atlas.json
3. Copy temp_atlas.json entries into web/_js/atlas.js
4. Pull Request
"""
total_all_flairs += 1
if (submission.id in existing_ids):
@ -83,8 +86,8 @@ def set_flair(submission, flair):
break
else:
continue
if (submission.link_flair_text == "New Entry"):
if submission.link_flair_text == "New Entry" or submission.link_flair_text == "Edit Entry":
try:
@ -92,7 +95,7 @@ def set_flair(submission, flair):
rawtext = text
text = text.replace('\u200c', '')
text = re.compile(r".*(\{.+\}).*", re.DOTALL).search(text).group(1)
text = re.compile(r"(\{.+\})", re.DOTALL).search(text).group(0)
# Test if it needs to escape the escape character. Usually happens on fancy mode.
try: json.loads(text)
except json.JSONDecodeError: text = re.sub(r"\\(.)", r"\1", text)
@ -101,11 +104,18 @@ def set_flair(submission, flair):
if submission_json:
submission_json_dummy = {"id": submission.id, "submitted_by": ""}
try:
submission_json_dummy["submitted_by"] = submission.author.name
except AttributeError:
submission_json_dummy["submitted_by"] = "unknown"
if submission.link_flair_text == "Edit Entry":
assert submission_json["id"] != 0, "Edit invalid because ID is tampered, it must not be 0!"
submission_json_dummy = {"id": submission_json["id"], "edit": submission.id}
else:
assert submission_json["id"] == 0, "Edit invalid because ID is tampered, it must be 0!"
submission_json_dummy = {"id": submission.id}
for key in submission_json:
if not key in submission_json_dummy:
submission_json_dummy[key] = submission_json[key];
@ -113,15 +123,20 @@ def set_flair(submission, flair):
assert validation_status < 3, \
"Submission invalid after validation. This may be caused by not enough points on the path."
outfile.write(json.dumps(submission_json, ensure_ascii=False) + ",\n")
editidsfile.write(submission.id + '\n')
submission_json = migrate_atlas_format(submission_json)
add_comma_line = len(OUT_FILE_LINES) - 2
if len(OUT_FILE_LINES[add_comma_line]) > 2:
OUT_FILE_LINES[add_comma_line] = OUT_FILE_LINES[add_comma_line].replace('\n', ',\n')
OUT_FILE_LINES.insert(len(OUT_FILE_LINES) - 1, json.dumps(submission_json, ensure_ascii=False) + '\n')
READ_IDS_FILE.write(submission.id + '\n')
successcount += 1
set_flair(submission, "Processed Entry")
except Exception as e:
failfile.write(
"\n\n" + "="*40 + "\n\n" +
FAIL_FILE.write(
"\n\n" + "="*40 + "\n\nSubmission ID: " +
submission.id + "\n\n" +
traceback.format_exc() + "\n\n" +
"==== RAW ====" + "\n\n" +
@ -132,13 +147,9 @@ def set_flair(submission, flair):
failcount += 1
set_flair(submission, "Rejected Entry")
print("Wrote "+submission.id+", submitted "+str(round(time.time()-submission.created_utc))+" seconds ago")
print("Wrote " + submission.id + ", submitted " + str(round(time.time()-submission.created_utc)) + " seconds ago")
totalcount += 1
# Remove last trailing comma
outfile.seek(outfile.tell()-3, os.SEEK_SET)
outfile.truncate()
OUT_FILE.writelines(OUT_FILE_LINES)
outfile.write("\n]")
print(f"\n\nTotal all flairs:{total_all_flairs}\nSuccess: {successcount}/{totalcount}\nFail: {failcount}/{totalcount}\nPlease check manual_atlas.txt for failed entries to manually resolve.")
print(f"\n\nTotal all flairs: {total_all_flairs}\nSuccess: {successcount}/{totalcount}\nFail: {failcount}/{totalcount}\nPlease check manual_atlas.txt for failed entries to manually resolve.")

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

0
tools/combine.sh → tools/unused/combine.sh Executable file → Normal file
View file

View file

@ -1,13 +1,13 @@
<!--
========================================================================
The 2022 /r/place Atlas
The 2022 r/place Atlas
An Atlas of Reddit's 2022 /r/place, with information to each
An atlas of Reddit's 2022 r/place, with information to each
artwork of the canvas provided by the community.
Copyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2022 r/placeAtlas2 contributors
Copyright (c) 2022 Place Atlas contributors
Licensed under the GNU Affero General Public License Version 3
https://place-atlas.stefanocoding.me/license.txt

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -4,3 +4,8 @@
/_img/place/*.png
cache-control: public, max-age=604800
/_img/canvas/*/*.png
cache-control: public, max-age=604800
/_img/canvas/*.png
cache-control: public, max-age=604800

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Some files were not shown because too many files have changed in this diff Show more