├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.txt ├── README.md ├── composer.json ├── composer.lock ├── cron-pending.sh ├── docker-compose.yml ├── docker-php-composer.sh ├── entrypoint.sh ├── examples └── putific │ ├── .gitignore │ ├── cover.png │ ├── curl-putific.sh │ ├── node-putific.mjs │ └── python-putific.py ├── importers ├── .gitignore ├── entrant-creator.js ├── ifcomp │ ├── .gitignore │ ├── README.md │ ├── compute-ifarchive-links.mjs │ ├── extract-microdata.mjs │ ├── merge-tuids.mjs │ ├── process-cover-art.mjs │ ├── remove-ballot-links.mjs │ ├── settings.mjs.template │ ├── submit-external-links.mjs │ ├── submit-games.mjs │ └── tag-games.mjs ├── itch │ ├── .gitignore │ ├── README.md │ ├── division-detector.mjs │ ├── division-tagger.mjs │ ├── fetch-entries-json.mjs │ ├── merge-tuids.mjs │ ├── process-cover-art.mjs │ ├── settings.mjs.template │ └── submit-games.mjs ├── package-lock.json └── package.json ├── local-credentials.php.template ├── prepare_dev_environment.sh ├── prepare_full_dev_environment.sh ├── query-docker.sh ├── sql ├── create-admin.sql ├── create-db.sql ├── create-test-user.sql ├── ifdb-to-ifarchive-scrubber.sql ├── incoming-schema-changes.sql ├── patch-full-schema.sql └── unscrub-ifarchive.sql ├── test ├── IFDB.side ├── README.md └── unit │ └── test.php └── www ├── .htaccess ├── .well-known └── .htaccess ├── Akismet.class.php ├── adminops ├── akismet.php ├── alllists ├── allnew ├── allnew-rss ├── allpolls ├── allreviews ├── allupdates ├── api ├── api.php ├── gametags ├── index ├── putific ├── search ├── taggame └── viewgame ├── async-recommendations ├── capreq ├── captcha.php ├── captcha ├── .htaccess ├── audio │ ├── 0.wav │ ├── 1.wav │ ├── 2.wav │ ├── 3.wav │ ├── 4.wav │ ├── 5.wav │ ├── 6.wav │ ├── 7.wav │ ├── 8.wav │ ├── 9.wav │ ├── a.wav │ ├── b.wav │ ├── c.wav │ ├── d.wav │ ├── e.wav │ ├── f.wav │ ├── g.wav │ ├── h.wav │ ├── i.wav │ ├── j.wav │ ├── k.wav │ ├── l.wav │ ├── m.wav │ ├── n.wav │ ├── o.wav │ ├── p.wav │ ├── q.wav │ ├── r.wav │ ├── s.wav │ ├── t.wav │ ├── u.wav │ ├── v.wav │ ├── w.wav │ ├── x.wav │ ├── y.wav │ └── z.wav └── fonts │ ├── A1.TTF │ ├── A2.TTF │ ├── A3.TTF │ ├── A4.TTF │ ├── A5.TTF │ ├── A6.TTF │ ├── A7.TTF │ └── A8.TTF ├── changepsw ├── code-of-conduct ├── combobox.php ├── comment.php ├── commentlog ├── commentutil.php ├── components ├── check-inbox.php ├── competitions.php ├── database-stats.php ├── games.php ├── ifdb-recommends.php ├── poll-sampler.php ├── recommended-lists.php ├── reviews.php └── top-reviewers.php ├── contact ├── copyright ├── coverart ├── cron-pending.php ├── crossrec ├── csp-nonce.php ├── css ├── dbconnect.php ├── delcomp ├── delgame ├── delpoll ├── editImageCopyright ├── editcomp ├── editgame ├── editgame-util.php ├── editlist ├── editnews ├── editprofile ├── error403 ├── error404 ├── error503 ├── favicon.ico ├── favicons ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ai ├── favicon.svg └── site.webmanifest ├── fileformat ├── filetypes.php ├── game-rss.php ├── gameinfo.php ├── gamelink ├── gamesearchpopup.php ├── gametags ├── google7f64b84ffa585e59.html ├── gridform.js ├── help-add-game ├── help-author ├── help-bafs ├── help-crossrec ├── help-css ├── help-discussions ├── help-edit-game ├── help-ff ├── help-forgiveness ├── help-formatting ├── help-gameprofilelink ├── help-ifid ├── help-image-quota ├── help-license-type ├── help-link-policy ├── help-links ├── help-pending-link ├── help-polls ├── help-reclist ├── help-review ├── help-review-votes ├── help-spamcloaking ├── help-stars ├── help-tags ├── help-top-rev ├── help-tuid ├── help-zip-primary ├── home ├── ifarchive-commit ├── ifarchive-pending ├── ifarchive-tuid-report ├── ifarchive-upload ├── ifdb.css ├── ifdbutil.js ├── iframe ├── image-util.php ├── imageUpload ├── images.php ├── imageuploadhandler.php ├── img ├── blank.gif ├── blorbicon.gif ├── ckbox0.gif ├── ckbox1.gif ├── ckbox2.gif ├── close.svg ├── comboarrow.gif ├── dark-images │ ├── ifdb-smalltopbar-midnight.jpg │ ├── ifdb-topbar-midnight.jpg │ ├── movedown-midnight.gif │ ├── moveup-midnight.gif │ ├── star-half-checked.svg │ └── star-unchecked.svg ├── delrow.gif ├── dlarrow.gif ├── docicon.gif ├── erricon.gif ├── hintfileicon.gif ├── htmlicon.gif ├── ifdb-smalltopbar.jpg ├── ifdb-topbar.jpg ├── iftf-logo.svg ├── infoicon.gif ├── list-arrow.gif ├── manualicon.gif ├── mapicon.gif ├── menu.svg ├── menuarrow.gif ├── moveblank.gif ├── movedown.gif ├── moveup.gif ├── newcomment.png ├── porticon.gif ├── readmeicon.gif ├── rss.svg ├── search_small.svg ├── setupicon.gif ├── srcfileicon.gif ├── star-checked.svg ├── star-half-checked.svg ├── star-hovered.svg ├── star-unchecked.svg ├── tadsicon.gif ├── tbclose.gif ├── topbarcurarrow.gif ├── ttf_icon.gif ├── vote-checkmark.gif ├── vsnarrow.gif ├── walkthruicon.gif ├── winicon.gif ├── zicon.gif └── zipmain.gif ├── listcomment ├── lists.php ├── login ├── login-check.php ├── login-persist.php ├── logout ├── lostact ├── lostpass ├── mergegame ├── mirrorUrl.php ├── needjs ├── newitems.php ├── news ├── news.php ├── newslog ├── newuser ├── opsys ├── pagetpl.php ├── passwords.js ├── pending-report ├── personal ├── playlist ├── plugins └── ifdb-opensearchdesc.xml ├── poll ├── pollcomment ├── privacy ├── profilelink.php ├── profilepic ├── putific ├── random ├── recaptchalib.php ├── refresh-embargoed-reviews ├── refresh-user-scores ├── resetpsw ├── review ├── reviewcomment ├── reviewcommentlog ├── reviewflag ├── reviews.php ├── reviewunflag ├── reviewvote ├── rollbackGameVersion ├── rss.php ├── search ├── searchutil.php ├── session-start.php ├── setplayed ├── setrating ├── settime ├── setunwishlist ├── setwelcome ├── setwishlist ├── showimage ├── showtags ├── showuser ├── similargames ├── speculation-rules ├── starctl.php ├── stylepics ├── styles ├── taggame ├── taggamedelete ├── tags.php ├── tempimage ├── tips ├── tos ├── useractivation.php ├── usercomment ├── userconfirm ├── userfile ├── userfilter ├── util.php ├── viewcomp ├── viewgame ├── viewgame.js ├── viewlist ├── whoselist └── whylicense /.gitignore: -------------------------------------------------------------------------------- 1 | www/local-credentials.php 2 | **/ifdb-archive.sql 3 | **/ifdb-archive.zip 4 | **/ifdb-archive-*.zip 5 | initdb 6 | www/vendor 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * When creating pull requests, target the `main` branch. Please rebase your PRs and keep each commit small and focused. It's OK to have a big PR, as long as each commit in the PR is small, but smaller PRs are better, if feasible. 4 | * It's OK to file a PR that only partially works, just to begin discussion about the code. 5 | * We have a few Selenium tests in the `tests` directory. Please don't break the tests! 6 | * This is PHP, so we need to be particularly careful about [XSS](https://owasp.org/www-community/attacks/xss/) bugs. 7 | * [SQL injections](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) are also a major risk. 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM composer:2 as builder 2 | WORKDIR /app 3 | COPY composer.json composer.lock ./ 4 | RUN composer install --no-dev --optimize-autoloader --no-interaction 5 | 6 | FROM php:7-apache as web 7 | 8 | RUN docker-php-ext-install mysqli 9 | RUN apt-get update -y && apt-get install -y zlib1g-dev libpng-dev libjpeg-dev 10 | RUN docker-php-ext-configure gd --with-jpeg 11 | RUN docker-php-ext-install gd 12 | RUN a2enmod rewrite headers 13 | COPY --from=builder /app/vendor /opt/vendor 14 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 15 | 16 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh 17 | RUN chmod +x /usr/local/bin/entrypoint.sh 18 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 19 | 20 | CMD ["apache2-foreground"] 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | IFDB Server 2 | Copyright 2007, 2020 Michael J Roberts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "glenscott/url-normalizer": "^1.4" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "5d2ff3627508b7f98794b355aedc2d22", 8 | "packages": [ 9 | { 10 | "name": "glenscott/url-normalizer", 11 | "version": "1.4.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/glenscott/url-normalizer.git", 15 | "reference": "b8e79d3360a1bd7182398c9956bd74d219ad1b3c" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/glenscott/url-normalizer/zipball/b8e79d3360a1bd7182398c9956bd74d219ad1b3c", 20 | "reference": "b8e79d3360a1bd7182398c9956bd74d219ad1b3c", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-mbstring": "*", 25 | "php": ">=5.3.0" 26 | }, 27 | "type": "library", 28 | "autoload": { 29 | "psr-4": { 30 | "URL\\": "src/URL" 31 | } 32 | }, 33 | "notification-url": "https://packagist.org/downloads/", 34 | "license": [ 35 | "MIT" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "Glen Scott", 40 | "email": "glen@glenscott.co.uk" 41 | } 42 | ], 43 | "description": "Syntax based normalization of URL's", 44 | "support": { 45 | "issues": "https://github.com/glenscott/url-normalizer/issues", 46 | "source": "https://github.com/glenscott/url-normalizer/tree/master" 47 | }, 48 | "time": "2015-06-11T16:06:02+00:00" 49 | } 50 | ], 51 | "packages-dev": [], 52 | "aliases": [], 53 | "minimum-stability": "stable", 54 | "stability-flags": {}, 55 | "prefer-stable": false, 56 | "prefer-lowest": false, 57 | "platform": {}, 58 | "platform-dev": {}, 59 | "plugin-api-version": "2.6.0" 60 | } 61 | -------------------------------------------------------------------------------- /cron-pending.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker exec -it ifdb-web-1 php /var/www/html/cron-pending.php $1 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: mariadb:10 4 | command: mysqld --sql_mode="" --ft_stopword_file="" --ft_min_word_len=1 5 | volumes: 6 | - ./initdb:/docker-entrypoint-initdb.d 7 | environment: 8 | MYSQL_ROOT_PASSWORD: secret 9 | phpmyadmin: 10 | image: phpmyadmin/phpmyadmin 11 | container_name: pma 12 | links: 13 | - db 14 | environment: 15 | PMA_HOST: db 16 | PMA_USER: root 17 | PMA_PASSWORD: secret 18 | ports: 19 | - 8081:80 20 | web: 21 | build: . 22 | ports: 23 | - 8080:80 24 | links: 25 | - db 26 | volumes: 27 | - ./www:/var/www/html 28 | -------------------------------------------------------------------------------- /docker-php-composer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | docker run --rm -v "$(pwd):/app" builder composer "$@" 3 | rm -rf vendor 4 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | APP_ROOT=/var/www/html 4 | VENDOR_IMAGE_SRC=/opt/vendor 5 | VENDOR_LINK_TARGET=${APP_ROOT}/vendor 6 | 7 | # Check if the target vendor path exists AND is not already the symlink we want. 8 | # This prevents errors if the container restarts 9 | if [ ! -L "${VENDOR_LINK_TARGET}" ] || [ "$(readlink -f ${VENDOR_LINK_TARGET})" != "${VENDOR_IMAGE_SRC}" ]; then 10 | 11 | if [ -e "${VENDOR_LINK_TARGET}" ]; then 12 | echo "Warning: Removing existing file/directory at ${VENDOR_LINK_TARGET} to create vendor symlink." 13 | rm -rf "${VENDOR_LINK_TARGET}" 14 | fi 15 | 16 | echo "Creating symlink: ${VENDOR_LINK_TARGET} -> ${VENDOR_IMAGE_SRC}" 17 | ln -s "${VENDOR_IMAGE_SRC}" "${VENDOR_LINK_TARGET}" 18 | else 19 | echo "Vendor symlink already exists and is correct." 20 | fi 21 | 22 | # Execute the original command passed to the container (e.g., apache2-foreground) 23 | exec "$@" 24 | -------------------------------------------------------------------------------- /examples/putific/.gitignore: -------------------------------------------------------------------------------- 1 | putific-venv 2 | -------------------------------------------------------------------------------- /examples/putific/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/examples/putific/cover.png -------------------------------------------------------------------------------- /examples/putific/curl-putific.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # if you want to test on the local Docker dev environment, change the url to http://localhost:8080/putific 4 | # but beware, per the README, the local dev environment doesn't allow you to upload images, so you won't see the cover art 5 | 6 | USERNAME=ifdbadmin@ifdb.org 7 | PASSWORD=secret 8 | URL=https://ifdb.org/putific 9 | #URL=http://localhost:8080/putific 10 | 11 | TMP_DIR=`mktemp -d` 12 | IFICTION="$TMP_DIR"/ifiction.xml 13 | TIMESTAMP=$(date +%s) 14 | cat < $IFICTION 15 | 16 | 17 | 18 | 19 | Test $TIMESTAMP 20 | Test Author 21 | 22 | 23 | 24 | EOT 25 | 26 | LINKS="$TMP_DIR"/links.xml 27 | cat < $LINKS 28 | 29 | 30 | 31 | http://www.ifarchive.org/if-archive/games/palm/ACgames.zip 32 | ACgames.zip 33 | converted to PalmOS .prc file 34 | 35 | executable 36 | PalmOS.PalmOS-LoRes 37 | zip 38 | PHOTOPIA.PRC 39 | 40 | 41 | EOT 42 | 43 | curl -v \ 44 | -F username="$USERNAME" \ 45 | -F password="$PASSWORD" \ 46 | -F ifiction=@$IFICTION \ 47 | -F links=@$LINKS \ 48 | -F coverart=@cover.png \ 49 | -F requireIFID=no \ 50 | "$URL" 51 | -------------------------------------------------------------------------------- /examples/putific/node-putific.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // This script works in Node 18+ 3 | 4 | // update the username and password to your email address and password 5 | // if you want to run it on the local Docker dev environment, change the url to http://localhost:8080/putific 6 | // but beware, per the README, the local dev environment doesn't allow you to upload images, so you won't see the cover art 7 | 8 | // or, you can even copy and paste this script into Chrome Dev Tools 9 | // Just go to the IFDB home page (either ifdb.org or localhost:8080, both work) and paste the script in 10 | 11 | const body = new FormData(); 12 | body.append('username', 'ifdbadmin@ifdb.org'); 13 | body.append('password', 'secret'); 14 | let url = 'https://ifdb.org/putific'; 15 | // url = 'http://localhost:8080/putific'; 16 | if (typeof window !== 'undefined' && /\b(ifdb.org|localhost)$/.test(location.hostname)) url = '/putific'; 17 | const xml = ` 18 | 19 | 20 | 21 | Test ${Date.now()} 22 | Test Author 23 | 24 | 25 | 26 | `; 27 | body.append('ifiction', new Blob([xml], { type: 'text/xml' })); 28 | const links = ` 29 | 30 | 31 | http://www.ifarchive.org/if-archive/games/palm/ACgames.zip 32 | ACgames.zip 33 | converted to PalmOS .prc file 34 | 35 | executable 36 | PalmOS.PalmOS-LoRes 37 | zip 38 | PHOTOPIA.PRC 39 | 40 | 41 | `; 42 | body.append('links', new Blob([links], { type: 'text/xml' })); 43 | body.append('requireIFID', 'no'); 44 | 45 | if (typeof process !== 'undefined') { 46 | // we're in node 47 | const { readFile } = await import('fs/promises'); 48 | const image = await readFile('cover.png'); 49 | body.append('coverart', new Blob([image], { type: 'image/png' })); 50 | } 51 | 52 | const response = await fetch(url, { 53 | method: 'post', 54 | body, 55 | }); 56 | const { status, statusText } = response; 57 | const text = await response.text(); 58 | console.log(JSON.stringify({ status, statusText, text }, null, 2)); 59 | -------------------------------------------------------------------------------- /examples/putific/python-putific.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This library requires the "requests" library 4 | # https://pypi.org/project/requests/ 5 | # because python doesn't have a built-in way of handling multipart/form-data 6 | 7 | # run it like this: 8 | # python3 -m venv putific-venv 9 | # source putific-venv/bin/activate 10 | # pip3 install requests 11 | # python3 python-putific.py 12 | 13 | import requests 14 | import time 15 | 16 | # to test, consider setting up the ifdb dev environment 17 | # then use localhost:8080 instead 18 | # but beware, per the README, the local dev environment doesn't allow you to upload images, so you won't see the cover art 19 | 20 | url = 'https://ifdb.org/putific' 21 | # url = 'http://localhost:8080/putific' 22 | 23 | username = 'ifdbadmin@ifdb.org' 24 | password = 'secret' 25 | 26 | xml = """ 27 | 28 | 29 | 30 | Test %s 31 | Test Author 32 | 33 | 34 | 35 | """ % time.time() 36 | 37 | links = """ 38 | 39 | 40 | http://www.ifarchive.org/if-archive/games/palm/ACgames.zip 41 | ACgames.zip 42 | converted to PalmOS .prc file 43 | 44 | executable 45 | PalmOS.PalmOS-LoRes 46 | zip 47 | PHOTOPIA.PRC 48 | 49 | 50 | """ 51 | 52 | body = ( 53 | ('username', (None, username)), 54 | ('password', (None, password)), 55 | ('ifiction', ('ifiction.xml', xml, 'text/xml')), 56 | ('links', ('links.xml', links, 'text/xml')), 57 | ('coverart', ('cover.png', open('cover.png', 'rb'), 'image/png')), 58 | ('requireIFID', (None, 'no')), 59 | ) 60 | 61 | #print(requests.Request('POST', url, files=body).prepare().body.decode('utf8')) 62 | 63 | r = requests.post(url, files=body) 64 | print(r.text) 65 | -------------------------------------------------------------------------------- /importers/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /importers/entrant-creator.js: -------------------------------------------------------------------------------- 1 | async function entrantCreator() { 2 | // run this in JS Console when creating the competition 3 | 4 | const tag = prompt("Search?"); 5 | 6 | const url = '/search?' + new URLSearchParams({ 7 | searchbar: tag, 8 | xml: 1, 9 | pg: 'all', 10 | sortby: 'ttl', 11 | }).toString(); 12 | 13 | const xml = await fetch(url).then(r => r.text()); 14 | 15 | const results = [...new DOMParser().parseFromString(xml, "application/xml").querySelectorAll('game')].map(game => ({ 16 | id: game.querySelector('tuid').textContent, 17 | title: game.querySelector('title').textContent, 18 | author: game.querySelector('author').textContent, 19 | })) 20 | 21 | const divId = prompt("Div ID?"); 22 | 23 | for (const { id, title, author } of results) { 24 | const i = window[window[`divModel${divId}`].vals].length; 25 | gfInsRow(`divModel${divId}`, i); 26 | document.getElementById(`gameplace${divId}_${i}`).value = "Entrant"; 27 | gameSearchPopupSetID(id, title, author); 28 | } 29 | } -------------------------------------------------------------------------------- /importers/ifcomp/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cover-art 3 | cover-art.json 4 | microdata.json 5 | microdata-*.json 6 | external-links.json 7 | *.zip 8 | settings.mjs -------------------------------------------------------------------------------- /importers/ifcomp/extract-microdata.mjs: -------------------------------------------------------------------------------- 1 | import {writeFile} from 'fs/promises'; 2 | import microdata from 'microdata-node'; 3 | 4 | const response = await fetch('https://ifcomp.org/ballot/?alphabetize=1'); 5 | const text = await response.text(); 6 | 7 | const {items} = microdata.toJson(text); 8 | const games = items.map(item => { 9 | const {properties} = item; 10 | const output = {}; 11 | const simpleFields = ['name', 'alternateName', 'description', 'gamePlatform', 'genre', 'size', 'interactivityType', 'downloadUrl', 'url']; 12 | for (const simpleField of simpleFields) { 13 | output[simpleField] = properties[simpleField]?.[0]; 14 | } 15 | output.description = output.description 16 | .replace(/\n +\n/g, '\n') 17 | .replace(/\n\n\n\n/g, "\n\n") 18 | .replace(/\n\n\n +Content warning/, "\n\nContent warning") 19 | .trim(); 20 | output.authors = properties.author?.map(item => item.properties.name[0]); 21 | const image = properties.image?.[0]; 22 | output.fullCoverArtUrl = image?.properties?.contentUrl?.[0]; 23 | output.thumbnailArtUrl = image?.properties?.thumbnailUrl?.[0]; 24 | return output; 25 | }) 26 | await writeFile('microdata.json', JSON.stringify(games, null, 2), 'utf8'); 27 | -------------------------------------------------------------------------------- /importers/ifcomp/merge-tuids.mjs: -------------------------------------------------------------------------------- 1 | import {url} from './settings.mjs'; 2 | import {readFile, writeFile} from 'fs/promises'; 3 | import {XMLParser} from 'fast-xml-parser'; 4 | import {runTasks} from 'concurrency-limit-runner'; 5 | 6 | const microdata = JSON.parse(await readFile('microdata.json', 'utf8')); 7 | const year = new Date().getFullYear(); 8 | 9 | const tasks = []; 10 | for (const game of microdata) { 11 | tasks.push(async () => { 12 | //if (game.tuid) continue; 13 | const queryString = `${game.name} added:1d-`; 14 | const searchUrl = `${url}/search?xml&game&searchfor=${escape(queryString)}`; 15 | console.log(searchUrl); 16 | let response; 17 | try { 18 | response = await fetch(searchUrl); 19 | } catch (e) { } 20 | if (!response?.ok) { 21 | throw new Error(`Failed (${response?.status}) loading ${searchUrl}`); 22 | } 23 | const xml = new XMLParser().parse(await response.text()); 24 | let games = xml.searchReply.games.game; 25 | if (!Array.isArray(games)) { 26 | // when there's only one result, the parser inlines the array 27 | games = [games]; 28 | } 29 | const matches = games.filter(result => result.title === game.name); 30 | if (!matches.length) { 31 | console.log(`Couldn't find ${game.name}: ${JSON.stringify(xml)}`); 32 | } else if (matches.length > 1) { 33 | console.log(`Too many matches for ${game.name}: ${JSON.stringify(xml)}`); 34 | } else { 35 | game.tuid = matches[0].tuid; 36 | } 37 | }) 38 | } 39 | 40 | for await (const result of runTasks(10, tasks.values())) { } 41 | 42 | await writeFile('microdata-tuids.json', JSON.stringify(microdata, null, 2), 'utf8'); 43 | -------------------------------------------------------------------------------- /importers/ifcomp/process-cover-art.mjs: -------------------------------------------------------------------------------- 1 | import {readFile, writeFile, mkdir} from 'fs/promises'; 2 | import sharp from 'sharp'; 3 | const games = JSON.parse(await readFile('microdata.json', 'utf8')); 4 | const urls = games.filter(game => game.thumbnailArtUrl).map(game => game.thumbnailArtUrl); 5 | try { 6 | await mkdir('cover-art'); 7 | } catch (e) { 8 | if (e.code !== 'EEXIST') throw e; 9 | } 10 | const extensions = { 11 | 'image/jpeg': 'jpg', 12 | 'image/png': 'png', 13 | } 14 | const coverFiles = {}; 15 | 16 | const maxImageSize = 262144; 17 | 18 | async function resizeJpeg(inputBuffer) { 19 | const originalImage = sharp(inputBuffer); 20 | let outputBuffer = inputBuffer; 21 | let quality = 100; 22 | while (outputBuffer.length > maxImageSize) { 23 | quality -= 20; 24 | console.log({quality}); 25 | outputBuffer = await originalImage.jpeg({quality}).toBuffer(); 26 | } 27 | return outputBuffer; 28 | } 29 | 30 | await Promise.all(urls.map(async (url) => { 31 | const [, entryId] = /\/([^\/]+?)\/cover$/.exec(url); 32 | if (!entryId) throw new Error("Couldn't parse entryId from url: " + url); 33 | const response = await fetch(url); 34 | if (!response.ok) { 35 | throw new Error(`Error ${response.status} ${response.statusText} downloading ${url}: ${await response.text()}`); 36 | } 37 | const contentType = response.headers.get('content-type'); 38 | let extension = extensions[contentType]; 39 | if (!extension) { 40 | throw new Error(`Couldn't handle ${contentType} image for ${url}`); 41 | } 42 | let fileName = `cover-art/${entryId}.${extension}`; 43 | let image = Buffer.from(await response.arrayBuffer()); 44 | if (image.length > 250000) { 45 | const originalLength = image.length; 46 | if (extension === 'jpg') { 47 | image = await resizeJpeg(image); 48 | } else { 49 | extension = 'jpg'; 50 | fileName = `cover-art/${entryId}.${extension}`; 51 | image = await sharp(image).jpeg({quality: 100}).toBuffer(); 52 | image = await resizeJpeg(image); 53 | } 54 | console.log(`resizing ${fileName} from ${originalLength} to ${image.length}`); 55 | } 56 | await writeFile(fileName, image); 57 | coverFiles[entryId] = fileName; 58 | console.log(fileName); 59 | })); 60 | await writeFile("cover-art.json", JSON.stringify(coverFiles, null, 2), 'utf8'); -------------------------------------------------------------------------------- /importers/ifcomp/remove-ballot-links.mjs: -------------------------------------------------------------------------------- 1 | import { username, password, url, compYear } from './settings.mjs'; 2 | import { XMLParser } from 'fast-xml-parser'; 3 | import { runTasks } from 'concurrency-limit-runner'; 4 | 5 | const xml = new XMLParser().parse(await fetch(`${url}/search?pg=all&xml&searchbar=tag:IFComp ${compYear}`).then(r => r.text())); 6 | 7 | const games = xml.searchReply.games.game; 8 | 9 | const tasks = games.map(({tuid}) => async () => { 10 | console.log('start', tuid); 11 | const xml = new XMLParser().parse(await fetch(`${url}/viewgame?id=${tuid}&ifiction`).then(r=>r.text())); 12 | let existingLinks = xml.ifindex.story.ifdb.downloads?.links?.link ?? []; 13 | if (!Array.isArray(existingLinks)) existingLinks = [existingLinks]; 14 | const filtered = existingLinks.filter(l => !/https:\/\/ifcomp.org\/ballot/.test(l.url)); 15 | if (existingLinks.length === filtered.length) { 16 | console.log(`${url}/viewgame?id=${tuid}`, 'NO BALLOT LINK'); 17 | return; 18 | } 19 | const body = new FormData(); 20 | body.append('username', username); 21 | body.append('password', password); 22 | body.append('lastversion', xml.ifindex.story.ifdb.pageversion); 23 | const ifiction = ` 24 | 25 | 26 | ${tuid} 27 | 28 | 29 | ` 30 | body.append('ifiction', new Blob([ifiction], { type: 'text/xml' })); 31 | const links = ` 32 | 33 | ${filtered.map(link => ` 34 | 35 | ${link.url} 36 | ${('isGame' in link ? '' : '')} 37 | ${['title', 'desc', 'format', 'os', 'compression', 'compressedPrimary'].map(field => 38 | field in link ? `<${field}>${link[field]}` : '' 39 | ).join('\n')} 40 | 41 | `)} 42 | 43 | 44 | `; 45 | body.append('links', new Blob([links], { type: 'text/xml' })); 46 | body.append('replaceLinks', 'yes'); 47 | const response = await fetch(`${url}/putific`, { 48 | method: 'post', 49 | body, 50 | }); 51 | const { status, statusText, ok } = response; 52 | const text = await response.text(); 53 | if (!ok) { 54 | throw new Error(`Failed ${status} ${statusText} adding download link for ${name}: ${text}`); 55 | } 56 | console.log(`${url}/viewgame?id=${tuid}`, "OK"); 57 | }); 58 | 59 | for await (const result of runTasks(10, tasks.values())) { } 60 | -------------------------------------------------------------------------------- /importers/ifcomp/settings.mjs.template: -------------------------------------------------------------------------------- 1 | export const username = 'ifdbadmin@ifdb.org'; 2 | export const password = 'secret'; 3 | // the compStartDate will be the "first published on" date for IFDB listings 4 | // We'll also look for `IFCompYYYY.zip` based on this date 5 | // And submit download links pointing to https://ifarchive.org/if-archive/games/competitionYYYY/ based on the year 6 | export const compStartDate = '2022-10-01'; 7 | export const compYear = compStartDate.substring(0, 4); 8 | export const url = 'https://ifdb.org'; 9 | // export const url = 'http://localhost:8080'; -------------------------------------------------------------------------------- /importers/ifcomp/submit-external-links.mjs: -------------------------------------------------------------------------------- 1 | import { username, password, url, compYear } from './settings.mjs'; 2 | import { readFile } from 'fs/promises'; 3 | import { XMLParser } from 'fast-xml-parser'; 4 | import { runTasks } from 'concurrency-limit-runner'; 5 | 6 | const games = JSON.parse(await readFile('external-links.json', 'utf8')); 7 | 8 | function escapeXml(unsafe) { 9 | return unsafe.replace(/[<>&'"]/g, function (c) { 10 | switch (c) { 11 | case '<': return '<'; 12 | case '>': return '>'; 13 | case '&': return '&'; 14 | case '\'': return '''; 15 | case '"': return '"'; 16 | } 17 | }); 18 | } 19 | 20 | const tasks = []; 21 | 22 | for (const game of games) { 23 | tasks.push(async () => { 24 | const { name: downloadTitle, tuid, url: downloadLink, format, zipMainFile } = game; 25 | const viewgameUrl = `${url}/viewgame?id=${tuid}&ifiction`; 26 | let response; 27 | try { 28 | response = await fetch(viewgameUrl); 29 | } catch (e) { } 30 | if (!response?.ok) { 31 | throw new Error(`Failed (${response?.status}) loading ${viewgameUrl}`); 32 | } 33 | const xml = new XMLParser().parse(await response.text()); 34 | 35 | const body = new FormData(); 36 | body.append('username', username); 37 | body.append('password', password); 38 | body.append('lastversion', xml.ifindex.story.ifdb.pageversion); 39 | const ifiction = ` 40 | 41 | 42 | ${tuid} 43 | 44 | 45 | ` 46 | body.append('ifiction', new Blob([ifiction], { type: 'text/xml' })); 47 | const links = ` 48 | 49 | 50 | ${downloadLink} 51 | ${downloadTitle} 52 | 53 | ${format} 54 | ${format === 'executable' ? `Windows.`: ''} 55 | ${zipMainFile ? ` 56 | zip 57 | ${zipMainFile} 58 | `: ''} 59 | 60 | 61 | `; 62 | body.append('links', new Blob([links], { type: 'text/xml' })); 63 | response = await fetch(`${url}/putific`, { 64 | method: 'post', 65 | body, 66 | }); 67 | const { status, statusText, ok } = response; 68 | const text = await response.text(); 69 | if (!ok) { 70 | throw new Error(`Failed ${status} ${statusText} adding download link for ${name}: ${text}`); 71 | } 72 | console.log(downloadTitle, `${url}/viewgame?id=${tuid}`, "OK"); 73 | }) 74 | } 75 | 76 | for await (const result of runTasks(10, tasks.values())) { } 77 | -------------------------------------------------------------------------------- /importers/ifcomp/tag-games.mjs: -------------------------------------------------------------------------------- 1 | import { username, password, url, compYear } from './settings.mjs'; 2 | import { readFile } from 'node:fs/promises'; 3 | 4 | const games = JSON.parse(await readFile('microdata-tuids.json', 'utf8')); 5 | 6 | for (const {name, tuid} of games) { 7 | console.log(name, tuid); 8 | const result = await fetch(`${url}/taggame?xml`, { 9 | method: 'post', 10 | body: new URLSearchParams({ 11 | username, 12 | password, 13 | id: tuid, 14 | t0: `IFComp ${compYear}`, 15 | }), 16 | }).then(r => r.text()); 17 | if (//.test(result)) throw new Error(result); 18 | } -------------------------------------------------------------------------------- /importers/itch/.gitignore: -------------------------------------------------------------------------------- 1 | cover-art 2 | *.json 3 | settings.mjs 4 | -------------------------------------------------------------------------------- /importers/itch/README.md: -------------------------------------------------------------------------------- 1 | This importer is designed to grab the game list from an Itch.io game jam and upload the games to IFDB. 2 | 3 | # How to use the scripts 4 | 5 | 1. Run `npm install` to install dependencies. 6 | 2. Copy `settings.mjs.template` to `settings.mjs`, putting in your username, password, jamEntriesUrl, tagName, and the compStartDate. (If you want to test the scripts against a local IFDB dev environment, also change the url to `http://localhost:8080`.) 7 | 3. Run `node fetch-entries-json.mjs`. This will record the entries data in `entries.json`. 8 | 4. Run `node process-cover-art.mjs` to download all of the cover art in the `cover-art` directory. 9 | 5. Run `node submit-games.mjs` to submit all of the game listings. (Note that when testing in a dev environment, uploaded images will not appear in the web UI.) 10 | 6. Run `merge-tuids.mjs` to generate `entries-tuids.json`. Check the logs to ensure that we didn't create any duplicate IFDB entries. 11 | 12 | # List of the scripts 13 | 14 | 1. `settings.mjs`: All of the other scripts depend on this script. Copy `settings.mjs.template` to `settings.mjs`, putting in your username, password, jamEntriesUrl, tagName, and the compStartDate. (If you want to test the scripts against a local IFDB dev environment, also change the url to `http://localhost:8080`.) 15 | 1. `fetch-entries-json.mjs`: According to [this Itch forum post](https://itch.io/t/1487695/solved-any-api-to-fetch-jam-entries), Itch jams have an unofficial JSON URL at `https://itch.io/jam/ID/entries.json`. This script goes to the `entries.json` URL, processes the results, and stores the results in `entries.json`. 16 | 1. `process-cover-art.mjs`: This script reads `entries.json` and downloads the cover art for all games. Some games have art too large for IFDB's 256KiB limit, so we convert PNGs to JPG, and try lowering the quality bit by bit until the image is small enough to submit. We deposit the art in the `cover-art` directory, and store a record of our results in `cover-art.json`. 17 | 1. `submit-games.mjs`: This script reads `entries.json` and `cover-art.json`, and uses the IFDB [putific API](https://ifdb.org/api/putific) to create results for all games, including a "Play Online" link for all games that list their platform as "web." 18 | 1. `merge-tuids.mjs`: This script queries IFDB for each game in `entries.json` by title, finds the matching TUID, and generates `entries-tuids.json`. -------------------------------------------------------------------------------- /importers/itch/division-detector.mjs: -------------------------------------------------------------------------------- 1 | import {writeFileSync} from 'node:fs'; 2 | import {jamEntriesUrl, divisions} from './settings.mjs'; 3 | import entries from './entries-tuids.json' with {type: "json"}; 4 | import { runTasks } from 'concurrency-limit-runner'; 5 | 6 | async function sleep(ms) { 7 | return new Promise(r => setTimeout(r, ms)); 8 | } 9 | 10 | async function tryTryAgain(code, tries = 3) { 11 | for (let i = 0; i < tries; i++) { 12 | try { 13 | return await code(); 14 | } catch (e) { 15 | console.error(e); 16 | if (i < (tries - 1)) { 17 | console.error('retrying ' + i); 18 | } else { 19 | throw e; 20 | } 21 | } 22 | } 23 | } 24 | 25 | async function fetchRateUrl(rateUrl) { 26 | return tryTryAgain(async () => { 27 | await sleep(500); 28 | const response = await fetch(rateUrl); 29 | if (!response.ok) throw new Error(`failed fetching ${rateUrl}: ${response.status} ${response.statusText}: ${await response.text()}`); 30 | return response.text(); 31 | }); 32 | } 33 | 34 | const tasks = entries.map(entry => async () => { 35 | const rateUrl = `https://itch.io${entry.rate}`; 36 | console.log(rateUrl); 37 | 38 | const page = await fetchRateUrl(rateUrl); 39 | let found = false; 40 | for (const division of divisions) { 41 | if (page.includes(division)) { 42 | entry.division = division; 43 | found = true; 44 | break; 45 | } 46 | } 47 | if (!found) console.error(`Couldn't find a division for ${rateUrl}: ${page}`); 48 | }); 49 | 50 | for await (const result of runTasks(10, tasks.values())) { } 51 | 52 | writeFileSync('entries-tuids-divisions.json', JSON.stringify(entries, null, 2), 'utf8'); -------------------------------------------------------------------------------- /importers/itch/division-tagger.mjs: -------------------------------------------------------------------------------- 1 | import entries from './entries-tuids-divisions.json' with {type: "json"}; 2 | import { username, password, url } from './settings.mjs'; 3 | import { XMLParser } from 'fast-xml-parser'; 4 | import { runTasks } from 'concurrency-limit-runner'; 5 | 6 | const tasks = entries.map(entry => async () => { 7 | if (!entry.division) return; 8 | console.log(entry.tuid, entry.division); 9 | try { 10 | const text = await fetch(`${url}/gametags?xml`, { 11 | method: 'post', 12 | body: new URLSearchParams({ 13 | username, 14 | password, 15 | id: entry.tuid, 16 | mine_only: true, 17 | }), 18 | }).then(r => r.text()); 19 | const xml = new XMLParser().parse(text); 20 | let tags = xml.response.tag; 21 | if (!Array.isArray(tags)) { 22 | tags = [tags]; 23 | } 24 | tags.push({name: entry.division}); 25 | const result = await fetch(`${url}/taggame?xml`, { 26 | method: 'post', 27 | body: new URLSearchParams({ 28 | username, 29 | password, 30 | id: entry.tuid, 31 | ...Object.fromEntries(tags.map(({ name }, i) => [`t${i}`, name])) 32 | }), 33 | }).then(r => r.text()); 34 | if (//.test(result)) throw new Error(result); 35 | } catch (cause) { 36 | throw new Error(`failed on ${entry.tuid}`, {cause}); 37 | } 38 | }); 39 | 40 | for await (const result of runTasks(10, tasks.values())) { } 41 | -------------------------------------------------------------------------------- /importers/itch/fetch-entries-json.mjs: -------------------------------------------------------------------------------- 1 | import { jamEntriesUrl } from './settings.mjs'; 2 | import { writeFile } from 'node:fs/promises'; 3 | 4 | //https://itch.io/t/1487695/solved-any-api-to-fetch-jam-entries 5 | 6 | if (!/\/entries/.test(jamEntriesUrl)) throw new Error("URL should end with /entries"); 7 | const text = await fetch(jamEntriesUrl).then(r => r.text()); 8 | const match = /"entries_url":"\\\/jam\\\/(\d+)\\\/entries.json"/.exec(text); 9 | if (!match) { 10 | throw new Error("Couldn't scrape entries JSON"); 11 | } 12 | const entries_url = `https://itch.io/jam/${match[1]}/entries.json`; 13 | 14 | const {jam_games} = await fetch(entries_url).then(r => r.json()); 15 | 16 | console.log(JSON.stringify(jam_games, null, 2)); 17 | 18 | const results = jam_games.map(({ game: { id, title, url, short_text, cover, user: { name: author }, platforms = [], }, url: rate }) => ( 19 | { title, url, short_text, author, cover, id, play_online: platforms.includes('web'), rate } 20 | )); 21 | 22 | await writeFile('entries.json', JSON.stringify(results, null, 2), 'utf8'); 23 | -------------------------------------------------------------------------------- /importers/itch/merge-tuids.mjs: -------------------------------------------------------------------------------- 1 | import {url} from './settings.mjs'; 2 | import {readFile, writeFile} from 'fs/promises'; 3 | import {XMLParser} from 'fast-xml-parser'; 4 | import {runTasks} from 'concurrency-limit-runner'; 5 | 6 | const entries = JSON.parse(await readFile('entries.json', 'utf8')); 7 | const year = new Date().getFullYear(); 8 | 9 | const tasks = []; 10 | for (const game of entries) { 11 | tasks.push(async () => { 12 | //if (game.tuid) continue; 13 | const queryString = `${game.title} added:${year}`; 14 | const searchUrl = `${url}/search?xml&game&searchfor=${escape(queryString)}`; 15 | console.log(searchUrl); 16 | const response = await fetch(searchUrl); 17 | if (!response?.ok) { 18 | throw new Error(`Failed (${response?.status}) loading ${searchUrl}`); 19 | } 20 | const xml = new XMLParser().parse(await response.text()); 21 | let games = xml.searchReply.games.game; 22 | if (!Array.isArray(games)) { 23 | // when there's only one result, the parser inlines the array 24 | games = [games]; 25 | } 26 | const matches = games.filter(result => result?.title === game.title); 27 | if (!matches.length) { 28 | console.log(`Couldn't find ${game.title}: ${JSON.stringify(xml)}`); 29 | } else if (matches.length > 1) { 30 | console.log(`Too many matches for ${game.title}: ${JSON.stringify(xml)}`); 31 | } else { 32 | game.tuid = matches[0].tuid; 33 | } 34 | }) 35 | } 36 | 37 | for await (const result of runTasks(10, tasks.values())) { } 38 | 39 | await writeFile('entries-tuids.json', JSON.stringify(entries, null, 2), 'utf8'); 40 | -------------------------------------------------------------------------------- /importers/itch/process-cover-art.mjs: -------------------------------------------------------------------------------- 1 | import {readFile, writeFile, mkdir} from 'fs/promises'; 2 | import sharp from 'sharp'; 3 | const file = process.argv[2] ?? 'entries.json'; 4 | const games = JSON.parse(await readFile(file, 'utf8')); 5 | try { 6 | await mkdir('cover-art'); 7 | } catch (e) { 8 | if (e.code !== 'EEXIST') throw e; 9 | } 10 | const extensions = { 11 | 'image/jpeg': 'jpg', 12 | 'image/png': 'png', 13 | } 14 | const coverFiles = {}; 15 | 16 | const maxImageSize = 262144; 17 | 18 | async function resizeJpeg(inputBuffer) { 19 | const originalImage = sharp(inputBuffer); 20 | let outputBuffer = inputBuffer; 21 | let quality = 100; 22 | while (outputBuffer.length > maxImageSize) { 23 | quality -= 20; 24 | console.log({quality}); 25 | outputBuffer = await originalImage.jpeg({quality}).toBuffer(); 26 | } 27 | return outputBuffer; 28 | } 29 | 30 | await Promise.all(games.map(async ({cover, id}) => { 31 | if (!cover) return; 32 | const response = await fetch(cover); 33 | if (!response.ok) { 34 | throw new Error(`Error ${response.status} ${response.statusText} downloading ${cover}: ${await response.text()}`); 35 | } 36 | const contentType = response.headers.get('content-type'); 37 | let extension = extensions[contentType]; 38 | if (!extension) { 39 | throw new Error(`Couldn't handle ${contentType} image for ${cover}`); 40 | } 41 | let fileName = `cover-art/${id}.${extension}`; 42 | let image = Buffer.from(await response.arrayBuffer()); 43 | if (image.length > 250000) { 44 | const originalLength = image.length; 45 | if (extension === 'jpg') { 46 | image = await resizeJpeg(image); 47 | } else { 48 | extension = 'jpg'; 49 | fileName = `cover-art/${id}.${extension}`; 50 | image = await sharp(image).jpeg({quality: 100}).toBuffer(); 51 | image = await resizeJpeg(image); 52 | } 53 | console.log(`resizing ${fileName} from ${originalLength} to ${image.length}`); 54 | } 55 | await writeFile(fileName, image); 56 | coverFiles[id] = fileName; 57 | console.log(fileName); 58 | })); 59 | await writeFile("cover-art.json", JSON.stringify(coverFiles, null, 2), 'utf8'); -------------------------------------------------------------------------------- /importers/itch/settings.mjs.template: -------------------------------------------------------------------------------- 1 | export const username = 'ifdbadmin@ifdb.org'; 2 | export const password = 'secret'; 3 | // the compStartDate will be the "first published on" date for IFDB listings 4 | export const compStartDate = '2022-10-01'; 5 | // jamEntriesUrl should end in /entries 6 | export const jamEntriesUrl = 'https://itch.io/jam/parsercomp-2022/entries'; 7 | // all created entries will be tagged 8 | export const tagName = 'ParserComp 2022'; 9 | export const url = 'https://ifdb.org'; 10 | // export const url = 'http://localhost:8080'; -------------------------------------------------------------------------------- /importers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ifcomp-importer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "concurrency-limit-runner": "^1.0.0", 14 | "fast-xml-parser": "^4.0.11", 15 | "jszip": "^3.10.1", 16 | "leven": "^4.0.0", 17 | "microdata-node": "^2.0.0", 18 | "sharp": "^0.33.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /local-credentials.php.template: -------------------------------------------------------------------------------- 1 | "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", 14 | "private" => "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe", 15 | ); 16 | } 17 | 18 | function localAkismetKey() { 19 | return ""; 20 | } 21 | 22 | function localIfArchiveKey() { 23 | return ""; 24 | } 25 | 26 | ?> 27 | -------------------------------------------------------------------------------- /prepare_dev_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | #We're not auto-detecting DB updates because if we do any schema changes in production, we'll have to remove them from patch-schema.sql when we download the updated archive, months later 4 | #FILENAME=`curl --silent https://ifarchive.org/if-archive/info/ifdb/ | grep --only-matching "ifdb-archive-\d*.zip" | tail -1` 5 | FILENAME=ifdb-archive-20250308.zip 6 | if [ ! -f sql/$FILENAME ]; then 7 | curl -o sql/$FILENAME https://ifarchive.org/if-archive/info/ifdb/$FILENAME 8 | fi 9 | unzip -o sql/$FILENAME 10 | 11 | rm -rf initdb 12 | mkdir initdb 13 | cat sql/create-db.sql ifdb-archive.sql > initdb/00-init.sql 14 | cp sql/unscrub-ifarchive.sql initdb/01-unscrub-ifarchive.sql 15 | cp sql/create-admin.sql initdb/02-create-admin.sql 16 | cp sql/create-test-user.sql initdb/03-create-test-user.sql 17 | cp sql/incoming-schema-changes.sql initdb/04-incoming-schema-changes.sql 18 | 19 | sed 's/"127.0.0.1", "username", "password"/"db", "root", "secret"/' local-credentials.php.template > www/local-credentials.php 20 | -------------------------------------------------------------------------------- /prepare_full_dev_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | IFDB_FULL_EXPORT=$1 4 | 5 | rm -rf initdb 6 | mkdir initdb 7 | echo "create database if not exists ifdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS ifdb; use ifdb;" | cat - $IFDB_FULL_EXPORT/ifdb-*.sql > initdb/ifdb.sql 8 | echo "create database if not exists ifdb_images0; use ifdb_images0;" | cat - $IFDB_FULL_EXPORT/ifdb_images0-*.sql > initdb/ifdb_images0.sql 9 | echo "create database if not exists ifdb_images1; use ifdb_images1;" | cat - $IFDB_FULL_EXPORT/ifdb_images1-*.sql > initdb/ifdb_images1.sql 10 | echo "create database if not exists ifdb_images2; use ifdb_images2;" | cat - $IFDB_FULL_EXPORT/ifdb_images2-*.sql > initdb/ifdb_images2.sql 11 | echo "create database if not exists ifdb_images3; use ifdb_images3;" | cat - $IFDB_FULL_EXPORT/ifdb_images3-*.sql > initdb/ifdb_images3.sql 12 | echo "create database if not exists ifdb_images4; use ifdb_images4;" | cat - $IFDB_FULL_EXPORT/ifdb_images4-*.sql > initdb/ifdb_images4.sql 13 | cp sql/patch-full-schema.sql initdb/patch-full-schema.sql 14 | 15 | sed 's/"127.0.0.1", "username", "password"/"db", "root", "secret"/' local-credentials.php.template > www/local-credentials.php 16 | sed 's/return null/return localCredentials()/' www/local-credentials.php > www/local-credentials2.php 17 | mv www/local-credentials2.php www/local-credentials.php 18 | -------------------------------------------------------------------------------- /query-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker exec -it ifdb-db-1 mysql -psecret ifdb --default-character-set=utf8mb4 4 | -------------------------------------------------------------------------------- /sql/create-admin.sql: -------------------------------------------------------------------------------- 1 | USE ifdb; 2 | 3 | INSERT INTO `users` (`id`, `name`, `created`, `email`, `emailflags`, `profilestatus`, `password`, `pswsalt`, `activationcode`, `acctstatus`, `privileges`) VALUES ('0000000000000000', 'Admin', now(), 'ifdbadmin@ifdb.org', '0', 'T', 'be517aef5a4b32b84d8d4b170cdea17144240c93', '5b9b327dc1e326665ecb867f52f05938', '829435c662e1cf9c9f2d93c03df105cf388860f0', 'A', 'A'); 4 | -------------------------------------------------------------------------------- /sql/create-db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS ifdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 2 | CREATE USER IF NOT EXISTS ifdb; 3 | USE ifdb; 4 | 5 | -------------------------------------------------------------------------------- /sql/create-test-user.sql: -------------------------------------------------------------------------------- 1 | USE ifdb; 2 | 3 | INSERT INTO `users` (`id`, `name`, `created`, `email`, `emailflags`, `profilestatus`, `password`, `pswsalt`, `activationcode`, `acctstatus`, `privileges`) VALUES ('0000000000000001', 'Test Tester', now(), 'test@ifdb.org', '3', 'R', 'e47e74efb16c07efac450882bdfeaeba0677f3df', '02cbc07fd9c2e9a699f7a28df9833771', '3cd02536097bd7c576f1f4fd0c9798ca38fcc227', 'A', ''); 4 | -------------------------------------------------------------------------------- /sql/incoming-schema-changes.sql: -------------------------------------------------------------------------------- 1 | USE ifdb; 2 | 3 | -- use this script for pending changes to the production DB schema 4 | -------------------------------------------------------------------------------- /sql/patch-full-schema.sql: -------------------------------------------------------------------------------- 1 | USE ifdb; 2 | 3 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # IFDB Selenium IDE Testing 2 | 3 | This is the [Selenium IDE](https://www.selenium.dev/selenium-ide/) automated browser test suite for the IFDB. 4 | 5 | ## Preparing Your Own Testing Environment 6 | 7 | Selenium IDE is currently available as a Firefox Browser Add-On and as a Chrome Browser Extension. Select one, or both, depending on which browser you are interested in testing. 8 | 9 | ### Firefox Installation 10 | 11 | 1. Open Firefox and navigate to the download page for the [Firefox Browser Selenium IDE Add-On](https://addons.mozilla.org/en-GB/firefox/addon/selenium-ide/). 12 | 1. Follow the instructions for installing the add-on. 13 | 14 | ### Chrome Installation 15 | 16 | 1. Open Chrome and navigate to the Chrome Web Store for the [Chrome Browser Selenium IDE Extension](https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd). 17 | 1. Follow the instructions for installing the extension. 18 | 19 | ### Opening and Running Tests 20 | 21 | 1. Start up your local development version of the IFDB. 22 | 1. Open the browser of your choice and click on the Selenium IDE icon in the upper right corner. 23 | 1. In the pop up dialog, select `Open an existing project`. 24 | 1. Open `./IFDB.side`. 25 | 1. Select `Test suites` from the drop down menu on the upper left corner of the application. 26 | 1. Select any of the available test suites and tests. 27 | 1. To run the currently selected test, click on the `Run current test` button. 28 | 1. To run all of the tests in the suite, click on the `Run all tests in suite button`. 29 | 30 | For information about using the Selenium IDE, see: https://www.selenium.dev/selenium-ide/docs/en/introduction/getting-started. 31 | -------------------------------------------------------------------------------- /test/unit/test.php: -------------------------------------------------------------------------------- 1 | world", "hello world"], 8 | ["hello world bla bla", "hello world bla bla"], 9 | ["hello world bla bla", "hello world bla bla"], 10 | ["hello world bla bla", "hello world bla bla"], 11 | ["hello

world

", "hello

world

"], 12 | 13 | # Add tags at the end 14 | ["hello world bla bla", "hello world bla bla"], 15 | ["hello world bla bla", "hello world bla bla"], 16 | ["hello world bla bla", "hello world bla bla"], 17 | 18 | # Tags in the wrong order -- this is just documenting the current behavior. I don't know if it's desired. 19 | # It's probably not this function's job to fix bad HTML 20 | ["hello world", "hello world"], 21 | 22 | # Unknown tag 23 | ["hello world", "hello <unknown>world"], 24 | 25 | # Removed spoiler tag (in regular mode) 26 | ["hello secret world", "hello world"], 27 | 28 | # Replaced spoiler tag (in RSS mode) 29 | ["hello secret world", "hello [spoilers] world", FixDescRSS], 30 | 31 | # Strip newlines 32 | ["\nhello world\n", "hello world"], 33 | ["\r\nhello world\r\n", "hello world"], 34 | ]; 35 | 36 | foreach ($tests as &$test) { 37 | $output = fixDesc($test[0], count($test) == 3 ? $test[2] : 0); 38 | if ($output != $test[1]) { 39 | echo "Failed test.\nInput: {$test[0]}\nOutput: {$output}\nExpected: {$test[1]}\n"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | ErrorDocument 403 /error403 2 | ErrorDocument 404 /error404 3 | ErrorDocument 503 /error503 4 | 5 | 6 | ForceType application/x-httpd-php 7 | 8 | 9 | 10 | Order Deny,Allow 11 | Deny from All 12 | 13 | 14 | # Cache immutable when t parameter is present, else no-cache 15 | RewriteCond %{QUERY_STRING} (^|&)t= [NC] 16 | RewriteRule \.(css|js)$ - [E=CACHE_CONTROL:immutable] 17 | 18 | Header set Cache-Control "public, max-age=31536000, immutable" env=CACHE_CONTROL 19 | # When using mod_deflate, Apache refuses to send 304s with ETags 20 | # https://scarff.id.au/blog/2009/apache-304-and-mod_deflate-revisited/ 21 | FileEtag None 22 | 23 | 24 | Header set Cache-Control "public, no-cache" env=!CACHE_CONTROL 25 | FileETag None 26 | 27 | 28 | RewriteEngine on 29 | Options FollowSymLinks 30 | RewriteBase / 31 | 32 | RewriteCond %{HTTPS} =on 33 | RewriteCond %{HTTP_HOST} ^ifdb.tads.org$ 34 | RewriteRule ^(google7f64b84ffa585e59.html)$ $1 [L,NE] 35 | 36 | RewriteCond %{HTTPS} =on 37 | RewriteCond %{HTTP_HOST} !^ifdb.org$ 38 | RewriteCond %{HTTP_HOST} !^dev.ifdb.org$ 39 | RewriteRule ^(.*)$ https://ifdb.org/$1 [L,R=301,NE] 40 | 41 | RewriteCond %{HTTPS} !=on 42 | RewriteCond %{HTTP_HOST} ^ifdb.org$ [OR] 43 | RewriteCond %{HTTP_HOST} \.ifdb.org$ 44 | RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE] 45 | 46 | #Uncomment to enable maintenance mode 47 | #RewriteCond %{REQUEST_URI} "!/error503" 48 | #RewriteRule .* - [R=503,L] 49 | 50 | RewriteRule ^$ home [L] 51 | RewriteRule ^index$ home [L] 52 | RewriteRule ^index\.htm$ home [L] 53 | RewriteRule ^index\.html$ home [L] 54 | 55 | RewriteRule ^api/$ /api/index [END] 56 | RewriteRule ^api/index$ /api/ [L,R=301] 57 | 58 | RewriteRule ^dla-zoom/([a-z0-9]+)/[^/]+\.signpost$ /dladviser?id=$1&xml&os=MacOSX [L] 59 | 60 | RewriteRule ^users/([a-z0-9]+)/(.+)$ /userfile?u=$1&f=$2 [L] 61 | 62 | RewriteRule ^t3file/([a-z0-9]+)/(.+)$ /t3files?download=$1/$2 [L] 63 | 64 | RewriteRule ^images/(.*)$ /img/$1 [L,R=301,NE] 65 | RewriteRule ^dark-images/(.*)$ /img/dark-images/$1 [L,R=301,NE] 66 | RewriteRule ^([^/]*)\.(gif|jpg|png)$ /img/$1.$2 [L,R=301,NE] 67 | 68 | #RewriteCond %{SERVER_PORT} !^443$ 69 | #RewriteRule ^login$ mylogin [R] 70 | 71 | RewriteCond %{REQUEST_FILENAME} !-f 72 | RewriteRule (.*) /error404 [L] 73 | 74 | -------------------------------------------------------------------------------- /www/.well-known/.htaccess: -------------------------------------------------------------------------------- 1 | AuthType None 2 | Require all granted 3 | 4 | -------------------------------------------------------------------------------- /www/akismet.php: -------------------------------------------------------------------------------- 1 | Terms of Service. " 15 | . "You can revise your text and try again; removing any hyperlinks " 16 | . "that point to commercial sites might reduce the chances of " 17 | . "being flagged again. If you feel that your entry was " 18 | . "unfairly classified as spam, feel free to " 19 | . "contact us about the problem."; 20 | } 21 | 22 | ?> 23 | -------------------------------------------------------------------------------- /www/allnew: -------------------------------------------------------------------------------- 1 | " 35 | . makePageControl("allnew?" . http_build_query($params), $pg, $pg + ($tot > PER_PAGE ? 1 : 0), 36 | $firstOnPage, $lastOnPage, -1, 37 | false, false, false) 38 | . ""; 39 | 40 | $pageTitle = $reviews ? "New Reviews on IFDB" : "New on IFDB"; 41 | 42 | // start the page 43 | pageHeader($pageTitle); 44 | echo "

$pageTitle

\n
\n"; 45 | 46 | // show the page controls 47 | echo "$pageCtl


"; 48 | 49 | // show the new items 50 | showNewItems($db, $firstOnPage, $lastOnPage, $items, ['showFlagged' => $showFlagged]); 51 | 52 | // show the page controls again at the bottom 53 | echo "


$pageCtl
\n"; 54 | echo "

"; // prerender-moderate 55 | 56 | // end the page 57 | pageFooter(); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /www/allnew-rss: -------------------------------------------------------------------------------- 1 | \r\n"; 27 | ?> 28 | 29 | 30 | New on IFDB 31 | 32 | The latest reviews and listings on IFDB, the 33 | Interactive Fiction DataBase. 34 | en-us 35 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /www/api/api.php: -------------------------------------------------------------------------------- 1 | \n" 14 | . ".api__header, .api__footer { margin-top: 2em; }\n" 15 | . "\n"; 16 | 17 | 18 | echo "
" 19 | . "IFDB APIs > $title" 20 | . "
" 21 | . "

The $title API

"; 22 | } 23 | 24 | function apiPageFooter() 25 | { 26 | echo ""; 29 | pageFooter(); 30 | } 31 | 32 | 33 | ?> 34 | -------------------------------------------------------------------------------- /www/api/gametags: -------------------------------------------------------------------------------- 1 | 9 | 10 |

This API is deprecated. Use the viewgame API to view tags on a listing instead.

11 | 12 |

If you need to search for your own tags specifically, use the search API, like this:

13 | 14 |

https://ifdb.org/search?json&tag&searchfor=tuid:xxx+mine:yes

15 | 16 |

(Be sure to authenticate when searching for your own tags!)

17 | 18 |
19 | 20 |

The gametags API lets applications read tags on IFDB games.

21 | 22 |

Protocol

23 | 24 |

The gametags API is implemented as an http XML web service at 25 | https://ifdb.org/gametags. All parameters are passed as 26 | POST data with the HTTP request. As typical for HTTP, there's no 27 | session state or context; the entire transaction is carried out as 28 | the single request. 29 | 30 |

Parameter names and values

31 | 32 |

id: the TUID for 33 | the game you want to update. Ordinary text parameter; required. 34 | 35 |

mine_only: if this parameter is set, then we'll only return 36 | the tags that your username has set on the current game. Boolean 37 | parameter; optional. 38 | 39 |

username: the IFDB username for the account under which the 40 | listing will be created. Ordinary text parameter; required if 41 | mine_only is true. 42 | 43 |

password: the user's IFDB password. Ordinary text parameter; 44 | required if mine_only is true. 45 | 46 |

Reply format

47 | 48 |

The server replies to the request with an XML object describing the 49 | result. The response has this tag structure for a successful 50 | transaction: 51 | 52 |

53 | <response>
54 |     <tag>
55 |         <name>example</name>
56 |         <tagcnt>1</tagcnt>
57 |         <gamecnt>1</gamecnt>
58 |     </tag>
59 | </response>
60 | 
61 | 62 |

The tagcnt tag shows how many users have used that tag on the 63 | currently selected game. (Normally, tagcnt will be 1, because 64 | it's unusual for a player to add a tag when the game already has that 65 | tag.) 66 | 67 |

The gamecnt tag shows how many games have used that tag. 68 | 69 |

On error, the reply has this structure: 70 | 71 |

72 | <response>
73 |     <error>Please specify a valid username and password to login.</error>
74 | </response>
75 | 
76 | 77 | 78 | -------------------------------------------------------------------------------- /www/api/taggame: -------------------------------------------------------------------------------- 1 | 9 | 10 |

The taggame API lets applications edit tags on IFDB games.

11 | 12 |

Protocol

13 | 14 |

The taggame API is implemented as an http JSON web service at 15 | https://ifdb.org/taggame. All parameters are passed as 16 | JSON-encoded POST data with the HTTP request. 17 | 18 | 19 |

Provide your IFDB email address and password using HTTP Basic 20 | Authentication, using the Authorization: Basic ... header. 21 | (Optionally, you can provide "username" and "password" 22 | keys in the JSON request body, but Basic Authentication is preferred.) 23 | 24 |

Example request body

25 | 26 |

27 |

28 | {
29 |     "id": "the TUID for the game you want to update; required",
30 |     "tags": [
31 |         "one tag",
32 |         "another tag",
33 |         "yet another tag"
34 |     ],
35 | }
36 | 
37 |

38 | 39 |

The tags you add will remove/replace all tags you've previously 40 | set on the current game. If you submit a request with ["example1"] and then 41 | later submit a request with ["example2"], the API will 42 | remove your "example1" tag, replacing it with "example2". If 43 | you pass an empty array [], all of the tags you've set 44 | will be removed. 45 | 46 |

To add a tag without removing any tags, first search for your 47 | existing tags using the search API. Perform 48 | a search like this: 49 | 50 |

https://ifdb.org/search?json&tag&searchfor=mine:yes+tuid:xxx

51 | 52 |

Be sure to provide Basic Authentication credentials to the search. This will 53 | return a list of all of your own tags. Modify that tag list however you like, 54 | then submit your entire list of tags with this taggame API. 55 | 56 |

Reply format

57 | 58 |

The server replies to the request with an HTTP status code 200 OK on success. 59 | 60 |

On error, the server replies with another status code, and response text with an error message.

61 | -------------------------------------------------------------------------------- /www/async-recommendations: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /www/capreq: -------------------------------------------------------------------------------- 1 | $msg]); 16 | exit(); 17 | } 18 | 19 | // Get the captcha key. This identifiers the $_SESSION parameter for 20 | // the page that we're talking to. If there's no session key, it's 21 | // an invalid request. 22 | $skey = get_req_data("id"); 23 | if (!$skey) 24 | replyErr("The session key is missing from the request."); 25 | 26 | // retrieve the session data 27 | $skey = "CAPTCHA.$skey"; 28 | if (!isset($_SESSION[$skey])) 29 | replyErr("The session key is invalid. " 30 | . "Please refresh the page and try again."); 31 | 32 | $succReply = $_SESSION[$skey]; 33 | 34 | // Check to see if this is a reply with a code. If there's a code, 35 | // replace the commonly confused characters with the canonical mappings. 36 | $answer = get_req_data("code"); 37 | if ($answer) 38 | { 39 | // it's a response - validate it 40 | $resp = recaptcha_check_answer(RECAPTCHA_PRIVATE_KEY, $_SERVER["REMOTE_ADDR"], $answer); 41 | if ($resp->is_valid) { 42 | header("Content-Type: application/json"); 43 | header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 44 | header("Cache-Control: no-store, no-cache, must-revalidate"); 45 | echo "{\"reply\": $succReply}"; 46 | exit(); 47 | } else { 48 | $errTab = [ 49 | "incorrect-captcha-sol" => "Incorrect entry - please try again."]; 50 | if (isset($errTab[$resp->error])) 51 | replyErr($errTab[$resp->error]); 52 | else 53 | replyErr("reCAPTCHA error ($resp->error)"); 54 | } 55 | } 56 | else 57 | replyErr("Please enter the code."); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /www/captcha/.htaccess: -------------------------------------------------------------------------------- 1 | # 2 | # Order Deny,Allow 3 | # Deny from All 4 | # 5 | 6 | RewriteEngine on 7 | Options FollowSymLinks 8 | RewriteBase / 9 | 10 | RewriteCond %{REQUEST_FILENAME} !-f 11 | RewriteRule (.*) /error404 [L] 12 | 13 | RewriteCond %{REQUEST_FILENAME} -f 14 | RewriteRule (.*) /error403 [L] 15 | 16 | RewriteCond %{REQUEST_FILENAME} -d 17 | RewriteRule (.*) /error403 [L] 18 | 19 | -------------------------------------------------------------------------------- /www/captcha/audio/0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/0.wav -------------------------------------------------------------------------------- /www/captcha/audio/1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/1.wav -------------------------------------------------------------------------------- /www/captcha/audio/2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/2.wav -------------------------------------------------------------------------------- /www/captcha/audio/3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/3.wav -------------------------------------------------------------------------------- /www/captcha/audio/4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/4.wav -------------------------------------------------------------------------------- /www/captcha/audio/5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/5.wav -------------------------------------------------------------------------------- /www/captcha/audio/6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/6.wav -------------------------------------------------------------------------------- /www/captcha/audio/7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/7.wav -------------------------------------------------------------------------------- /www/captcha/audio/8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/8.wav -------------------------------------------------------------------------------- /www/captcha/audio/9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/9.wav -------------------------------------------------------------------------------- /www/captcha/audio/a.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/a.wav -------------------------------------------------------------------------------- /www/captcha/audio/b.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/b.wav -------------------------------------------------------------------------------- /www/captcha/audio/c.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/c.wav -------------------------------------------------------------------------------- /www/captcha/audio/d.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/d.wav -------------------------------------------------------------------------------- /www/captcha/audio/e.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/e.wav -------------------------------------------------------------------------------- /www/captcha/audio/f.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/f.wav -------------------------------------------------------------------------------- /www/captcha/audio/g.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/g.wav -------------------------------------------------------------------------------- /www/captcha/audio/h.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/h.wav -------------------------------------------------------------------------------- /www/captcha/audio/i.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/i.wav -------------------------------------------------------------------------------- /www/captcha/audio/j.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/j.wav -------------------------------------------------------------------------------- /www/captcha/audio/k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/k.wav -------------------------------------------------------------------------------- /www/captcha/audio/l.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/l.wav -------------------------------------------------------------------------------- /www/captcha/audio/m.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/m.wav -------------------------------------------------------------------------------- /www/captcha/audio/n.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/n.wav -------------------------------------------------------------------------------- /www/captcha/audio/o.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/o.wav -------------------------------------------------------------------------------- /www/captcha/audio/p.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/p.wav -------------------------------------------------------------------------------- /www/captcha/audio/q.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/q.wav -------------------------------------------------------------------------------- /www/captcha/audio/r.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/r.wav -------------------------------------------------------------------------------- /www/captcha/audio/s.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/s.wav -------------------------------------------------------------------------------- /www/captcha/audio/t.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/t.wav -------------------------------------------------------------------------------- /www/captcha/audio/u.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/u.wav -------------------------------------------------------------------------------- /www/captcha/audio/v.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/v.wav -------------------------------------------------------------------------------- /www/captcha/audio/w.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/w.wav -------------------------------------------------------------------------------- /www/captcha/audio/x.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/x.wav -------------------------------------------------------------------------------- /www/captcha/audio/y.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/y.wav -------------------------------------------------------------------------------- /www/captcha/audio/z.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/audio/z.wav -------------------------------------------------------------------------------- /www/captcha/fonts/A1.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A1.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A2.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A2.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A3.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A3.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A4.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A4.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A5.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A5.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A6.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A6.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A7.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A7.TTF -------------------------------------------------------------------------------- /www/captcha/fonts/A8.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/captcha/fonts/A8.TTF -------------------------------------------------------------------------------- /www/code-of-conduct: -------------------------------------------------------------------------------- 1 | 7 | 8 |

IFDB Code of Conduct

9 | 10 |

The Interactive Fiction Database (ifdb.org) is a community-run database of computer games whose player interactions center on text.

11 | 12 |

Guiding Principles

13 | 14 |
    15 |
  • Principle 1: All users deserve respect and have a right to use the site without fear of harassment.
  • 16 |
  • Principle 2: All users have the right to feel welcome and have their voice heard, as long as they follow the first principle.
  • 17 |
  • Principle 3: The goal of IFDB is to record, share, promote, and evaluate interactive fiction while following the first two principles.
  • 18 |
19 | 20 |

Policies for Prohibited Conduct

21 | 22 |

Harassment Policy

23 | 24 |

The following conduct is prohibited in comments, reviews, and other user-created text:

25 | 26 |
    27 |
  • Personal threats, sexual harassment, bigotry, or encouragement of harm to others
  • 28 |
  • Insults to other users
  • 29 |
  • Trolling other users, including hounding them with frequent comments, repeatedly deleting their contributions, or using game ratings or review votes in bad faith against individual users
  • 30 |
  • Inappropriate disclosure of someone's personal or contact information ("doxxing")
  • 31 |
32 | 33 | 34 | 35 |

The following is prohibited:

36 | 37 |
    38 |
  • Linking to illegal material
  • 39 |
  • Automated creation of game entries without moderator permission
  • 40 |
  • Posting spam, malware, or misleading links
  • 41 |
42 | 43 |

Rating Policy

44 | 45 |

Ratings and reviews are expected to be made in good faith. Ratings and reviews that are judged by moderators not to be in good faith may be removed.

46 | 47 |

The following are prohibited:

48 | 49 |
    50 |
  • Maintaining multiple accounts to support or denigrate specific games, authors, or users ("sockpuppeting")
  • 51 |
  • Using ratings for anything other than an evaluation of the game's contents ("bad-faith ratings")
  • 52 |
  • Encouraging others to break the above rules
  • 53 |
54 | 55 |

Off-site Policy

56 | 57 |

Harassment of other members of the IF community or promotion or coordination of bad-faith ratings may result in moderation even if it takes place off-site. Such harassment goes against our principles and makes IFDB a non-welcoming environment.

58 | 59 |

Security Policy

60 | 61 |

Circumventing passwords or other site features meant to protect personal information, or altering/employing code or exploits that may cause the site to work improperly are grounds for disciplinary action including termination of user accounts and removal of material as determined necessary by site staff.

62 | 63 | 66 | -------------------------------------------------------------------------------- /www/components/check-inbox.php: -------------------------------------------------------------------------------- 1 | $newCaughtUp) 7 | $newCaughtUp = $srow[4]; 8 | 9 | // $inboxCnt is returned by the queryComments function, which is 10 | // defined in commentutil.php and called in pagetpl.php 11 | if ($inboxCnt) { 12 | 13 | global $nonce; 14 | echo "\n"; 17 | 18 | echo "
" 19 | . "" 20 | . "" 21 | . "Your Inbox (RSS)" 22 | . "" 23 | . "Your Discussions
"; 24 | 25 | $new = $since = ""; 26 | if ($caughtUpDate) { 27 | $new = " new"; 28 | $since = "since ". date("F j, Y", strtotime($caughtUpDate)); 29 | } 30 | 31 | list($crow) = $inbox[0]; 32 | $cid = $crow[0]; 33 | $cdate = $crow[5]; 34 | $newCaughtUp = $crow[4]; 35 | echo "You have $inboxCnt $new item" . ($inboxCnt == 1 ? "" : "s") 36 | . " $since in your " 37 | . "comment inbox" 38 | . ($caughtUpDate ? "" : " (latest on $cdate)") 39 | . ".
"; 40 | } 41 | } 42 | 43 | ?> 44 | -------------------------------------------------------------------------------- /www/components/competitions.php: -------------------------------------------------------------------------------- 1 |

Competitions

2 | 3 | Add a competition listing 4 | 5 |
6 | 7 |

IF Comp | Spring Thing | XYZZY Awards

8 | 9 | " 43 | . "News on $gtitle: " 44 | . "$nhead " 45 | . "$ncre " 46 | . "Details" 47 | . ""; 48 | } 49 | else if ($pick == 'C') 50 | { 51 | // it's a competition 52 | $c = $row; 53 | 54 | // pull out the competition item 55 | $cid = $c["compid"]; 56 | $ctitle = htmlspecialcharx($c["title"]); 57 | $cdate = $c["fmtdate"]; 58 | 59 | 60 | // summarize the item 61 | echo "
" 62 | . "" 63 | . "$ctitle created $cdate" 64 | . "
" 65 | . "
" 66 | . "
"; 67 | } 68 | 69 | } 70 | 71 | ?> 72 |

