Compare commits

..

4 Commits

@ -1,6 +1,6 @@
# The Bad Space # The Bad Space
A searcable catalog of the worst places on the web. A searchable catalog of the worst places on the web.
More features incoming More features incoming

@ -2,60 +2,34 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Location;
use App\Models\Source;
class ExportController extends Controller class ExportController extends Controller
{ {
public function exportIndex()
{
return view('front.exports', [
'title' => "Exports"
]);
}
// //
public function exportCSV($type, $percent) public function exportCSV()
{ {
$columns = []; /*
$list = [];
$locations = Location::where("active", true)->get();
$sources = Source::where("active", true)->get();
if ($type == 'mastodon') {
$columns = [ $columns = [
'domain', 'id',
'severity', 'product_name',
'public_comment', 'product_url',
'reject_media', 'price',
'reject_reports', 'category'
'obfuscate',
]; ];
};
foreach ($locations as $location) { $products = [
$total = $location->block_count + $location->silence_count; [1, 'product 1', 'https://example.com/product-1', '9.99', 'category 1'],
if ($total >= 2) { [2, 'product 2', 'https://example.com/product-2', '19.99', 'category 2'],
$rate = $total / count($sources); [3, 'product 3', 'https://example.com/product-3', '29.99', 'category 3'],
if ($rate * 100 >= $percent) { [4, 'product 4', 'https://example.com/product-4', '39.99', 'category 4'],
if ($type == 'mastodon') { ];
//comman break teh CSV so just take them out
$comments = str_replace(",", ";", $location->description);
//remove extra white space
$comments = str_replace(["\n\r", "\n", "\r"], " ", $comments);
//add to the export list
array_push($list, [$location->url, $location->rating, $comments, "FALSE", "FALSE", "FALSE"]);
}
}
}
}
header('Content-Type: text/csv'); header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=' . $type . "-" . $percent); header('Content-Disposition: attachment; filename="products.csv"');
echo implode(',', $columns) . PHP_EOL; echo implode(',', $columns) . PHP_EOL;
foreach ($list as $item) { foreach ($products as $product) {
echo implode(',', $item) . PHP_EOL; echo implode(',', $product) . PHP_EOL;
} }
*/
} }
} }

