Add uploader which can deal with errors
This commit is contained in:
parent
ff134ebfba
commit
5aa1413078
5 changed files with 131 additions and 62 deletions
|
@ -4,6 +4,11 @@ export interface MapCompleteUsageOverview {
|
||||||
showTopContributors?: boolean,
|
showTopContributors?: boolean,
|
||||||
showTopThemes?: boolean,
|
showTopThemes?: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a content warning to all posts
|
||||||
|
*/
|
||||||
|
contentWarning?: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Term to use in 'created/moved/deleted one point'
|
* Term to use in 'created/moved/deleted one point'
|
||||||
*/
|
*/
|
||||||
|
|
75
src/ImageUploader.ts
Normal file
75
src/ImageUploader.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import {ChangeSetData} from "./OsmCha";
|
||||||
|
import MastodonPoster from "./Mastodon";
|
||||||
|
import OsmUserInfo from "./OsmUserInfo";
|
||||||
|
import ImgurAttribution from "./ImgurAttribution";
|
||||||
|
import Utils from "./Utils";
|
||||||
|
import Config from "./Config";
|
||||||
|
|
||||||
|
export default class ImageUploader {
|
||||||
|
private readonly _imageQueue: { image: string; changeset: ChangeSetData }[];
|
||||||
|
private _poster: MastodonPoster;
|
||||||
|
private _authors: string[] = []
|
||||||
|
|
||||||
|
private readonly _globalConfig: Config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor(imageQueue: {image: string, changeset: ChangeSetData}[], poster: MastodonPoster, config: Config) {
|
||||||
|
this._imageQueue = imageQueue;
|
||||||
|
this._poster = poster;
|
||||||
|
this._globalConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentAuthors(){
|
||||||
|
return [...this._authors]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async attemptToUpload(targetcount: number): Promise<string[]>{
|
||||||
|
const mediaIds = []
|
||||||
|
while(mediaIds.length < targetcount && this._imageQueue.length >0){
|
||||||
|
const first = this._imageQueue[0]
|
||||||
|
try {
|
||||||
|
const id = await this.uploadFirstImage()
|
||||||
|
mediaIds.push(id)
|
||||||
|
}catch (e) {
|
||||||
|
console.error("Could not upload image! ", first.image, e)
|
||||||
|
console.log("Trying again")
|
||||||
|
try {
|
||||||
|
const id = await this.uploadFirstImage()
|
||||||
|
mediaIds.push(id)
|
||||||
|
}catch (e) {
|
||||||
|
console.error("Retry could not upload image! ", first.image, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mediaIds
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadFirstImage(): Promise<string>{
|
||||||
|
const image = this._imageQueue.shift()
|
||||||
|
const cs = image.changeset.properties
|
||||||
|
let authorName = cs.user
|
||||||
|
try {
|
||||||
|
const authorInfo = new OsmUserInfo(Number(cs.uid), this._globalConfig)
|
||||||
|
authorName = (await authorInfo.GetMastodonUsername(this._poster)) ?? cs.user
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Could not fetch more info about contributor", authorName, cs.uid, "due to", e)
|
||||||
|
}
|
||||||
|
if (this._globalConfig.mastodonAuth.dryrun) {
|
||||||
|
console.log("Not uploading/downloading image:" + image.image + " dryrun")
|
||||||
|
this._authors.push(authorName)
|
||||||
|
return "dummy_id"
|
||||||
|
}
|
||||||
|
console.log("Fetching attribution for", image.image)
|
||||||
|
const attribution = await ImgurAttribution.DownloadAttribution(image.image)
|
||||||
|
const id = image.image.substring(image.image.lastIndexOf("/") + 1)
|
||||||
|
const path = this._globalConfig.cacheDir + "/image_" + id
|
||||||
|
await Utils.DownloadBlob(image.image, path)
|
||||||
|
const mediaId = await this._poster.uploadImage(path, "Image taken by " + authorName + ", available under " + attribution.license + ". It is made with the thematic map " + image.changeset.properties.theme + " in changeset https://openstreetmap.org/changeset/" + image.changeset.id)
|
||||||
|
|
||||||
|
this._authors.push(authorName)
|
||||||
|
return mediaId
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ export default class MastodonPoster {
|
||||||
if (descrParts.indexOf("#nobot") >= 0 || descrParts.indexOf("#nomapcompletebot") >= 0) {
|
if (descrParts.indexOf("#nobot") >= 0 || descrParts.indexOf("#nomapcompletebot") >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const nobot = info.fields.find(f => f.name === "nobot").value
|
const nobot = info.fields.find(f => f.name === "nobot")?.value ?? ""
|
||||||
if (nobot.toLowerCase() === "yes" || nobot.toLowerCase() === "true") {
|
if (nobot.toLowerCase() === "yes" || nobot.toLowerCase() === "true") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default class OsmUserInfo {
|
||||||
private _userData: UserInfo = undefined
|
private _userData: UserInfo = undefined
|
||||||
private readonly _cachingPath: string | undefined;
|
private readonly _cachingPath: string | undefined;
|
||||||
|
|
||||||
constructor(userId: number, options?:
|
constructor(userId: number, options:
|
||||||
{
|
{
|
||||||
osmBackend?: string,
|
osmBackend?: string,
|
||||||
cacheDir?: string
|
cacheDir?: string
|
||||||
|
|
|
@ -4,8 +4,8 @@ import {ChangeSetData} from "./OsmCha";
|
||||||
import OsmUserInfo from "./OsmUserInfo";
|
import OsmUserInfo from "./OsmUserInfo";
|
||||||
import Config, {MapCompleteUsageOverview} from "./Config";
|
import Config, {MapCompleteUsageOverview} from "./Config";
|
||||||
import MastodonPoster from "./Mastodon";
|
import MastodonPoster from "./Mastodon";
|
||||||
import ImgurAttribution from "./ImgurAttribution";
|
|
||||||
import Overpass from "./Overpass";
|
import Overpass from "./Overpass";
|
||||||
|
import ImageUploader from "./ImageUploader";
|
||||||
|
|
||||||
type ImageInfo = { image: string, changeset: ChangeSetData }
|
type ImageInfo = { image: string, changeset: ChangeSetData }
|
||||||
|
|
||||||
|
@ -35,13 +35,15 @@ export class Postbuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getStatisticsFor(changesetsMade?: ChangeSetData[]): { total: number,
|
getStatisticsFor(changesetsMade?: ChangeSetData[]): {
|
||||||
|
total: number,
|
||||||
answered?: number,
|
answered?: number,
|
||||||
created?: number,
|
created?: number,
|
||||||
addImage?: number,
|
addImage?: number,
|
||||||
deleted: number,
|
deleted: number,
|
||||||
moved?: number,
|
moved?: number,
|
||||||
summaryText?: string } {
|
summaryText?: string
|
||||||
|
} {
|
||||||
|
|
||||||
const stats: Record<string, number> = {}
|
const stats: Record<string, number> = {}
|
||||||
changesetsMade ??= this._changesetsMade
|
changesetsMade ??= this._changesetsMade
|
||||||
|
@ -162,11 +164,15 @@ export class Postbuilder {
|
||||||
return `${contribCountStr} ${statistics.summaryText} on https://mapcomplete.osm.be/${theme}`
|
return `${contribCountStr} ${statistics.summaryText} on https://mapcomplete.osm.be/${theme}`
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectImages(images: ImageInfo[], targetCount: number = 4):
|
/**
|
||||||
|
* Creates a new list of images, sorted by priority.
|
||||||
|
* It tries to order them in such a way that the number of contributors is as big as possible.
|
||||||
|
* However, it is biased to select pictures from certain themes too
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public selectImages(images: ImageInfo[]):
|
||||||
ImageInfo[] {
|
ImageInfo[] {
|
||||||
if (images.length <= targetCount) {
|
|
||||||
return images
|
|
||||||
}
|
|
||||||
const themeBonus = {
|
const themeBonus = {
|
||||||
climbing: 1,
|
climbing: 1,
|
||||||
rainbow_crossings: 1,
|
rainbow_crossings: 1,
|
||||||
|
@ -184,7 +190,7 @@ export class Postbuilder {
|
||||||
const alreadyEncounteredUid = new Map<string, number>()
|
const alreadyEncounteredUid = new Map<string, number>()
|
||||||
|
|
||||||
const result: ImageInfo[] = []
|
const result: ImageInfo[] = []
|
||||||
for (let i = 0; i < targetCount; i++) {
|
for (let i = 0; i < images.length; i++) {
|
||||||
let bestImageScore: number = -999999999
|
let bestImageScore: number = -999999999
|
||||||
let bestImageOptions: ImageInfo[] = []
|
let bestImageOptions: ImageInfo[] = []
|
||||||
|
|
||||||
|
@ -235,7 +241,8 @@ export class Postbuilder {
|
||||||
const total = data.elements.length
|
const total = data.elements.length
|
||||||
const date = data.osm3s.timestamp_osm_base.substring(0, 10)
|
const date = data.osm3s.timestamp_osm_base.substring(0, 10)
|
||||||
lastPostId = (await this._poster.writeMessage(
|
lastPostId = (await this._poster.writeMessage(
|
||||||
report.post.replace(/{total}/g, ""+total).replace(/{date}/g, date)
|
report.post.replace(/{total}/g, "" + total).replace(/{date}/g, date),
|
||||||
|
{spoilerText: this._config.contentWarning}
|
||||||
)).id
|
)).id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,10 +263,10 @@ export class Postbuilder {
|
||||||
const totalStats = this.getStatisticsFor()
|
const totalStats = this.getStatisticsFor()
|
||||||
const {
|
const {
|
||||||
totalImagesCreated,
|
totalImagesCreated,
|
||||||
attachmentIds,
|
randomImages,
|
||||||
imgAuthors,
|
|
||||||
totalImageContributorCount
|
totalImageContributorCount
|
||||||
} = await this.prepareImages(changesets, 12)
|
} = await this.prepareImages(changesets)
|
||||||
|
const imageUploader = new ImageUploader(randomImages, this._poster, this._globalConfig)
|
||||||
|
|
||||||
let timePeriod = "Yesterday"
|
let timePeriod = "Yesterday"
|
||||||
if (this._config.numberOfDays > 1) {
|
if (this._config.numberOfDays > 1) {
|
||||||
|
@ -276,7 +283,7 @@ export class Postbuilder {
|
||||||
const uid = topContributor.key
|
const uid = topContributor.key
|
||||||
const changesetsMade = perContributor.get(uid)
|
const changesetsMade = perContributor.get(uid)
|
||||||
try {
|
try {
|
||||||
const userInfo = new OsmUserInfo(Number(uid))
|
const userInfo = new OsmUserInfo(Number(uid), this._globalConfig)
|
||||||
const {nobot} = await userInfo.hasNoBotTag()
|
const {nobot} = await userInfo.hasNoBotTag()
|
||||||
if (nobot) {
|
if (nobot) {
|
||||||
continue
|
continue
|
||||||
|
@ -290,7 +297,11 @@ export class Postbuilder {
|
||||||
console.error("Could not add contributor " + uid, e)
|
console.error("Could not add contributor " + uid, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastPostId = (await this._poster.writeMessage(toSend.join("\n"), {mediaIds: attachmentIds.slice(0, 4)})).id
|
lastPostId = (await this._poster.writeMessage(toSend.join("\n"),
|
||||||
|
{
|
||||||
|
mediaIds: await imageUploader.attemptToUpload(4),
|
||||||
|
spoilerText: this._config.contentWarning
|
||||||
|
})).id
|
||||||
toSend = []
|
toSend = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,30 +327,33 @@ export class Postbuilder {
|
||||||
|
|
||||||
lastPostId = (await this._poster.writeMessage(toSend.join("\n"), {
|
lastPostId = (await this._poster.writeMessage(toSend.join("\n"), {
|
||||||
inReplyToId: lastPostId,
|
inReplyToId: lastPostId,
|
||||||
mediaIds: attachmentIds.slice(4, 8)
|
mediaIds: await imageUploader.attemptToUpload(4),
|
||||||
|
spoilerText: this._config.contentWarning
|
||||||
})).id
|
})).id
|
||||||
toSend = []
|
toSend = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const authorNames = Array.from(new Set<string>(imgAuthors))
|
const images = await imageUploader.attemptToUpload(4)
|
||||||
|
const authors = Array.from(new Set(imageUploader.getCurrentAuthors()))
|
||||||
await this._poster.writeMessage([
|
await this._poster.writeMessage([
|
||||||
"In total, " + totalImageContributorCount + " different contributors uploaded " + totalImagesCreated + " images.\n",
|
"In total, " + totalImageContributorCount + " different contributors uploaded " + totalImagesCreated + " images.\n",
|
||||||
"Images in this thread are randomly selected from them and were made by: ",
|
"Images in this thread are randomly selected from them and were made by: ",
|
||||||
...authorNames.map(auth => "- " + auth),
|
...authors.map(auth => "- " + auth),
|
||||||
"",
|
"",
|
||||||
"All changes were made on " + date + (this._config.numberOfDays > 1 ? ` or at most ${this._config.numberOfDays} days before` : "")
|
"All changes were made on " + date + (this._config.numberOfDays > 1 ? ` or at most ${this._config.numberOfDays} days before` : "")
|
||||||
|
|
||||||
].join("\n"), {
|
].join("\n"), {
|
||||||
inReplyToId: lastPostId,
|
inReplyToId: lastPostId,
|
||||||
mediaIds: attachmentIds.slice(8, 12)
|
mediaIds: images,
|
||||||
|
spoilerText: this._config.contentWarning
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async prepareImages(changesets: ChangeSetData[], targetCount: number = 4): Promise<{ imgAuthors: string[], attachmentIds: string[], totalImagesCreated: number, totalImageContributorCount: number }> {
|
private async prepareImages(changesets: ChangeSetData[]): Promise<{ randomImages: { image: string, changeset: ChangeSetData }[], totalImagesCreated: number, totalImageContributorCount: number }> {
|
||||||
const withImage: ChangeSetData[] = changesets.filter(cs => cs.properties["add-image"] > 0)
|
const withImage: ChangeSetData[] = changesets.filter(cs => cs.properties["add-image"] > 0)
|
||||||
const totalImagesCreated = Utils.Sum(withImage.map(cs => cs.properties["add-image"]))
|
const totalImagesCreated = Utils.Sum(withImage.map(cs => cs.properties["add-image"]))
|
||||||
|
|
||||||
|
@ -347,7 +361,7 @@ export class Postbuilder {
|
||||||
const seenURLS = new Set<string>()
|
const seenURLS = new Set<string>()
|
||||||
for (const changeset of withImage) {
|
for (const changeset of withImage) {
|
||||||
|
|
||||||
const userinfo = new OsmUserInfo(Number(changeset.properties.uid))
|
const userinfo = new OsmUserInfo(Number(changeset.properties.uid), this._globalConfig)
|
||||||
const {nobot} = await userinfo.hasNoBotTag()
|
const {nobot} = await userinfo.hasNoBotTag()
|
||||||
if (nobot) {
|
if (nobot) {
|
||||||
console.log("Not indexing images of user", changeset.properties.user)
|
console.log("Not indexing images of user", changeset.properties.user)
|
||||||
|
@ -369,35 +383,10 @@ export class Postbuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomImages: ImageInfo[] = this.selectImages(images, targetCount)
|
const randomImages: ImageInfo[] = this.selectImages(images)
|
||||||
const attachmentIds: string[] = []
|
|
||||||
const imgAuthors: string[] = []
|
|
||||||
for (const randomImage of randomImages) {
|
|
||||||
|
|
||||||
const cs = randomImage.changeset.properties
|
|
||||||
let authorName = cs.user
|
|
||||||
try {
|
|
||||||
const authorInfo = new OsmUserInfo(Number(cs.uid), this._globalConfig)
|
|
||||||
authorName = (await authorInfo.GetMastodonUsername(this._poster)) ?? cs.user
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Could not fetch more info about contributor", authorName, cs.uid, "due to", e)
|
|
||||||
}
|
|
||||||
imgAuthors.push(authorName)
|
|
||||||
if (this._globalConfig.mastodonAuth.dryrun) {
|
|
||||||
console.log("Not uploading/downloading image:" + randomImage.image + " dryrun")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const attribution = await ImgurAttribution.DownloadAttribution(randomImage.image)
|
|
||||||
const id = randomImage.image.substring(randomImage.image.lastIndexOf("/") + 1)
|
|
||||||
const path = this._globalConfig.cacheDir + "/image_" + id
|
|
||||||
await Utils.DownloadBlob(randomImage.image, path)
|
|
||||||
const mediaId = await this._poster.uploadImage(path, "Image taken by " + authorName + ", available under " + attribution.license + ". It is made with the thematic map " + randomImage.changeset.properties.theme + " in changeset https://openstreetmap.org/changeset/" + randomImage.changeset.id)
|
|
||||||
attachmentIds.push(mediaId)
|
|
||||||
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
attachmentIds,
|
randomImages,
|
||||||
imgAuthors,
|
|
||||||
totalImagesCreated,
|
totalImagesCreated,
|
||||||
totalImageContributorCount: new Set(withImage.map(cs => cs.properties.uid)).size
|
totalImageContributorCount: new Set(withImage.map(cs => cs.properties.uid)).size
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue