Neulich hatten wir mal darüber diskutiert, wie wir mehr Aktivität bei Spielregeländerungen erzeugen können. Ich habe vorgeschlagen, man könnte doch einfach alle aktiven Benutzer erwähnen, um sie auf die Abstimmung aufmerksam zu machen.
Natürlich ist das manuell einiges an Aufwand, weswegen ich wie angekündigt ein kleines Python-Skript geschrieben habe.
Dafür müsst ihr euch natürlich einerseits Python installieren, andererseits zwei Python-Packages (beautifulsoup4 und requests).
Python könnt ihr von der offiziellen Webseite herunterladen.
Die Packages könnt ihr über den Befehl "python -m pip install beautifulsoup4 requests" installieren, dazu müsst ihr auf Windows die "Eingabeaufforderung" öffnen (dazu: Windows-Taste drücken und "cmd" eintippen).
Ihr müsst untenstehenden Code einfach in eine Datei einzufügen. Ganz wichtig ist die Endung ".py"
Es reicht dann, die Datei einfach mit einem Doppelklick zu öffnen. Dann einfach den Anweisungen (auf Englisch, wegen Konsistenz ok) folgen und ihr bekommt einen schönen Spoiler zum Kopieren.
Denn dann einfach über den Quellcodemodus im Forum-Editor einfügen und ihr habt die Chance, ziemlich viele Leute zu nerven.
Ihr könnt grundsätzlich zwischen einigen Einstellungen wählen.
1. Die Anzahl der Tage, die ihr anschauen wollt (Standard: letzte 14 Tage)
2. Die Foren, die das Skript untersuchen soll (Foren können nur öffentliche Foren sein, also z.B. nicht Parteiforen)
- Standardauswahl sind 3 Foren (Vereinshaus, Bürgerversammlung, Konferenz- und Medienzentrum, da dies Foren sind, die oftmals sehr aktiv sind, insbesondere das Preuß. Im Vergleich zu allen Foren ist das etwa 4 vier mal so schneller und hat immer noch 80% der Benutzer)
- Ihr könnt auch alle Foren auswählen
- Man kann auch eine Einzelauswahl der Foren über ihre IDs treffen; die sind Teil der Links auf die Foren (Bürgerversammlung hat zum Beispiel die ID 12). Diese Foren müssen aber irgendwelche Threads haben!
Genießt es dann einfach und wartet auf das Ergebnis.
Den Spoiler könnt ihr dann einfach ganz normal kopieren (markieren + Strg + C).
Oh, und abschließend möchte ich noch selbstverständlich - was ja sowieso klar ist - darauf hinweißen, das nicht zu missbrauchen.
- from bs4 import BeautifulSoup
- import requests
- import time
- import traceback
- import sys
- from datetime import timedelta, datetime
- # Settings
- basicUrl = 'https://forum.politik-sim.de/forum'
- days = 14
- periodUnix = 0
- # For the sake of counting numbers, without any purpose
- requestCounter = 0
- # Helper functions
- def exitEnter():
- print("Press enter to close")
- enter: str = None
- while enter is None:
- enter = input()
- # Exception hook
- def onException(exc_type, exc_value, tb):
- print("\nAn error occured:\n")
- traceback.print_exception(exc_type, exc_value, tb)
- print("\n")
- exitEnter()
- sys.exit(-1)
- sys.excepthook = onException
- def getPeriodUnix(days: int) -> int:
- return int(time.mktime((datetime.now() - timedelta(days=days)).timetuple()))
- def getHtml(url: str, toPrint: str) -> BeautifulSoup:
- global requestCounter
- requestCounter += 1
- print(f"{toPrint} (Request #{requestCounter})")
- return BeautifulSoup(requests.get(url).text, 'html.parser')
- def getBoardUrl(id: int, page: int = None) -> str:
- url = f"{basicUrl}?board/{id}"
- if page:
- url += f"&pageNo={page}"
- return url
- def getThreadUrl(id: int, page: int = None) -> str:
- url = f"{basicUrl}?thread/{id}"
- if page is not None:
- if page == 0:
- url += "&action=lastPost"
- else:
- url += f"&pageNo={page}"
- return url
- def getChildrenProperties(property: str, children):
- return list(map(lambda child: child[property], children))
- def getThreadTime(messageGroup: BeautifulSoup) -> int:
- lastPost = messageGroup.find(class_='columnLastPost')
- subject = messageGroup.find(class_='columnSubject')
- unix = 0
- # Take timestamp from lastPost or from the subject post which is de facto the last post
- # Last post is either not existing or empty
- if lastPost is None or lastPost.find(class_='box32') is None:
- unix = int(subject.find(class_='messageGroupTime').find(class_='datetime')['data-timestamp'])
- else:
- unix = int(lastPost.find(class_='datetime')['data-timestamp'])
- return unix
- # Functions
- def getBoards() -> dict:
- boards = {}
- # Main board
- html = getHtml(basicUrl, 'Getting main board') # Assumes basicUrl is starting point for boards
- boardList = html.find(class_='wbbBoardList')
- boardChildren = boardList.find_all('li', recursive=False)
- boardChildrenIds = getChildrenProperties('data-board-id', boardChildren)
- # Other boards
- def boardHasThreads(html: BeautifulSoup) -> bool:
- threadList = html.find(class_='wbbThreadList')
- if threadList is None:
- return False
- else:
- return True
- def boardHasSubboards(html: BeautifulSoup) -> bool:
- boardList = html.find(class_='wbbBoardList')
- if boardList is None:
- return False
- else:
- return True
- def getBoardChildren(boardId: int) -> list:
- html = getHtml(getBoardUrl(boardId), f"Getting board #{boardId}")
- # Check for threads
- if boardHasThreads(html):
- threadList = html.find(class_='wbbThreadList')
- messageGroups = threadList.find_all(class_='messageGroup')
- for messageGroup in messageGroups:
- threadTime = getThreadTime(messageGroup)
- # Is within period
- if threadTime >= periodUnix:
- boardTitle = html.find(class_='contentTitle').contents[0]
- boards[boardId] = boardTitle
- break # Faster! Almost lightning speed!
- # Get subboards
- if boardHasSubboards(html):
- boardList = html.find(class_='wbbBoardList')
- boardChildren = boardList.find_all('li', recursive=False)
- boardChildrenIds = getChildrenProperties('data-board-id', boardChildren)
- for boardChildrenId in boardChildrenIds:
- getBoardChildren(boardChildrenId)
- for boardChildrenId in boardChildrenIds:
- getBoardChildren(boardChildrenId)
- return boards
- def getThreads(boards: dict):
- threads = {}
- for boardId, boardTitle in boards.items():
- page = 1
- def goTroughPage(page: int):
- pageThreads = {}
- html = getHtml(getBoardUrl(boardId, page=page), f"Getting threads for page {page} on board {boardId} ({boardTitle})")
- totalPages = 0
- pagination = html.find(class_='pagination')
- if pagination is None:
- totalPages = 1
- else:
- totalPages = int(pagination['data-pages'])
- threadList = html.find(class_='wbbThreadList')
- messageGroups = threadList.find_all(class_='messageGroup')
- for messageGroup in messageGroups:
- # Skip deleted threads
- if 'messageDeleted' in messageGroup['class']:
- continue
- threadTime = getThreadTime(messageGroup)
- if threadTime >= periodUnix:
- threadId = messageGroup['data-thread-id']
- threadTitle = messageGroup.find(class_='messageGroupLink').contents[0]
- pageThreads[threadId] = threadTitle
- # Add page threads to threads
- threads.update(pageThreads)
- # If last successfully checked element is last element of viewable threads, go to next page
- lastPageThreadInPeriodId = list(pageThreads.keys())[-1]
- lastPageThreadId = messageGroups[-1]['data-thread-id']
- if lastPageThreadId == lastPageThreadInPeriodId:
- nextPage = page + 1
- if nextPage <= totalPages:
- goTroughPage(nextPage)
- goTroughPage(page)
- return threads
- def getUsers(threads: dict):
- users = {}
- for threadId, threadName in threads.items():
- page = 0
- def goTroughPage(page: int):
- html = getHtml(getThreadUrl(threadId, page=page), f"Getting posts on page {page} on thread {threadId} ({threadName})")
- postList = html.find(class_='wbbThreadPostList')
- posts = html.find_all(class_='wbbPost')
- # Set page to last page if first round
- pagination = html.find(class_='pagination')
- totalPages = 0
- if pagination is None:
- totalPages = 1
- else:
- totalPages = int(pagination['data-pages'])
- if page == 0:
- page = totalPages
- for post in posts:
- # Skip deleted posts
- if 'wbbPostDeleted' in post['class']:
- continue
- postTime = int(post.find(class_='datetime')['data-timestamp'])
- if postTime >= periodUnix:
- userId = post['data-user-id']
- username = post.find(attrs={'itemprop': 'name'}).contents[0]
- if userId not in users and userId != '':
- users[userId] = username
- print(f"Found user {userId} ({username})!")
- # Go to previous page if first post is within the period
- firstPostTime = int(posts[0].find(class_='datetime')['data-timestamp'])
- if firstPostTime >= periodUnix:
- nextPage = page - 1
- if nextPage != 0:
- goTroughPage(nextPage)
- goTroughPage(page)
- return users
- def getSpoiler(users: dict) -> str:
- return '<woltlab-spoiler><p>{}</p></woltlab-spoiler>'.format(' '.join([f"[user='{userId}']{username}[/user]" for userId, username in users.items()]))
- # Actual code
- print("Welcome!\n")
- daysInput: int = None
- while daysInput is None:
- inputStr = input(f"Select number of days to search users\nPress enter to continue with the default ({days}) or type a number\n")
- if inputStr.isdigit():
- daysInput = int(inputStr)
- days = daysInput
- if inputStr == '':
- break
- # Recalculate period unix in any case
- periodUnix = getPeriodUnix(days)
- print(f"You selected {days} days\n")
- print("Select boards")
- boards = {}
- action: str = None
- while action is None:
- inputStr = input("Press enter or type 'd' or 'default' for the default selection\nType 's' or 'select' to select your own boards\nType 'a' or 'all' for all boards (not recommended!)\n")
- if inputStr == '' or inputStr == 'd' or inputStr == 'default':
- action = 'default'
- if inputStr == 's' or inputStr == 'select':
- action = 'select'
- if inputStr == 'a' or inputStr == 'all':
- action = 'all'
- print(f"You choose: {action}\n")
- if action == 'default':
- boards = {11: 'Vereinshaus', 12: 'Bürgerversammlung', 2: 'Konferenz- und Medienzentrum'}
- if action == 'all':
- boards = getBoards()
- if action == 'select':
- inputBoards = {}
- inputStr = None
- while inputStr != '':
- inputStr = input('Press enter to stop input\nType a board id to add it to the boards list (board must have threads!)\n')
- if inputStr.isdigit():
- inputBoards[inputStr] = 'User selection'
- print(f"Added board {inputStr} to boards list\nCurrent boards: {', '.join(inputBoards.keys())}")
- boards = inputBoards
- print(f"{len(boards.keys())} Boards found/selected: {', '.join([f'{boardId} ({title})' for boardId, title in boards.items()])}\n")
- print("Getting threads for boards")
- threads = getThreads(boards)
- print(f"Found {len(threads.keys())} threads\n")
- print("Getting users for threads")
- users = getUsers(threads)
- print(users)
- print(f"Found {len(users.keys())} users\n")
- print(f"Spoiler:\n{getSpoiler(users)}\n")
- print("Thank you for using this! Don't abuse it though! Really, just... don't\n")
- exitEnter()