@ -34,19 +34,12 @@ class FrontIndexController extends Controller
$rawSearch = $terms; $rawSearch = $terms;
$terms = str_replace(",", "", $terms); $terms = str_replace(",", "", $terms);
$terms = str_replace(" ", "|", $terms); $terms = str_replace(" ", "|", $terms);
$raw = DB::select("SELECT * FROM searchlocations(?)", [$terms]); $results = DB::select("SELECT * FROM searchlocations('$terms')");
$results = [];
foreach ($raw as $item) {
if (($item->block_count + $item->silence_count) > 2) {
array_push($results, $item);
}
}
$locations = Location::where("active", true)->get(); $locations = Location::where("active", true)->get();
$count = count($locations); $count = count($locations);
$recent = Location::where("active", true) $recent = Location::where("active", true)
->where('block_count', '>', 2) ->where('block_count', '>', 2)
->where('silence_count', '>', 2)
->limit(10)->orderByDesc('updated_at')->get(); ->limit(10)->orderByDesc('updated_at')->get();
return view('front.index', [ return view('front.index', [
@ -69,7 +62,6 @@ class FrontIndexController extends Controller
public function location(string $uuid = "1") public function location(string $uuid = "1")
{ {
$location = Location::where("uuid", $uuid)->first(); $location = Location::where("uuid", $uuid)->first();
$sources = Source::where("active", true)->get();
$name = "NO LOCATION FOUND"; $name = "NO LOCATION FOUND";
if ($location) { if ($location) {
$name = $location->name; $name = $location->name;
@ -77,8 +69,6 @@ class FrontIndexController extends Controller
return view('front.location', [ return view('front.location', [
'title' => str_replace(".", " ", $name), 'title' => str_replace(".", " ", $name),
'location' => $location, 'location' => $location,
'actions' => $location->block_count + $location->silence_count,
'sources_count' => count($sources),
'images' => json_decode($location->images), 'images' => json_decode($location->images),
'updated' => $location->updated_at->format('Y M d'), 'updated' => $location->updated_at->format('Y M d'),
]); ]);

@ -6,10 +6,17 @@ use Illuminate\Http\Request;
use App\Models\Location; use App\Models\Location;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use League\Csv\Reader;
use App\Models\Source; use App\Models\Source;
class LocationController extends Controller class LocationController extends Controller
{ {
//url to oli's unified tier 3 list
private $three = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier3_blocklist.csv';
//url to oli's domain audit containin block counts per domain
private $defed = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/other/domain_audit_file.csv';
public function addLocation(Request $request) public function addLocation(Request $request)
{ {
$fields = $request->validate([ $fields = $request->validate([
@ -38,22 +45,31 @@ class LocationController extends Controller
return back()->with('message', 'New Location Added. Take a break!'); return back()->with('message', 'New Location Added. Take a break!');
} else { } else {
return back()->withErrors([ return back()->withErrors([
'error' => 'Uh oh. There was an inssue', 'error' => 'Uh oh. There was an issue.',
]); ]);
} }
} else { } else {
return back()->withErrors([ return back()->withErrors([
'error' => 'All fields are required', 'error' => 'All fields are required.',
]); ]);
} }
} }
public function updateLocations() public function updateLocations()
{ {
//$fresh = file($this->three);
//$deny = Reader::createFromPath($fresh, "r");
//$deny->setHeaderOffset(0);
//$list = $deny->getRecords();
//$recordCount = count($fresh);
$duplicates = 0; $duplicates = 0;
$fresh = 0; $fresh = 0;
// ['url' => "rage.love"],
//['url' => "indiepocalypse.social"],
$unified = []; $unified = [];
//$denycount = array_map('str_getcsv', file($this->defed));
//$denylist = array_map('str_getcsv', file($this->three));
$sources = Source::where("active", true)->get(); $sources = Source::where("active", true)->get();
foreach ($sources as $source) { foreach ($sources as $source) {
//parsing for mastodon //parsing for mastodon
@ -72,26 +88,14 @@ class LocationController extends Controller
$index = array_search($item['domain'], array_column($unified, 'url')); $index = array_search($item['domain'], array_column($unified, 'url'));
if ($index) { if ($index) {
//if there is a match, update the count //if there is a match, update the count
if ($item['severity'] == "suspend" || $item['severity'] == "defederate") { ++$unified[$index]['count'];
++$unified[$index]['block_count'];
} else {
++$unified[$index]['silence_count'];
}
} else { } else {
$silence = 0;
$suspend = 0;
if ($item['severity'] == "suspend" || $item['severity'] == "defederate") {
++$silence;
} else {
++$suspend;
}
array_push($unified, [ array_push($unified, [
'name' => $item['domain'], 'name' => $item['domain'],
'url' => $item['domain'], 'url' => $item['domain'],
'rating' => $item['severity'], 'rating' => $item['severity'],
'comment' => $item['comment'], 'comment' => $item['comment'],
'block_count' => $suspend, 'count' => 1,
'silence_count' => $silence,
]); ]);
} }
} }
@ -103,56 +107,20 @@ class LocationController extends Controller
$index = array_search($item[0], array_column($unified, 'url')); $index = array_search($item[0], array_column($unified, 'url'));
if ($index) { if ($index) {
//if there is a match, update the count //if there is a match, update the count
if ($item[1] == "suspend" || $item['severity'] == "defederate") { ++$unified[$index]['count'];
++$unified[$index]['block_count'];
} else {
++$unified[$index]['silence_count'];
}
} else {
$silence = 0;
$suspend = 0;
if ($item[1] == "suspend" || $item[1] == "defederate") {
++$silence;
} else { } else {
++$suspend;
}
array_push($unified, [ array_push($unified, [
'name' => $item[0], 'name' => $item[0],
'url' => $item[0], 'url' => $item[0],
'rating' => $item[1], 'rating' => $item[1],
'comment' => $item[2], 'comment' => $item[2],
'block_count' => $suspend, 'count' => 1,
'silence_count' => $silence,
]); ]);
} }
} }
} }
} }
//get all locations and sort which are present in unified or not
/*
$sorted = [];
$listed = 0;
$notlisted = 0;
foreach (Location::all() as $location) {
if (array_search($location->url, array_column($unified, 'url'))) {
++$listed;
// locations present in unfied, so updated
array_push($sorted, [
'location' => $location,
'listed' => true
]);
} else {
++$notlisted;
//locations not present
array_push($sorted, [
'location' => $location,
'listed' => false
]);
}
};
*/
//once the unified list is created, update current entries or create fresh ones //once the unified list is created, update current entries or create fresh ones
foreach ($unified as $item) { foreach ($unified as $item) {
@ -161,8 +129,7 @@ class LocationController extends Controller
++$duplicates; ++$duplicates;
//update block count for existing item //update block count for existing item
$location->block_count = $item['block_count']; $location->block_count = $item['count'];
$location->silence_count = $item['silence_count'];
//replace null with empty array //replace null with empty array
if ($location->images == null) { if ($location->images == null) {
@ -173,23 +140,41 @@ class LocationController extends Controller
// make new entries for instances not present // make new entries for instances not present
++$fresh; ++$fresh;
$images = []; $images = [];
$rating = ($item['rating'] == 'defederate') ? 'suspend' : $item['rating'];
$new = Location::create([ $new = Location::create([
'uuid' => Uuid::uuid4(), 'uuid' => Uuid::uuid4(),
'name' => $item['url'], 'name' => $item['url'],
'url' => $item['url'], 'url' => $item['url'],
'description' => ($item['comment'] != null) ? $item['comment'] : "no description", 'description' => ($item['comment'] != null) ? $item['comment'] : "No description provided.",
'active' => true, 'active' => true,
'rating' => $rating, 'rating' => $item['rating'],
'added_by' => 1, 'added_by' => 1,
'tags' => 'poor moderation, hate speech', 'tags' => 'poor moderation, hate speech',
'images' => json_encode($images), 'images' => json_encode($images),
'block_count' => $item['block_count'], 'block_count' => $item['count'],
'silence_count' => $item['silence_count'],
]); ]);
} }
} }
//$lookfor = '0sint.social';
//$index = array_search($lookfor, array_column($unified, 'url'));
//return back()->with('message', 'TOTAL: ' . count($unified) . " - " . $unified[$index]['count'] . " COUNT");
return back()->with('message', $duplicates . ' UPDATED - ' . $fresh . ' CREATED'); return back()->with('message', $duplicates . ' UPDATED - ' . $fresh . ' CREATED');
//$domain = $csv[1000][0];
//$record = null;
/*
foreach ($blockcount as $line) {
if ($line[0] == $domain) {
$record = $line;
}
}
if ($record != null) {
return back()->with('message', $domain . ' has ' . $record[1] . ' blocks.');
} else {
return back()->with('message', 'NO MATCHES');
}
*/
} }
} }

@ -1,24 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class LocationCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
//return parent::toArray($request);
return [
'listingCount' => count($this->collection),
'locations' => LocationResource::collection($this->collection),
];
}
}

@ -1,26 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class LocationResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'url' => $this->url,
'name' => $this->name,
'description' => $this->description,
'rating' => $this->rating,
'count' => $this->block_count,
'link' => "/location/" . $this->uuid,
];
}
}

@ -4,12 +4,10 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Location extends Model class Location extends Model
{ {
use HasFactory; use HasFactory;
use SoftDeletes;
/** /**
* The table associated with the model. * The table associated with the model.
@ -31,7 +29,6 @@ class Location extends Model
"added_by", "added_by",
"tags", "tags",
"block_count", "block_count",
"silence_count",
"created_at", "created_at",
"updated_at" "updated_at"
]; ];

@ -37,7 +37,7 @@ section[role="listings"] div[role="paginate"] span {
a.list-link { a.list-link {
display: grid; display: grid;
grid-template-columns: 30px 50px 30px 50px 300px; grid-template-columns: 30px 50px 300px;
width: 80%; width: 80%;
height: 45px; height: 45px;
} }

@ -42,9 +42,6 @@
<a href="/listings/1" title="instance listing" class="nav-links"> <a href="/listings/1" title="instance listing" class="nav-links">
Listings Listings
</a><br /> </a><br />
<a href="/exports" title="list exports" class="nav-links">
Exports
</a><br />
@if(Auth::check()) @if(Auth::check())
<a href="/den" title="den-start" class="nav-links"> <a href="/den" title="den-start" class="nav-links">
Den Den

@ -4,62 +4,61 @@
@parent @parent
<section> <section>
<article> <article>
<h2 id="what">What is The Bad Space?</h2> <h2 id="what">What Is The Bad Space?</h2>
<p>The Bad Space project was born from a need to effectively identify instances that house bad actors and are poorly moderated, which puts marginalized communities at risk. <p>The Bad Space project was born from a need to effectively identify instances that house bad actors and are poorly moderated, which puts marginalized communities at risk.
</p> </p>
<p> <p>
It is an extension of the It is an extension of the
<strong>#fediblock</strong> <strong>#fediblock</strong>
hashtag created by hashtag created by
<a href="https://www.artistmarciax.com/">Artist Marcia X</a> <a href="https://www.artistmarciax.com/" target="_new">Artist Marcia X</a>,
with additional support from with additional support from
<a href="https://digital.rooting.garden">Ginger</a> <a href="https://digital.rooting.garden" target="_new">Ginger</a>,
to provide a catalog of instances that seek to cause harm and reduce the quality of experience in the fediverse. to provide a catalog of instances that seek to cause harm and reduce the quality of experience in the fediverse.
</p> </p>
<p> <p>
Technical support provided by Technical support provided by
<a href="https://roiskinda.cool/profile.html">Ro</a>. <a href="https://roiskinda.cool/profile.html" target="_new">Ro</a>.
</p> </p>
<h2 id="how">How does it work?</h2> <h2 id="how">How Does It Work?</h2>
<p>The Bad Space is a collaboration of instances committed to actively moderating against racism, sexism, heterosexism, transphobia, ableism, casteism, or religion.</p> <p>The Bad Space is a collaboration of instances committed to actively moderating against racism, sexism, heterosexism, transphobia, ableism, casteism, and religion.</p>
<p>These instances have permitted The Bad Space to read their respective blocklists to create a composite directory of sites tagged for the behavior above that can be searched and, through a public API, can be integrated into external services.</p> <p>These instances have permitted The Bad Space to read their respective blocklists to create a composite directory of sites tagged for the behavior above that can be searched and, through a public API, can be integrated into external services.</p>
<p><strong>Current Sources:</strong></p> <p><strong>Current Sources:</strong></p>
Maston:<br /> Mastodon:<br />
@foreach($sources as $source) @foreach($sources as $source)
@if($source->format == 'json') @if($source->format == 'json')
<a href="https://{{$source->url}}">{{$source->url}}</a><br /> <a href="https://{{$source->url}}">{{$source->url}}</a><br />
@endif @endif
@endforeach @endforeach<br />
Custom CSV<br /> Custom CSV:<br />
@foreach($sources as $source) @foreach($sources as $source)
@if($source->format == 'csv') @if($source->format == 'csv')
<a href="{{$source->url}}">{{$source->url}}</a><br /> <a href="{{$source->url}}">{{$source->url}}</a><br />
@endif @endif
@endforeach @endforeach
<h2>How do I use it?</h2> <h2>How Do I Use It?</h2>
<p> <p>
The Bad Space is meant to be a resource for anyone looking to improve the quality of their online experience by creating a tool that catalogs sources for harassment and abuse. There are several options for how it can be used. The Bad Space is meant to be a resource for anyone looking to improve the quality of their online experience by creating a tool that catalogs sources for harassment and abuse. There are several options for how it can be used.
<h3>Search</h3> <h3>Search</h3>
To see if a site is listed in the database, use the To see if an instance is listed in the database, use the
<a href="/">search feature</a> <a href="/">search feature</a>
to search for that URL. If it is in the database, information for that instance will be returned and associated instances if applicable. to search for its URL. If it is in the database, information for that instance will be returned and include associated instances, if applicable.
<h3>CSV Exports</h3> <h3>CSV Exports</h3>
For a list of the current locations being tracked, click on one of the links below to download a dynamically generated CSV file that can be consumed as a blocklist. More formats will be added over time. For a list of the current instances being tracked, click on one of the links below to download a dynamically generated CSV file that can be consumed as a blocklist. More formats will be added over time.
<br /> <br /><br />
<a href="/exports/mastodon">For Mastodon</a> <a href="/exports/mastodon">For Mastodon</a>
<h3>API</h3> <h3>API</h3>
The Bad Space has a public api that can be used to search the database programatically and return results in the JSON format. The API can be accsess at<br /> The Bad Space has a public API that can be used to search the database programatically and return results in the JSON format. The API can be accessed at<br />
<code>https://thebad.space/api/v1/search</code> <code>https://thebad.space/api/v1/search</code>
by posting a JSON object with the following format: by posting a JSON object with the following format:
<code>{"url":"search.url"}</code><br /> <code>{"url":"search.url"}</code><br /><br />
Data from API request will be returned in the follow format:<br /> Data from API requests will be returned in the following format:<br />
<pre> <pre>
<code>{ <code>{
data:{
"listingCount":1, "listingCount":1,
"locations": "locations":
[ [
@ -70,7 +69,6 @@
"link":"bad-space-instance-link" "link":"bad-space-instance-link"
} }
] ]
}
}</code> }</code>
</pre> </pre>
</p> </p>

@ -1,24 +0,0 @@
@extends('frame')
@section('title', 'The Bad Space|Exports')
@section('main-content')
@parent
<section>
<article>
<h2>CSV Exports</h2>
Heat Rating is the percentage of Current Sources that have taken action against an instance. The higher the number of Sources that have silenced and/or suspended an instance, the higher the Heat Rating.*
<h3>For Mastodon</h3>
<a href="/exports/mastodon/90">Heat Rating 90%</a><br />
<a href="/exports/mastodon/80">Heat Rating 80%</a><br />
<a href="/exports/mastodon/70">Heat Rating 70%</a><br />
<a href="/exports/mastodon/60">Heat Rating 60%</a><br />
<a href="/exports/mastodon/50">Heat Rating 50%</a><br />
<a href="/exports/mastodon/40">Heat Rating 40%</a><br />
<a href="/exports/mastodon/30">Heat Rating 30%</a><br />
<a href="/exports/mastodon/20">Heat Rating 20%</a><br />
<br />
<i>* Heating Ratings are still a work in progress so please review list before using.</i>
<br /><br />
</article>
</section>
@endsection

@ -32,9 +32,11 @@
@foreach($recent as $location) @foreach($recent as $location)
<a class="list-link" role="listitem" href="/location/{{$location->uuid}}"> <a class="list-link" role="listitem" href="/location/{{$location->uuid}}">
<span>{{$location->block_count}}</span> <span>{{$location->block_count}}</span>
<img class="menu-icon" src="/assets/images/global/status-suspend.svg" title="suspended" /> @if($location->rating == 'silence')
<span>{{$location->silence_count}}</span>
<img class="menu-icon" src="/assets/images/global/status-silence.svg" title="silenced" /> <img class="menu-icon" src="/assets/images/global/status-silence.svg" title="silenced" />
@else
<img class="menu-icon" src="/assets/images/global/status-suspend.svg" title="suspended" />
@endif
<label>{{$location->name}}</label> <label>{{$location->name}}</label>
</a> </a>
@endforeach @endforeach

@ -17,16 +17,18 @@
@endforeach @endforeach
@endif @endif
<br /> <br />
<strong>Total Actions:</strong> {{$actions}}/{{$sources_count}}<br /> @if($location->rating == 'silence')
<img class="rating-icon" src="/assets/images/global/status-silence.svg" title="silenced" />
<strong>Silenced Count: {{$location->block_count}}</strong>
@else
<img class="rating-icon" src="/assets/images/global/status-suspend.svg" title="suspended" /> <img class="rating-icon" src="/assets/images/global/status-suspend.svg" title="suspended" />
<strong>Suspended: {{$location->block_count}}</strong> <strong>Suspended Count: {{$location->block_count}}</strong>
<img class="rating-icon" src="/assets/images/global/status-silence.svg" title="silenced" /> @endif
<strong>Silenced: {{$location->silence_count}}</strong>
<br /><br /> <br /><br />
Total Actions represent the number of actions, silences or suspensions, that have been taken against an instance by <a href="/about#how">Current Sources</a> This count reflects the number of times this instance has been suspended or silenced by two or more <a href="/about#how">Current Sources</a>.
<br />UPDATED : {{$updated}} <br /><br />UPDATED: {{$updated}}
</article> </article>
</section> </section>
@endsection @endsection

@ -2,8 +2,6 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;
use App\Http\Resources\LocationCollection;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -19,13 +17,3 @@ use App\Http\Resources\LocationCollection;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user(); return $request->user();
}); });
// public search API
Route::post("/v1/search", function (Request $request) {
$data = json_decode($request->getContent());
$search = $data->url;
$search = str_replace(",", "", $search);
$search = str_replace(" ", "|", $search);
$results = DB::select("SELECT * FROM searchlocations('$search')");
return new LocationCollection($results);
});

@ -26,8 +26,7 @@ Route::get("/location/{uuid}", [FrontIndexController::class, 'location']);
Route::post("/search", [FrontIndexController::class, 'indexSearch']); Route::post("/search", [FrontIndexController::class, 'indexSearch']);
//exports //exports
Route::get("/exports", [ExportController::class, 'exportIndex']); Route::get("/exports/test", [ExportController::class, 'exportCSV']);
Route::get("/exports/{type}/{rate}", [ExportController::class, 'exportCSV']);
//auth //auth
Route::get("/login", [AuthController::class, 'showLogin']); Route::get("/login", [AuthController::class, 'showLogin']);

Loading…
Cancel
Save