73 | Browse competitions | 74 | Search competitions 75 |

-------------------------------------------------------------------------------- /www/components/database-stats.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
    4 | $cnt Game Listings"; 8 | 9 | $result = mysql_query( 10 | "select count(*) as c from reviews 11 | where special is null 12 | and review is not null 13 | and ifnull(now() >= embargodate, 1)", $db); 14 | $cnt = mysql_result($result, 0, "c"); 15 | if ($cnt) 16 | echo "
  • $cnt Member Reviews
  • "; 17 | 18 | $result = mysql_query( 19 | "select count(*) as c from reviews 20 | where 21 | rating is not null 22 | and special is null 23 | and review is null 24 | and ifnull(now() >= embargodate, 1)", $db); 25 | $cnt2 = mysql_result($result, 0, "c"); 26 | if ($cnt2) 27 | echo "
  • $cnt2 Member Ratings
  • "; 28 | 29 | $result = mysql_query( 30 | "select count(*) as c from users 31 | where acctstatus = 'A' and ifnull(profilestatus, ' ') != 'R' ", $db); 32 | $cnt = mysql_result($result, 0, "c"); 33 | if ($cnt) 34 | echo "
  • " 35 | . "$cnt Registered Members
  • "; 36 | 37 | $result = mysql_query("select count(*) as c from reclists", $db); 38 | $cnt = mysql_result($result, 0, "c"); 39 | if ($cnt) 40 | echo "
  • " 41 | . "$cnt Recommended Lists
  • "; 42 | 43 | $result = mysql_query("select count(*) as c from polls", $db); 44 | $cnt = mysql_result($result, 0, "c"); 45 | if ($cnt) 46 | echo "
  • " 47 | . "$cnt Polls
  • "; 48 | 49 | $result = mysql_query("select count(*) as c from competitions", $db); 50 | $cnt = mysql_result($result, 0, "c"); 51 | if ($cnt) 52 | echo "
  • " 53 | . "$cnt Competition Listings
  • "; 54 | ?> 55 |
56 |
57 | -------------------------------------------------------------------------------- /www/components/recommended-lists.php: -------------------------------------------------------------------------------- 1 |

Lists

2 | 3 | Create a recommended list 4 | 5 |
6 | 7 |

8 | \n" 26 | . ".new-item tr:first-child { vertical-align: top }\n" 27 | . ".new-item td:first-child { padding-right: 1em }\n" 28 | . "\n"; 29 | 30 | echo "" 32 | . "" 33 | . "
"; 34 | } 35 | 36 | // pull out the list record 37 | $itemcnt = $l['itemcnt']; 38 | $itemS = $itemcnt == 1 ? "" : "s"; 39 | $title = htmlspecialcharx($l['title']); 40 | $username = htmlspecialcharx($l['username']); 41 | 42 | // show the image: user image if available, otherwise the 43 | // generic list icon 44 | if (ENABLE_IMAGES) { 45 | if ($l["haspic"]) { 46 | echo "" 47 | . ""; 49 | } else { 50 | // echo "" 51 | // . ""; 52 | } 53 | echo ""; 54 | } 55 | 56 | // summarize it 57 | echo "
" 58 | . "$title " 59 | . "by $username, " 60 | . "$itemcnt item$itemS
"; 61 | 62 | if (ENABLE_IMAGES) 63 | echo "
"; 64 | } 65 | ?> 66 |

67 | Browse lists | 68 | Search lists 69 |

-------------------------------------------------------------------------------- /www/components/reviews.php: -------------------------------------------------------------------------------- 1 |

Reviews

2 | \n" 24 | . ".new-item tr:first-child { vertical-align: top }\n" 25 | . ".new-item td:first-child { padding-right: 1em }\n" 26 | . "\n"; 27 | 28 | echo "" 30 | . "" 31 | . ""; 85 | 86 | if (ENABLE_IMAGES) 87 | echo "
"; 32 | } 33 | 34 | // show the image: user image if available, otherwise game 35 | // image, otherwise generic review icon 36 | if (ENABLE_IMAGES) { 37 | if ($r["haspic"]) { 38 | echo "" 39 | . ""; 41 | } else if ($r["hasart"]) { 42 | echo "" 43 | . coverArtThumbnail($r['gameid'], 50, $r['pagevsn']) 44 | . ""; 45 | } else { 46 | // echo "" 48 | // . ""; 49 | } 50 | echo ""; 51 | } 52 | 53 | // summarize this review 54 | echo "
"; 55 | 56 | if (is_null($r['special'])) 57 | echo "" 58 | . htmlspecialcharx($r['username']) 59 | . " reviews "; 60 | else 61 | echo "A new review of "; 62 | 63 | echo "" 64 | . htmlspecialcharx($r['title']) 65 | . ""; 66 | 67 | if (!is_null($r['special'])) { 68 | $result = mysql_query("select name from specialreviewers 69 | where id = '{$r['special']}'", $db); 70 | echo " - " . mysql_result($result, 0, "name"); 71 | } else { 72 | echo ": \"" 73 | . htmlspecialcharx($r['summary']) 74 | . "\" "; 75 | } 76 | 77 | echo showStars($r['rating']); 78 | 79 | echo " - See full review"; 81 | 82 | echo "
"; 83 | if (ENABLE_IMAGES) 84 | echo "
"; 88 | } 89 | 90 | 91 | 92 | ?> 93 |

See the full list...

94 | -------------------------------------------------------------------------------- /www/components/top-reviewers.php: -------------------------------------------------------------------------------- 1 |
2 | IFDB's top reviewers, as determined by scores:

3 | 4 |

    5 | \n" 8 | . ".top-reviewers__reviewer { padding: 0 1.5ex 0 1ex; }\n" 9 | . "\n"; 10 | 11 | $rlst = getTopReviewers($db, 10); 12 | $n = 1; 13 | foreach ($rlst as $r) { 14 | list($ruid, $runame, $rscore) = $r; 15 | $runame = htmlspecialcharx($runame); 16 | echo "
  1. #$n" 17 | . "$runame
  2. "; 18 | $n++; 19 | } 20 | ?> 21 |
22 | 23 |

24 | Browse reviewers | 25 | Search reviewers | 26 | 27 | 28 |

29 | -------------------------------------------------------------------------------- /www/copyright: -------------------------------------------------------------------------------- 1 | 8 | 9 |

Copyrights

10 | 11 |

This site is copyrighted © 2007, by 12 | Michael J. Roberts. 13 | 14 |

Reviews from 15 | 16 | Baf's Guide to the IF Archive are copyrighted by their respective 17 | authors and are used here by permission. Baf's Guide is copyrighted 18 | © 2007 by Carl Muckenhoupt. 19 | 20 |

"Cover art" images (in game listings) and personal profile images 21 | have individual copyrights that are indicated in the full-size view of 22 | each image. When not otherwise specified, these images are 23 | copyrighted by their respective authors. 24 | 25 |

User-contributed items (such as reviews, bibliographical data, 26 | comments, and news items), except as noted above, are copyrighted by 27 | their credited authors and are licensed under a Creative 29 | Commons Attribution 3.0 United States license, as described in our 30 | Terms of Service. 31 | 32 |

Please note that the games themselves cataloged on IFDB are 33 | not user-contributed items, and thus are not subject to 34 | the Creative Commons license. IFDB doesn't store any games - just 35 | metadata about the games, such as bibliography and reviews. 36 | Each game's copyright and license terms are set exclusively by the 37 | game's author(s), and inclusion in IFDB does not in any way affect those 38 | terms. 39 | 40 | 41 |

Trademarks

42 | 43 |

Any trademarks, service marks, or similar marks or rights mentioned 44 | in any game listing or other material on this site are the property of 45 | their respective owners. They are used here for informational and 46 | educational purposes only, and do not imply any affiliation with or 47 | endorsement by their holders. 48 | 49 |

For More Information

50 | 51 |

Please contact us with any questions. 52 | 53 | 58 | -------------------------------------------------------------------------------- /www/csp-nonce.php: -------------------------------------------------------------------------------- 1 | 0) { 18 | $cont = mysql_result($result, 0, "contents"); 19 | echo $cont; 20 | 21 | // we've sent the style sheet - we're done 22 | return; 23 | } 24 | } 25 | 26 | // if we made it this far, we didn't find the requested style sheet - 27 | // fall back on the fixed default style sheet 28 | readfile("ifdb.css"); 29 | ?> 30 | -------------------------------------------------------------------------------- /www/editImageCopyright: -------------------------------------------------------------------------------- 1 | Your changes were saved.

"; 57 | else 58 | $statMsg = "$errMsg " 59 | . "Your changes were not saved.

"; 60 | } 61 | 62 | ?> 63 |

Image Copyright Information

64 | 65 |

66 |

68 | 69 | 70 |

Please select the image copyright status: 71 | 72 | 73 | 74 |

75 | 76 |

77 | 78 | 79 | 82 | -------------------------------------------------------------------------------- /www/error403: -------------------------------------------------------------------------------- 1 | 9 |

Forbidden

10 | 11 |

HTTP Error 403 12 | 13 |

Access to the page you requested is not allowed. 14 | 15 | 16 |

    17 | 18 |
  • If you typed the URL (the page address) directly into your 19 | browser's address bar, check to make sure you entered it correctly. 20 | 21 |
  • If you followed a link from another site, the link might be 22 | incorrect or out of date. If the problem persists, you might want to 23 | contact the administrator of the other site to let them know about the 24 | problem. 25 | 26 |
  • If you followed a link from within this site, please let us know 27 | so that we can fix the broken link - see our 28 | contact information. 29 | 30 |
  • You might be able to find the information you're looking for by 31 | following links from our home page, ifdb.org. 32 | 33 |
34 | 35 | 38 | -------------------------------------------------------------------------------- /www/error404: -------------------------------------------------------------------------------- 1 | 9 |

Page Not Found

10 | 11 |

HTTP Error 404 12 | 13 |

14 | Sorry! We couldn't find the page you're trying to reach. 15 | 16 |

    17 | 18 |
  • If you followed a link from another site, the link might be 19 | incorrect or out of date - you might want to contact the other site's 20 | owner to let them know about the problem. (If the broken link is on 21 | IFDB itself, see our contact information.) 22 | 23 |
  • If you typed the URL (the page address) directly into your 24 | browser's address bar, check to make sure you entered it correctly. 25 | 26 |
  • You might be able to find the information you're looking for via 27 | our home page, ifdb.org. 28 | 29 |
30 | 31 | 34 | -------------------------------------------------------------------------------- /www/error503: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | IFDB Service Unavailable 10 | 16 | 17 | 18 |

IFDB Service Unavailable

19 | 20 |

HTTP Error 503 21 | 22 |

The server is temporarily unable due to maintenance downtime or capacity problems. Please try again later. 23 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicon.ico -------------------------------------------------------------------------------- /www/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /www/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /www/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /www/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /www/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /www/favicons/favicon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftechfoundation/ifdb/034449794ea5e3e17f5b603e4fb4997b64a89d58/www/favicons/favicon.ai -------------------------------------------------------------------------------- /www/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IFDB", 3 | "short_name": "IFDB", 4 | "icons": [ 5 | { 6 | "src": "/favicons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#000000", 17 | "background_color": "#000000", 18 | "display": "minimal-ui" 19 | } 20 | -------------------------------------------------------------------------------- /www/filetypes.php: -------------------------------------------------------------------------------- 1 | array("Story File", 16 | false, 17 | "Interpreter-based story file - requires an " 18 | . "interpreter for the user's OS to play.", 19 | false), 20 | "GameExe" => array("Story Program", 21 | "Download and run this application to play " 22 | . "the game.", 23 | "Stand-alone executable game application.", 24 | false), 25 | "Inst" => array("Story Installer", 26 | "This Setup program automatically installs the " 27 | . "game on your system.", 28 | "Self-contained, executable SETUP program that " 29 | . "installs the game.", 30 | "img/setupicon.gif"), 31 | "Hints" => array("Hints", 32 | "Hints for the game (this may contain spoilers).", 33 | false, 34 | "img/hintfileicon.gif"), 35 | "Misc" => array("Miscellaneous", 36 | false, 37 | "Miscellaneous file(s).", 38 | false), 39 | "ReadMe" => array("ReadMe", 40 | "Brief overview and installation notes.", 41 | false, 42 | "img/readmeicon.gif"), 43 | "Src" => array("Source Code", 44 | "The source file(s) for the game.", 45 | false, 46 | "img/srcfileicon.gif"), 47 | "Walk" => array("Walkthrough", 48 | "Full instructions to solve the game. Warning: " 49 | . "contains spoilers.", 50 | false, 51 | "img/walkthruicon.gif")); 52 | } 53 | 54 | ?> 55 | -------------------------------------------------------------------------------- /www/google7f64b84ffa585e59.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google7f64b84ffa585e59.html 2 | -------------------------------------------------------------------------------- /www/help-add-game: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Guidelines for New Game Listings

9 | 10 |
    11 |
  • IFDB is for interactive fiction, which is roughly 12 | defined as "adventure" type games that are mostly or entirely text-based. 13 | Games with graphical content are allowed as long as they have a 14 | significant text component. 15 | 16 |
  • Don't list non-IF games, such as entirely graphical 17 | adventures, RPGs, shooters, etc. 18 | 19 |
  • Commercial IF is acceptable, whether classic or new. 20 | You can add a commercial game of your own, but please read 21 | our guidelines about what goes 22 | in your listing. 23 | 24 |
  • Don't add separate listings for different versions of 25 | a game. Any given game should have only one listing that covers 26 | all of its release versions. 27 |
28 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /www/help-author: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Tips for the Author Field

9 | 10 |

Linking to a profile

11 | 12 |

If the game's author is an IFDB member, you can link the game to 13 | the author's profile by putting the profile TUID in {curly braces} after the author's name. 15 | For example, Arthur Dent {a10x00139ke9041j}. You can find the 16 | TUID on the profile page. 17 | 18 |

Identifying the author

19 | 20 |

Most freeware IF shows the author's byline as part of its introductory 21 | text, so you can usually find the author by running the game and scanning 22 | the first screen or two of output. 23 | 24 |

For a commercial game, enter the author's or designer's name rather 25 | than a company name. If the author isn't credited anywhere, you can 26 | enter the name of the company that developed or published the game. 27 | 28 |

If the game was released without any author or publisher credits, 29 | enter "Anonymous" as the author. 30 | 31 |

If you think the game has an author credit, but you can't actually 32 | find the information for some reason (for example, your only copy is 33 | for an obsolete hardware platform, or you simply don't have a copy of 34 | the game), enter "Unknown." 35 | 36 |

For a game with multiple authors, simply enter them the way they're 37 | shown in the game's byline: "Julia Smithers and Orson D. Bisonhonker," 38 | for example. For a commercial game with a lengthy list of credits, enter 39 | the primary author or authors. 40 | 41 | 44 | -------------------------------------------------------------------------------- /www/help-bafs: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Baf's Guide Identifiers

9 | 10 |

IFDB's predecessor, 11 | 12 | Baf's Guide to the IF Archive, was an extensive catalog 13 | of the games at the IF Archive. 14 | Baf's Guide included search and cross-referencing features, and 15 | provided well-written capsule reviews for a huge selection of the 16 | IF Archive's games. 17 | 18 |

Baf's Guide assigned a unique number to each game in its 19 | database. This number was designed to be a permanent ID for the 20 | game. If you go to the 21 | 22 | Baf's Guide site and navigate to a page for one of the games, 23 | the original URL of the game page should look something like this: 24 | 25 |

26 |   http://www.wurb.com/if/game/395
27 | 
28 | 29 |

The number at the end of the URL is the Baf's ID - in this 30 | case, 395. 31 | 32 |

Since Baf's Guide is no longer maintained, newer games won't 33 | have a Baf's Guide ID. 34 | 35 | 38 | -------------------------------------------------------------------------------- /www/help-crossrec: -------------------------------------------------------------------------------- 1 | 7 | 8 |

How does IFDB pick games to recommend?

9 | 10 |

The recommendations on the IFDB home page are "algorithmic" - 11 | they're picked by the computer based on statistics in the database. 12 | They're not paid ads - they're based purely on ratings from our 13 | members and review sites like 14 | Baf's Guide. We're not being paid to promote one 16 | game over another. 17 | 18 | 23 | 24 |

The system picks front-page recommendations by randomly selecting 25 | a few games with the highest ratings, excluding games that you've 26 | told us you've already played. 27 | 28 |

We determine the games with the highest ratings using 29 | Evan 30 | Miller's formula, which we call "starsort". 31 | 32 |

We used to sort by average rating, but this would tend to rank games 33 | with just one perfect 5-star rating above games with dozens of 5-star 34 | ratings and a few 4-star ratings. Evan Miller's formula sorts by our 35 | confidence in the game, by adding five "fake" ratings to the average 36 | (one 1-star, one 2-star, one 3-star, one 4-star, and one 5-star rating) 37 | and subtracting the standard deviation from the result. 38 | 39 | 42 | 43 |

The system picks front-page recommendations by looking for other 44 | members with similar patterns of likes and dislikes to your own, as 45 | expressed in the ratings they gave to the games they reviewed. IFDB 46 | tries to match you up with a few other members, then recommends games 47 | that those other members rated highly. 48 | 49 |

In principle, the more ratings you and other members provide, 50 | the more accurate the matching will become. So the recommendations 51 | should get better and better as you rate more games. 52 | 53 |

This approach is sometimes called "collaborative filtering." Some 54 | people think it's great, others are skeptical. The obvious objection 55 | is that it doesn't capture the reasons that you like the games 56 | you like, so it might match you up with someone who happens to like 57 | some of the same games, but for completely different reasons. There's 58 | obviously no guarantee that the approach will actually produce good 59 | advice, but we hope that it gives you at least a few leads on games 60 | that you might otherwise have overlooked. 61 | 62 |

If the algorithmic recommendations on the home page don't work for 63 | you, remember that IFDB still offers several ways to get 64 | personal recommendations from other users, such as member 65 | reviews and Recommended lists. 66 | 67 | 71 | -------------------------------------------------------------------------------- /www/help-css: -------------------------------------------------------------------------------- 1 | 7 | 8 |

CSS Help

9 | 10 |

CSS (Cascading Style Sheets) is a Web standard, like HTML, so it's 11 | easy to find documentation about it on the Internet. Here are a 12 | couple of pointers to get you started: 13 | 14 |

21 | 22 |

Import the Default Style Sheet

23 | 24 |

Here's an important tip: you should always start your custom style sheets 25 | with this line: 26 |

27 |    @import url("/ifdb.css");
28 | 
29 |

This includes all of the styles from the default IFDB style sheet, 30 | so that you only have to define exceptions to the standard 31 | formatting. Everything you add to your style sheet will override the 32 | imported defaults, but anything you don't override will fall back 33 | on the defaults. This ensures that if any new styles are added in the 34 | future, they'll be supported automatically in your style sheet by 35 | virtue of being included from the standard definitions. 36 | 37 |

Custom Graphics

38 | 39 |

A style sheet can override most of the graphics on IFDB. To use 40 | your own custom graphics, you first need to upload your images to IFDB 41 | using the Style Sheet Image 42 | Manager. Once you've uploaded your images, you can use them in a 43 | style sheet - look at the standard style sheet (ifdb.css) and search for "background:" styles to 45 | see how it's done. 46 | 47 | 50 | -------------------------------------------------------------------------------- /www/help-discussions: -------------------------------------------------------------------------------- 1 | 7 | 8 |

IFDB Discussions

9 | 10 |

You can add comments to various pages on IFDB, including 11 | game reviews, Recommended Lists, and polls. You can also add 12 | comments to another member's profile. 13 | 14 |

You can reply other people's comments, which makes for a simple 15 | discussion system. This isn't a full-fledged "forum" system, let 16 | alone a replacement for the 17 | intfiction.org forum, but for comments 18 | related to a particular IFDB page, it has the advantage of keeping the 19 | discussion together with its subject. 20 | 21 |

You can start a discussion by clicking the "Add a comment" link 22 | shown below a game review, on a list or poll's main page, or on a 23 | user's profile page. A "View comments" link will appear as soon as a 24 | page has comments. You can reply to a comment by clicking the "Reply" 25 | link next to the comment. 26 | 27 | 30 | -------------------------------------------------------------------------------- /www/help-edit-game: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Guidelines for Game Listings

9 | 10 |
    11 | 12 |
  • Anyone is allowed to edit a game listing, whether they wrote the 13 | game or not. 14 | 15 |
  • Keep the listing factual. If you're not sure of a piece of data, 16 | leave it blank (or if it's already there, don't change it). 17 | 18 |
  • Don't include spoilers. Don't give away plot twists, 19 | solutions to puzzles, or any other of the game's secrets. 20 | 21 |
  • Don't use the listing as a review. If you want to express 22 | your opinion about the game, write an actual review instead (by going 23 | to the game's home page and clicking "Write a review"). 24 | 25 |
  • Don't attack the author. 26 | 27 |
  • Avoid things like "better" or "worse" comparisons to other games, 28 | judgments on writing quality, overall excellence, etc. Those belong in a 29 | review, not a listing. 30 | 31 |
  • It's okay to "sell" the game a little in its description, but 32 | keep it informative, basically factual, and focused on the game. The 33 | description should be like the "blurb" on the inside flap of a book - 34 | it should give readers an idea of what the game is about and what its 35 | overall style is. 36 | 37 |
      38 |
    • "An emotional 39 | roller-coaster ride with plot twists that keep you guessing to the 40 | end" - that's fine, because it tells you something about the style 41 | of the game. 42 | 43 |
    • "The first good text game since Infocom stopped making them!" - 44 | not so good, because it doesn't tell you anything concrete 45 | about the game, and it's completely subjective. 46 |
    47 | 48 |
49 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /www/help-forgiveness: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Forgiveness Ratings

9 | 10 |

The Forgiveness Rating measures how much freedom the game gives you 11 | to screw up the story. For example, in some games it's possible for 12 | the main character to die by doing the wrong thing, while in others 13 | there's no way to kill the main character. But dying is only the most 14 | obvious way to "lose" in IF. In some games, you can get yourself into 15 | a situation where you can go on playing forever, but you'll never be 16 | able to win because of some irreversible action you've taken. For 17 | instance, the game might let you lock your keys in the car, or it might 18 | let you discard or destroy a vital object that you'll need to solve to 19 | a puzzle much later in the game. 20 | 21 |

The Zarfian forgiveness scale was proposed by Andrew Plotkin on rec.arts.int-fiction to classify 23 | games according to whether they make unwinnable situations possible, 24 | and to what degree: 25 | 26 |

    27 | 28 |
  • Merciful - You can't get stuck, and the main character 29 | can't die. It's impossible to permanently lose an object that's 30 | required to win the game. The game will always remain winnable no 31 | matter what you do. 32 | 33 |
  • Polite - The main character can die, and it's possible to 34 | lose vital objects or otherwise make it impossible to win, but 35 | it's immediately obvious whenever you've just done something that 36 | makes the game unwinnable. This means that you can always immediately 37 | UNDO to get back on track. 38 | 39 |
  • Tough - You can get stuck or die, but it's immediately 40 | obvious when you're about to do something irrevocable. (You might not 41 | be able to anticipate that a particular action will make the game 42 | unwinnable, though - only that it will be irreversible.) 43 | 44 |
  • Nasty - You can get stuck or die, but it's immediately 45 | obvious after the fact when you've done something irrevocable. 46 | (Again, though, you might not realize that an action made the game 47 | unwinnable - just that the action was irrevocable.) 48 | 49 |
  • Cruel - You can get stuck or die, and you can do so by an 50 | action that you didn't even realize was irrevocable after doing it. 51 | 52 |
53 | 54 | 57 | -------------------------------------------------------------------------------- /www/help-formatting: -------------------------------------------------------------------------------- 1 | 9 | 10 |

Formatting Hints

11 | 12 |

You can use some limited HTML-style formatting code in this field: 13 | 14 | 15 | 16 |

<SPOILER> ... 17 | </SPOILER> - marks everything between the tags as "spoilers" 18 | - information that reveals the game's secrets. A reader who hasn't 19 | played the game might not want to see this, since it would spoil the 20 | fun of discovery when they play. IFDB will hide this text until the 21 | reader clicks a button saying they want to see it. 22 | 23 | 24 | 25 |

<B> ... </B> - the text between the <B> and 26 | </B> is shown in boldface. 27 | 28 |

<I> ... </I> - the text between the <I> and 29 | </I> is shown in italics. 30 | 31 |

<BR> - line break: ends the current line and starts a new 32 | one, with no extra space between the lines. 33 | 34 |

<P> - paragraph break: shows a blank line between the paragraphs. 35 | 36 |

<BR/> - paragraph break (this is provided so that you can 37 | copy text from an iFiction 38 | XML file - the iFiction format uses <BR/> for paragraph breaks) 39 | 40 |

<A GAME="tuid"> ... </A> - hyperlink the text 41 | between the <A> and </A> to a game. tuid is the 42 | TUID identifying the game. Rather than writing 43 | this out by hand, you can use our 44 | game hyperlink generator. 45 | 46 |

<A HREF="url"> ... </A> - hyperlink the 47 | text between the <A> and </A> to any Web site. (For 48 | safety reasons, Javascript isn't allowed. The URL must start with 49 | http://, ftp://, or news:.) 50 | 51 |

&lt; - a less-than sign (<) 52 | 53 |

&gt; - a greater-than sign (>) 54 | 55 |

&amp; - an ampersand (&) 56 | 57 |

If you use any of the line or paragraph break codes, then any 58 | newlines in your text will be ignored - we'll assume that you wanted 59 | to control the line formatting entirely with the HTML-style codes. 60 | On the other hand, if you don't use any break codes at all, 61 | we'll assume that you wanted any newlines in the text to show up 62 | literally as line breaks in the displayed version. 63 | 64 | 67 | 68 | -------------------------------------------------------------------------------- /www/help-gameprofilelink: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Linking a game to your profile

9 | 10 |

If you're the author of a game listed in IFDB, you can link it to your 11 | profile. This will automatically include the game in a list of 12 | authorship credits on your public profile page, and it will include a 13 | link to your profile next to your name as it appears in the byline on 14 | the game's overview page. 15 | 16 |

To link a game to your profile, follow these steps: 17 | 18 |

    19 | 20 |
  • Go to the game's main page 21 | 22 |
  • Click "Edit this page" at the bottom of the game page 23 | 24 |
  • In the Author field, insert your profile ID (the 25 | TUID) in {curly braces} after 26 | your name. For example, Arthur Dent {0x098109klasd09313k}. 27 | You can find the TUID on your main profile page. 28 | 29 |
  • Save your changes. A link to your profile will now appear 30 | on the game's page after your name in the byline, and the game 31 | will be listed in your profile's authorship section. 32 | 33 |
34 | 35 |

Unlinking a game

36 | 37 |

To remove a profile link from a game, just edit the game's listing 38 | and remove your profile ID (the {xxxxxxx} code) from the Author 39 | field. 40 | 41 | 44 | -------------------------------------------------------------------------------- /www/help-image-quota: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Why is there an image limit?

9 | 10 |

The limit on image uploads for style sheets is to ensure there's 11 | enough space on our server for everyone who wants to use this 12 | feature. 13 | 14 |

Our server resources are limited, so we unfortunately can't offer 15 | an infinite, general-purpose image hosting service for your vacation 16 | photos, eBay listings, etc. We created the Style Sheet Image Manager 17 | specifically to give you more control when customizing IFDB, by making 18 | it easier to add your own images to your custom style sheets. The 19 | limits should be more than adequate for this purpose, but if you do 20 | find yourself running out of space, contact us. 21 | 22 | 25 | -------------------------------------------------------------------------------- /www/help-license-type: -------------------------------------------------------------------------------- 1 | 7 | 8 |

IF License Types

9 | 10 |

The License Type field isn't meant to spell out the game's exact 11 | licensing terms - it's just meant to give people a general idea of the 12 | category the license fits into. Most IF fits one of these common 13 | categories: 14 | 15 |

Freeware: Software that's copyrighted, but which the author 16 | allows you to use without charge. There might be some restrictions; 17 | for example, users might not be allowed to modify the software. This 18 | is the most common license type for games in the IF Archive. 20 | 21 |

Public Domain: The author has explicitly renounced all 22 | rights to the software, allowing anyone to use it or modify it in any 23 | way they like. (Public Domain isn't the same as Freeware - a work 24 | isn't in the public domain unless its author specifically says it is.) 25 | This is uncommon in IF. 26 | 27 |

GPL, Apache, BSD, Creative Commons: If the work is published 28 | under one of these well-known "Open Source" licenses, you can put the 29 | license's name in the License Type field. 30 | 31 |

Shareware: The software is distributed for free, but 32 | the author asks that you pay a registration fee if you like it. 33 | Shareware operates under the honor system, so there are no technical 34 | limitations in the software that would force you to pay. 35 | 36 |

Former shareware: The software was once distributed as 37 | Shareware, but the author has since made it free. 38 | 39 |

Demo: This is similar to shareware, but instead of relying 40 | on the honor system, the publisher puts technical limitations in the 41 | free version. For a game, this usually means that only a portion of 42 | the storyline is included - you can play up to a certain point, but 43 | you have to buy the full version to go on. Alternatively, a demo 44 | version might have some critical features disabled, or might have a 45 | timer that disables the whole program after a trial period expires. 46 | 47 |

Commercial: The software is available only to paying customers. 48 | 49 |

Former commercial: The software was once sold commercially, 50 | but the copyright holder has since officially re-released it as free 51 | software. 52 | 53 |

Commercial (out of print): A product that was once published 54 | commercially, but is no longer being sold and has never been officially 55 | re-released for free. 56 | 57 |

Abandonware: A loaded term that we ask you not to use 58 | here. The word can spark heated arguments about copyright politics, 59 | and this isn't the place for that. For a title that was once 60 | published commercially and is no longer available for sale, please 61 | enter "Commercial (out of print)" instead. 62 | 63 | 64 | 67 | 68 | -------------------------------------------------------------------------------- /www/help-link-policy: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Policy for External Links

9 | 10 |

Please link only to legal downloads. This means that 11 | the copyright owner must explicitly allow the download. 12 | 13 |

Our Terms of Use prohibit uploading material that violates anyone's 14 | copyrights or other intellectual property rights. This prohibition 15 | includes merely linking to illegal material or to sites that 16 | traffic in illegal material, such as "warez" or "abandonware" sites. 17 | Such linking can be considered "contributory infringement," which 18 | means that it's essentially as illegal as offering the unauthorized 19 | content directly. 20 | 21 |

Violating this policy could seriously damage IFDB, both legally 22 | and in terms of its reputation. Please do your best to comply to 23 | help ensure that the site can continue operating. 24 | 25 |

It's always acceptable to link to sites that maintain and enforce 26 | similar policies of diligent copyright compliance, such as the IF Archive. 28 | 29 | 32 | -------------------------------------------------------------------------------- /www/help-pending-link: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Pending External Links

9 | 10 |

The "pending" checkbox for an external link tells IFDB that the 11 | file isn't available for download yet, but will be soon. If you check 12 | this box, IFDB will do two things: 13 | 14 |

    15 | 16 |
  • Hide the link on the game's home page, so that users viewing the 17 | page won't be confused by a broken link error. 18 | 19 |
  • Check the URL periodically to see if it's working yet, and remove 20 | the "pending" flag as soon as the link works. 21 | 22 |
23 | 24 |

This option is especially useful with the IF Archive, because 25 | files uploaded to the Archive are not available for download until 26 | they're reviewed by administrators. This process usually takes a few 27 | days. You could just wait to add the download link until the 28 | file appears on the Archive, but the "pending" option saves you that 29 | extra editing step. Just check the box, and IFDB will monitor the 30 | Archive for you, updating the link as soon as it's working. 31 | 32 | 35 | -------------------------------------------------------------------------------- /www/help-polls: -------------------------------------------------------------------------------- 1 | 7 | 8 |

What's an IFDB Poll?

9 | 10 |

A poll is essentially a Recommended List 11 | in reverse. A Recommended 12 | List lets you create a list of your favorite games in a particular 13 | category, to share with the rest of IFDB. A poll turns the tables: it 14 | lets you ask everyone for recommendations for some particular kind of 15 | game you're looking for. 16 | 17 |

The category can be anything you want it to be. Just describe what 18 | kind of game you're looking for. 19 | 20 |

Once you create a poll, it will appear on the IFDB home page, 21 | inviting other members to submit their "votes" - suggestions for games 22 | that fit the category. As the votes come in, they'll show up 23 | automatically on the poll's page, so you can check back from time to 24 | time to learn which games people have recommended. 25 | 26 |

A nice feature of polls is that anyone can see the results, so 27 | everyone can benefit from the recommendations. So a poll essentially 28 | becomes a collaborative Recommended List built by the whole IFDB 29 | community. 30 | 31 | 34 | -------------------------------------------------------------------------------- /www/help-reclist: -------------------------------------------------------------------------------- 1 | 7 | 8 |

What's a Recommended List?

9 | 10 |

A Recommended List lets you share your suggestions with other 11 | users. Each list has a category, so a list lets you recommend a 12 | specific kind of game. 13 | 14 |

The category of a list can be anything you want. It can range from 15 | something very general, such as your top 10 games overall, to very 16 | specific: your favorite one-room games set in medieval castles, say, 17 | or the hardest puzzle games you've played. You can base categories on 18 | things like subject matter, genre, setting, time to play, difficulty, 19 | intended audience... any set of criteria that you think would make a 20 | good list. 21 | 22 |

You can create as many lists as you like, so there's no need to fit 23 | all of your favorites into a single list. 24 | 25 |

If you're looking for recommendations from other users, you can 26 | look for a Recommended List using IFDB's Search feature. If you can't 27 | find any lists with the sorts of games you're looking for, you can 28 | create a Poll, which is the reverse of a 29 | list: it lets you ask other IFDB members to recommend games in 30 | whatever category you describe. 31 | 32 | 35 | -------------------------------------------------------------------------------- /www/help-review: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Review Guidelines

9 | 10 |

A good review is more than just a one-liner saying how much you 11 | liked (or didn't like) the game. Think about it from the perspective 12 | of someone reading the review - they want to know why 13 | you feel the way you do about the game. 14 | 15 |

Describe your own tastes. A reader who hasn't played the 16 | game yet would like to know if it's the sort of thing they'd enjoy. 17 | It helps to know if a reviewer has similar taste, so 18 | describe how the game measures up to your specific likes and dislikes. 19 | 20 |

Be specific. Readers like concrete details: things like the 21 | the difficulty level, playing time, writing style and quality, tone, 22 | how much the focus is on puzzles vs. story, and so on. 23 | 24 |

Avoid spoilers. A spoiler is 25 | information that gives away one of the game's secrets - the solution 26 | to a puzzle, a plot twist, etc. Many people who read your review 27 | won't have played the game yet - don't take away their chance to 28 | discover the game's secrets on their own. 29 | 30 |

31 | If you can't avoid spoilers, mark them. Sometimes it's 32 | difficult to entirely avoid spoilers. If you really feel it's 33 | important to include something spoilery in your review, mark it 34 | like so: 35 |

36 |   <spoiler>This is a spoiler!</spoiler>
37 | 
38 |

IFDB will hide the text within the <spoiler>...</spoiler> 39 | section until the reader clicks a button to explicitly reveal it. 40 | 41 |

Avoid referring to other member reviews on the page. The 42 | order and placement of reviews can change, and people can edit their 43 | reviews, so any references you make could lose their context and 44 | become confusing as the game's page evolves. 45 | 46 |

Be courteous. Please don't use profanity, attack other users, or 47 | stray off the topic - try to keep the focus on the game. See our 48 | Code of Conduct for more details. 49 | 50 |

Keep the length reasonable. We recommend in the neighborhood 51 | of two or three paragraphs: long enough to give a detailed review, but 52 | respectful of the reader's time. That said, it's fine to go longer, 53 | even much longer, if you have a lot to say about a game. We just 54 | don't want you to feel like you have to fill up pages for a homework 55 | assignment. 56 | 57 | 60 | -------------------------------------------------------------------------------- /www/help-review-votes: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Review Voting Options

9 | 10 |

These options let you "review the reviewer," to improve your own 11 | experience with IFDB and help guide other members to the best reviews. 12 | 13 |

Helpful (Yes/No): Vote Yes if you think the review does a 14 | good job of sharing the reviewer's opinion of the game - it's 15 | respectful, informative, well-written, insightful, or just generally 16 | helpful. Voting Yes doesn't mean that you agree with 17 | everything the reviewer said, or even with her overall opinion of the 18 | game. Reasons to vote No would include being off-topic, 19 | disrespectful, or overly spoilery. As votes accumulate, the reviews 20 | that members collectively deem to be the best (however they choose to 21 | define "best") will move to the top of the list, and less helpful 22 | reviews will move to the bottom. 23 | 24 |

Promote/demote this user: These affect how you (and only 25 | you) see this user's reviews throughout IFDB. If you Promote the 26 | user, you'll see her reviews listed first when viewing a game; if you 27 | Demote her, her reviews will move to the end of the list. 28 | (You can view and change your promotions and demotions via the 29 | user filter 30 | editor.) 31 | 32 |

Mute this user: 33 | This is the nuclear option, for someone you 34 | find so annoying that you never again want to see anything they write. 35 | If you mute a user, all of her reviews will be omitted when 36 | you view game pages throughout IFDB. This only affects you - it won't 37 | actually delete the user's reviews or hide them when other members 38 | view the same game pages. (You can change your mind and un-mute 39 | someone later via the user filter editor.) 40 | 41 |

Flag spoilers: Mark this review as containing 42 | unhidden spoilers. A spoiler gives away a secret that you're 43 | meant to discover in the course of playing the game, spoiling the fun 44 | of finding it or figuring it out on your own. After you flag the 45 | review, IFDB will put a warning label on the review when other users 46 | view it. This affects all users, so use this command 47 | responsibly. Don't flag a review for spoilers that are already 48 | properly hidden with "click to see" warnings - only flag it if the 49 | spoilers are out in the open. 50 | 51 |

Flag as inappropriate: Notify the moderators that this 52 | review may violate the 53 | Code of Conduct or the 54 | Terms of Service. 55 | 56 | 70 | 71 | 74 | -------------------------------------------------------------------------------- /www/help-spamcloaking: -------------------------------------------------------------------------------- 1 | 7 | 8 |

E-Mail Spam Cloaking

9 | 10 |

Before sending email to another member, you should check their 11 | profile address to make sure it's not "cloaked." Some people 12 | intentionally obscure their email addresses to escape the notice of 13 | spam robots, which constantly scan the web looking for fresh victims. 14 | Look for these signs of a cloaked address: 15 | 16 |

    17 | 18 |
  • SPAM or NOSPAM embedded in the address. For 19 | example, someone might write johnNOSPAMdoe@myisp.com - 20 | you'd just remove the word NOSPAM from the middle of the address, 21 | and what's left over is the actual address. 22 | 23 |
  • Other ALL-CAPITAL words besides SPAM or NOSPAM can be 24 | used the same way. 25 | 26 |
  • Spelled-out punctuation usually needs to be changed to 27 | the actual punctuation marks. For instance, john hyphen doe at 28 | myisp dot com would have to be changed to john-doe@myisp.com. 29 | 30 |
31 | 32 |

Note that this list isn't exhaustive - if it were, the spammers 33 | would just program their robots with everything on the list, defeating 34 | the entire purpose! The idea is that everyone does something a little 35 | different, choosing a scheme that's self-explanatory to real humans, 36 | but inscrutable to robots. So use your real-human common sense, and 37 | look at each address to see if it looks like it's cloaked in some way. 38 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /www/help-tags: -------------------------------------------------------------------------------- 1 | 7 | 8 |

What's a tag?

9 | 10 |

A tag is a search keyword that you can associate with a game. 11 | It can be any word or phrase that's relevant to the game. The 12 | most helpful tags describe things like the game's subject matter, 13 | style, tone - the kinds of things you might want to search for 14 | when looking for a game to play. 15 | 16 |

Whenever you search for a game, the results will include any games 17 | tagged with one or more of the words you're searching for. 18 | 19 |

Tags are helpful because they let you tell the search engine what 20 | concepts and subjects are related to the game. This makes searching 21 | easier by letting you find games that are conceptually similar to what 22 | you're looking for, rather than searching strictly by the literal 23 | title and author. 24 | 25 |

Tags are also helpful for finding other games related to the ones 26 | you like. People are likely to use the same tags for closely related 27 | games, so if you start with a game you like, you can look at its tags 28 | and search for other games with the same tags. 29 | 30 | 33 | -------------------------------------------------------------------------------- /www/help-top-rev: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Who Qualifies for Top Reviewer Status?

9 | 10 |

The top reviewer list is determined by who writes the most 11 | reviews - and the most helpful reviews, as judged by other IFDB 12 | members. 13 | 14 |

Top Reviewer status is a ranking of who has the most Frequent Fiction points. The Top 10 Reviewers are 16 | the ten members with the most points, and likewise for the Top 25, Top 17 | 50, and Top 100. 18 | 19 |

IFDB is a community enterprise. There's no elite group of 20 | appointed experts who get to decide which games are good and which 21 | aren't. Everyone can contribute, and Frequent Fiction points are 22 | meant to provide some small recognition for that. 23 | 24 |

Note that Top Reviewer status is dynamic, because 25 | the status is always relative to other members. You and others can 26 | earn more points any time. The site calculates your 27 | points and keeps them up to date automatically, so you don't have to 28 | do anything special to make sure your points are counted. 29 | 30 | 33 | -------------------------------------------------------------------------------- /www/help-tuid: -------------------------------------------------------------------------------- 1 | 7 | 8 |

What's a TUID?

9 | 10 |

A TUID is a "tads.org Unique Identifier" - a serial number that 11 | uniquely identifies a game listing or member profile in our database. 12 | A TUID has no meaning outside of our database, but we show them in the 13 | game listing because you can use them to write permanent, stable 14 | references to listings in our database. The database is designed so 15 | that once a TUID is assigned to a game, that identifier will stick 16 | with the game for as long as it's in our database. 17 | 18 |

You can find the TUID for a game on its home page, in the 19 | Details section. 20 | 21 |

The format of a TUID is a mix of random letters and numbers, 16 22 | characters long. We could have just used a simple sequential 23 | number as the ID (this is how Baf's 24 | Guide works), but we decided to use a long string of gibberish 25 | instead for a reason: these sorts of identifiers easily stand out from 26 | the crowd in full-text searches. 27 | 28 | 31 | -------------------------------------------------------------------------------- /www/help-zip-primary: -------------------------------------------------------------------------------- 1 | 11 | 12 |

Game file in a ZIP

13 | 14 |

For a compressed game file, it's helpful for IFDB to know the 15 | name of the main game file within the ZIP file. This lets 16 | download tools automatically extract the file and run the game. 17 | 18 |

For example, if you're uploading a ZIP file that contains a Z-code 19 | ".z5" story file, along with some other supporting files 20 | (documentation, for example), you'd enter the name of the .z5 file. 21 | 22 |

Use Unix-style relative paths if necessary - ZIP always uses "/" to 23 | separate directory names, even on operating systems with different 24 | path separators. For example, if you're linking to a ZIP file that 25 | contains a folder called Games and a Z-Machine file called MyGame.z5 26 | within the folder, you'd enter "Games/MyGame.z5". 27 | 28 | 31 | -------------------------------------------------------------------------------- /www/ifarchive-pending: -------------------------------------------------------------------------------- 1 | 5 | 6 |

File Pending Review

7 | 8 | This file has been uploaded to the IF 9 | Archive and is still pending review. Please allow a couple of days 10 | for the Archive to review the file. If it's been more than a week or 11 | so and this file is still showing as "pending", you might want to contact 12 | the Archive maintainers to check on the file's status. 13 | 14 |

This link will be automatically updated to point to the file's 15 | final location on the Archive when the review has been completed. 16 | 17 |

If necessary, you can manually edit the game's listing to correct this 18 | link by clicking "Edit this page" at the bottom of the game's main page. 19 | 20 | 23 | -------------------------------------------------------------------------------- /www/iframe: -------------------------------------------------------------------------------- 1 | " 10 | . "" 11 | . "\n" 15 | . "" 16 | . "