mirror of
https://github.com/fabianonline/telegram_backup.git
synced 2024-11-23 01:06:17 +00:00
Merging master into stable for version 1.1.3
This commit is contained in:
commit
edfdb90073
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,3 +10,6 @@ src/main/main.iml
|
|||||||
cache4.*
|
cache4.*
|
||||||
src/test/test.iml
|
src/test/test.iml
|
||||||
dev/
|
dev/
|
||||||
|
todo
|
||||||
|
deploy.secret.sh
|
||||||
|
release_notes.txt
|
||||||
|
10
.travis.yml
10
.travis.yml
@ -11,13 +11,3 @@ cache:
|
|||||||
directories:
|
directories:
|
||||||
- $HOME/.gradle/caches/
|
- $HOME/.gradle/caches/
|
||||||
- $HOME/.gradle/wrapper/
|
- $HOME/.gradle/wrapper/
|
||||||
|
|
||||||
deploy:
|
|
||||||
provider: pages
|
|
||||||
skip-cleanup: true
|
|
||||||
github-token: $github_token
|
|
||||||
keep-history: true
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
local-dir: build/libs
|
|
||||||
target-branch: gh-pages
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
* Update the version in the Dockerfile to the coming version.
|
* Update the version in the Dockerfile to the coming version.
|
||||||
* Commit the new Dockerfile.
|
* Commit the new Dockerfile.
|
||||||
|
* Merge into stable: `git checkout stable && git merge --no-ff master`
|
||||||
* Create a new tag for the new version: `git tag -a <version>`.
|
* Create a new tag for the new version: `git tag -a <version>`.
|
||||||
* Push everything to github: `git push --all && git push --tags`.
|
* Push everything to github: `git push --all && git push --tags`.
|
||||||
* Build it: `gradle build`.
|
* Build it: `gradle build`.
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,10 +1,15 @@
|
|||||||
FROM openjdk:8
|
FROM openjdk:8
|
||||||
|
|
||||||
ENV JAR_VERSION 1.1.2
|
ENV JAR_VERSION 1.1.3
|
||||||
ENV JAR_DOWNLOAD_URL https://github.com/fabianonline/telegram_backup/releases/download/${JAR_VERSION}/telegram_backup.jar
|
ENV JAR_DOWNLOAD_URL https://github.com/fabianonline/telegram_backup/releases/download/${JAR_VERSION}/telegram_backup.jar
|
||||||
|
|
||||||
RUN apt-get update -y && apt-get install -y curl && \
|
RUN apt-get update -y && \
|
||||||
|
apt-get install --no-install-recommends -y curl && \
|
||||||
curl -L "https://github.com/Yelp/dumb-init/releases/download/v1.1.3/dumb-init_1.1.3_amd64" -o /bin/dumb-init && \
|
curl -L "https://github.com/Yelp/dumb-init/releases/download/v1.1.3/dumb-init_1.1.3_amd64" -o /bin/dumb-init && \
|
||||||
curl -L $JAR_DOWNLOAD_URL -o telegram_backup.jar && mkdir /data/ && chmod +x /bin/dumb-init
|
curl -L $JAR_DOWNLOAD_URL -o telegram_backup.jar && mkdir /data/ && \
|
||||||
|
chmod +x /bin/dumb-init && \
|
||||||
|
apt-get remove -y curl && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/dumb-init", "--", "java", "-jar", "telegram_backup.jar", "--target", "/data/"]
|
ENTRYPOINT ["/bin/dumb-init", "--", "java", "-jar", "telegram_backup.jar", "--target", "/data/"]
|
||||||
|
@ -21,7 +21,7 @@ You can find the whole app packed into one fat jar file under
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
This tool relies on Telegram's API. They started rate limiting the calls
|
This tool relies on Telegram's API. They started rate limiting the calls
|
||||||
made by this tool some time ago. As of february 2017, downloading messages
|
made by this tool some time ago. As of February 2017, downloading messages
|
||||||
is limited to 400 messages every 30 seconds, resulting in 48,000 messages
|
is limited to 400 messages every 30 seconds, resulting in 48,000 messages
|
||||||
per hour. Media download is not throttled right now, so it should be a lot
|
per hour. Media download is not throttled right now, so it should be a lot
|
||||||
quicker.
|
quicker.
|
||||||
|
84
deploy.sh
Executable file
84
deploy.sh
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
error() {
|
||||||
|
echo "Error: $1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -z "$1" ] && error "Parameter's missing. Expecting version number like '1.2.3' as first and only parameter."
|
||||||
|
|
||||||
|
if [ "$1" == "--help" ]; then
|
||||||
|
echo "Usage: `basename "$0"` 1.2.3"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release_notes="$(cat release_notes.txt 2>/dev/null)"
|
||||||
|
[ -z "$release_notes" ] && error "release_notes.txt is empty"
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
source "deploy.secret.sh"
|
||||||
|
[ -z "$BOT_TOKEN" ] && error "BOT_TOKEN is not set or empty."
|
||||||
|
[ -z "$CHAT_ID" ] && error "CHAT_ID is not set or empty."
|
||||||
|
[ -z "$TOKEN" ] && error "TOKEN is not set or empty."
|
||||||
|
|
||||||
|
CURL_OPTS="-u fabianonline:$TOKEN"
|
||||||
|
|
||||||
|
git diff-files --quiet --ignore-submodules -- || error "You have changes in your working tree."
|
||||||
|
|
||||||
|
git diff-index --cached --quiet HEAD --ignore-submodules -- || error "You have uncommited changes."
|
||||||
|
|
||||||
|
branch_name=$(git symbolic-ref HEAD 2>/dev/null)
|
||||||
|
branch_name=${branch_name##refs/heads/}
|
||||||
|
[ "$branch_name" == "master" ] || error "Current branch is $branch_name, not master."
|
||||||
|
|
||||||
|
echo "Updating the Dockerfile..."
|
||||||
|
sed -i "s/ENV JAR_VERSION .\+/ENV JAR_VERSION $VERSION/g" Dockerfile || error "Couldn't modify Dockerfile."
|
||||||
|
|
||||||
|
echo "Committing the new Dockerfile..."
|
||||||
|
git commit -m "Bumping the version to $VERSION" Dockerfile || error "Couldn't commit the new Dockerfile."
|
||||||
|
|
||||||
|
echo "Tagging the new version..."
|
||||||
|
git tag -a "$VERSION" -m "Version $VERSION" || error
|
||||||
|
|
||||||
|
echo "Checking out stable..."
|
||||||
|
git checkout stable || error
|
||||||
|
|
||||||
|
echo "Merging master into stable..."
|
||||||
|
git merge --no-ff -m "Merging master into stable for version $VERSION" master || error
|
||||||
|
|
||||||
|
echo "Pushing all to Github..."
|
||||||
|
git push --all || error
|
||||||
|
|
||||||
|
echo "Pushing tags to Github..."
|
||||||
|
git push --tags || error
|
||||||
|
|
||||||
|
echo "Building it..."
|
||||||
|
gradle build || error "Build failed. What did you do?!"
|
||||||
|
|
||||||
|
echo "Generating a release on Github..."
|
||||||
|
json=$(ruby -e "require 'json'; puts({tag_name: '$VERSION', name: '$VERSION', draft: true, body: \$stdin.read}.to_json)" <<< "$release_notes") || error "Couldn't generate JSON for Github"
|
||||||
|
|
||||||
|
json=$(curl $CURL_OPTS https://api.github.com/repos/fabianonline/telegram_backup/releases -XPOST -d "$json") || error "Github failure"
|
||||||
|
|
||||||
|
echo "Uploading telegram_backup.jar to Github..."
|
||||||
|
upload_url=$(jq -r ".upload_url" <<< "$json") || error "Could not parse JSON from Github"
|
||||||
|
upload_url=$(sed 's/{.*}//' <<< "$upload_url")
|
||||||
|
release_url=$(jq -r ".url" <<< "$json") || error "Could not parse JSON from Github"
|
||||||
|
curl $CURL_OPTS --header "Content-Type: application/zip" "${upload_url}?name=telegram_backup.jar" --upload-file build/libs/telegram_backup.jar || error "Asset upload to github failed"
|
||||||
|
|
||||||
|
echo "Building the docker image..."
|
||||||
|
docker build -t fabianonline/telegram_backup:$VERSION -t fabianonline/telegram_backup:latest - < Dockerfile
|
||||||
|
|
||||||
|
echo "Pushing the docker image..."
|
||||||
|
docker push fabianonline/telegram_backup
|
||||||
|
|
||||||
|
echo "Notifying the Telegram group..."
|
||||||
|
release_notes=$(sed 's/\* /• /' <<< "$release_notes")
|
||||||
|
message="Version $VERSION released"$'\n'$'\n'"$release_notes"$'\n'$'\n'"$release_url"
|
||||||
|
|
||||||
|
curl https://api.telegram.org/bot${BOT_TOKEN}/sendMessage -XPOST --form "text=<-" --form-string "chat_id=${CHAT_ID}" <<< "$message"
|
||||||
|
|
||||||
|
echo "Cleaning release_notes.txt..."
|
||||||
|
> release_notes.txt
|
||||||
|
|
||||||
|
echo "Done."
|
@ -95,7 +95,7 @@ class CommandLineController {
|
|||||||
}
|
}
|
||||||
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
|
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
|
||||||
if (CommandLineOptions.cmd_login) {
|
if (CommandLineOptions.cmd_login) {
|
||||||
cmd_login(account)
|
cmd_login(CommandLineOptions.val_account)
|
||||||
System.exit(0)
|
System.exit(0)
|
||||||
}
|
}
|
||||||
// If we reach this point, we can assume that there is an account and a database can be loaded / created.
|
// If we reach this point, we can assume that there is an account and a database can be loaded / created.
|
||||||
@ -140,7 +140,8 @@ class CommandLineController {
|
|||||||
} else {
|
} else {
|
||||||
println("Skipping media download because --no-media is set.")
|
println("Skipping media download because --no-media is set.")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Throwable) {
|
||||||
|
println("An error occured!")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
logger.error("Exception caught!", e)
|
logger.error("Exception caught!", e)
|
||||||
// If we encountered an exception, we definitely don't want to start the daemon mode now.
|
// If we encountered an exception, we definitely don't want to start the daemon mode now.
|
||||||
@ -282,6 +283,7 @@ class CommandLineController {
|
|||||||
println(" --pagination <x> Splits the HTML export into multiple HTML pages with <x> messages per page. Default is 5000.")
|
println(" --pagination <x> Splits the HTML export into multiple HTML pages with <x> messages per page. Default is 5000.")
|
||||||
println(" --no-pagination Disables pagination.")
|
println(" --no-pagination Disables pagination.")
|
||||||
println(" --license Displays the license of this program.")
|
println(" --license Displays the license of this program.")
|
||||||
|
println(" -d, --daemon Keep running after the backup and automatically save new messages.")
|
||||||
println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.")
|
println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.")
|
||||||
println(" --stats Print some usage statistics.")
|
println(" --stats Print some usage statistics.")
|
||||||
println(" --with-channels Backup channels as well.")
|
println(" --with-channels Backup channels as well.")
|
||||||
|
@ -19,6 +19,7 @@ package de.fabianonline.telegram_backup
|
|||||||
import de.fabianonline.telegram_backup.CommandLineController
|
import de.fabianonline.telegram_backup.CommandLineController
|
||||||
import de.fabianonline.telegram_backup.Utils
|
import de.fabianonline.telegram_backup.Utils
|
||||||
import de.fabianonline.telegram_backup.Version
|
import de.fabianonline.telegram_backup.Version
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import ch.qos.logback.classic.Logger
|
import ch.qos.logback.classic.Logger
|
||||||
import ch.qos.logback.classic.LoggerContext
|
import ch.qos.logback.classic.LoggerContext
|
||||||
@ -78,14 +79,21 @@ object CommandLineRunner {
|
|||||||
fun checkVersion(): Boolean {
|
fun checkVersion(): Boolean {
|
||||||
val v = Utils.getNewestVersion()
|
val v = Utils.getNewestVersion()
|
||||||
if (v != null && v.isNewer) {
|
if (v != null && v.isNewer) {
|
||||||
System.out.println("A newer version is vailable!")
|
println()
|
||||||
System.out.println("You are using: " + Config.APP_APPVER)
|
println()
|
||||||
System.out.println("Available: " + v.version)
|
println()
|
||||||
System.out.println("Get it here: " + v.url)
|
println("A newer version is vailable!")
|
||||||
System.out.println()
|
println("You are using: " + Config.APP_APPVER)
|
||||||
System.out.println("Changes in this version:")
|
println("Available: " + v.version)
|
||||||
System.out.println(v.body)
|
println("Get it here: " + v.url)
|
||||||
System.out.println()
|
println()
|
||||||
|
println()
|
||||||
|
println("Changes in this version:")
|
||||||
|
println(v.body)
|
||||||
|
println()
|
||||||
|
println()
|
||||||
|
println()
|
||||||
|
TimeUnit.SECONDS.sleep(5)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -41,7 +41,7 @@ class DatabaseUpdates(protected var conn: Connection, protected var db: Database
|
|||||||
logger.debug("DatabaseUpdate.doUpdates running")
|
logger.debug("DatabaseUpdate.doUpdates running")
|
||||||
|
|
||||||
logger.debug("Getting current database version")
|
logger.debug("Getting current database version")
|
||||||
val version: Int
|
var version: Int
|
||||||
logger.debug("Checking if table database_versions exists")
|
logger.debug("Checking if table database_versions exists")
|
||||||
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'")
|
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'")
|
||||||
rs.next()
|
rs.next()
|
||||||
@ -60,6 +60,26 @@ class DatabaseUpdates(protected var conn: Connection, protected var db: Database
|
|||||||
System.out.println("Database version: " + version)
|
System.out.println("Database version: " + version)
|
||||||
logger.debug("Max available database version is {}", maxPossibleVersion)
|
logger.debug("Max available database version is {}", maxPossibleVersion)
|
||||||
|
|
||||||
|
if (version == 0) {
|
||||||
|
logger.debug("Looking for DatabaseUpdate with create_query...")
|
||||||
|
// This is a fresh database - so we search for the latest available version with a create_query
|
||||||
|
// and use this as a shortcut.
|
||||||
|
var update: DatabaseUpdate? = null
|
||||||
|
for (i in maxPossibleVersion downTo 1) {
|
||||||
|
update = getUpdateToVersion(i)
|
||||||
|
logger.trace("Looking at DatabaseUpdate version {}", update.version)
|
||||||
|
if (update.create_query != null) break
|
||||||
|
update = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update != null) {
|
||||||
|
logger.debug("Found DatabaseUpdate version {} with create_query.", update.version)
|
||||||
|
for (query in update.create_query!!) stmt.execute(query)
|
||||||
|
stmt.execute("INSERT INTO database_versions (version) VALUES (${update.version})")
|
||||||
|
version = update.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (version < maxPossibleVersion) {
|
if (version < maxPossibleVersion) {
|
||||||
logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion)
|
logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion)
|
||||||
var backup = false
|
var backup = false
|
||||||
@ -151,6 +171,8 @@ internal abstract class DatabaseUpdate(protected var conn: Connection, protected
|
|||||||
companion object {
|
companion object {
|
||||||
protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java)
|
protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open val create_query: List<String>? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
@ -308,6 +330,7 @@ internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn
|
|||||||
rs.close()
|
rs.close()
|
||||||
conn.setAutoCommit(false)
|
conn.setAutoCommit(false)
|
||||||
ps.executeBatch()
|
ps.executeBatch()
|
||||||
|
ps.close()
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.setAutoCommit(true)
|
conn.setAutoCommit(true)
|
||||||
stmt.executeUpdate("DROP TABLE messages")
|
stmt.executeUpdate("DROP TABLE messages")
|
||||||
@ -376,19 +399,29 @@ internal class DB_Update_9(conn: Connection, db: Database) : DatabaseUpdate(conn
|
|||||||
override val version: Int
|
override val version: Int
|
||||||
get() = 9
|
get() = 9
|
||||||
override val needsBackup = true
|
override val needsBackup = true
|
||||||
|
|
||||||
|
override val create_query = listOf(
|
||||||
|
"CREATE TABLE \"chats\" (id INTEGER PRIMARY KEY ASC, name TEXT, type TEXT);",
|
||||||
|
"CREATE TABLE \"users\" (id INTEGER PRIMARY KEY ASC, first_name TEXT, last_name TEXT, username TEXT, type TEXT, phone TEXT);",
|
||||||
|
"CREATE TABLE database_versions (version INTEGER);",
|
||||||
|
"CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER);",
|
||||||
|
"CREATE TABLE \"messages\" (id INTEGER PRIMARY KEY AUTOINCREMENT,message_id INTEGER,message_type TEXT,source_type TEXT,source_id INTEGER,sender_id INTEGER,fwd_from_id INTEGER,text TEXT,time INTEGER,has_media BOOLEAN,media_type TEXT,media_file TEXT,media_size INTEGER,media_json TEXT,markup_json TEXT,data BLOB,api_layer INTEGER);",
|
||||||
|
"CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id);"
|
||||||
|
)
|
||||||
|
|
||||||
@Throws(SQLException::class)
|
@Throws(SQLException::class)
|
||||||
override fun _doUpdate() {
|
override fun _doUpdate() {
|
||||||
val logger = LoggerFactory.getLogger(DB_Update_9::class.java)
|
val logger = LoggerFactory.getLogger(DB_Update_9::class.java)
|
||||||
println(" Updating supergroup channel message data (this might take some time)...")
|
println(" Updating supergroup channel message data (this might take some time)...")
|
||||||
|
print(" ")
|
||||||
val count = db.queryInt("SELECT COUNT(*) FROM messages WHERE source_type='channel' and sender_id IS NULL and api_layer=53")
|
val count = db.queryInt("SELECT COUNT(*) FROM messages WHERE source_type='channel' and sender_id IS NULL and api_layer=53")
|
||||||
logger.debug("Found $count candidates for conversion")
|
logger.debug("Found $count candidates for conversion")
|
||||||
val limit = 5000
|
val limit = 5000
|
||||||
var offset = 0
|
var offset = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while (offset + 1 < count) {
|
while (offset < count) {
|
||||||
logger.debug("Querying with limit $limit and offset $offset")
|
logger.debug("Querying with limit $limit and offset $offset")
|
||||||
val rs = stmt.executeQuery("SELECT id, data, source_id FROM messages WHERE source_type='channel' and sender_id IS NULL and api_layer=53 LIMIT ${limit} OFFSET ${offset}")
|
val rs = stmt.executeQuery("SELECT id, data, source_id FROM messages WHERE source_type='channel' and sender_id IS NULL and api_layer=53 ORDER BY id LIMIT ${limit} OFFSET ${offset}")
|
||||||
val messages = TLVector<TLAbsMessage>()
|
val messages = TLVector<TLAbsMessage>()
|
||||||
val messages_to_delete = mutableListOf<Int>()
|
val messages_to_delete = mutableListOf<Int>()
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
@ -399,11 +432,14 @@ internal class DB_Update_9(conn: Connection, db: Database) : DatabaseUpdate(conn
|
|||||||
messages_to_delete.add(rs.getInt(1))
|
messages_to_delete.add(rs.getInt(1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rs.close()
|
||||||
db.saveMessages(messages, api_layer=53, source_type=MessageSource.SUPERGROUP)
|
db.saveMessages(messages, api_layer=53, source_type=MessageSource.SUPERGROUP)
|
||||||
execute("DELETE FROM messages WHERE id IN (" + messages_to_delete.joinToString() + ")")
|
execute("DELETE FROM messages WHERE id IN (" + messages_to_delete.joinToString() + ")")
|
||||||
|
print(".")
|
||||||
|
|
||||||
offset += limit
|
offset += limit
|
||||||
}
|
}
|
||||||
|
println()
|
||||||
logger.info("Converted ${i} of ${count} messages.")
|
logger.info("Converted ${i} of ${count} messages.")
|
||||||
println(" Cleaning up the database (this might also take some time, sorry)...")
|
println(" Cleaning up the database (this might also take some time, sorry)...")
|
||||||
execute("VACUUM")
|
execute("VACUUM")
|
||||||
|
@ -74,7 +74,8 @@ class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient)
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun download(): Boolean {
|
override fun download(): Boolean {
|
||||||
val url = "https://maps.googleapis.com/maps/api/staticmap?" +
|
val url = "https://maps.googleapis.com/maps/api/staticmap?" +
|
||||||
"center=" + geo.getLat() + "," + geo.getLong() + "&" +
|
"center=${geo.getLat()},${geo.getLong()}&" +
|
||||||
|
"markers=color:red|${geo.getLat()},${geo.getLong()}&" +
|
||||||
"zoom=14&size=300x150&scale=2&format=png&" +
|
"zoom=14&size=300x150&scale=2&format=png&" +
|
||||||
"key=" + Config.SECRET_GMAPS
|
"key=" + Config.SECRET_GMAPS
|
||||||
return DownloadManager.downloadExternalFile(targetPathAndFilename, url)
|
return DownloadManager.downloadExternalFile(targetPathAndFilename, url)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
{{#media_sticker}}<span class="sticker"><img src="../stickers/{{media_file}}" /></span>{{/media_sticker}}
|
{{#media_sticker}}<span class="sticker"><img src="../stickers/{{media_file}}" /></span>{{/media_sticker}}
|
||||||
{{#media_photo}}<span class="photo"><img src="../{{media_file}}" /></span>{{/media_photo}}
|
{{#media_photo}}<span class="photo"><img src="../{{media_file}}" /></span>{{/media_photo}}
|
||||||
{{#media_document}}<span class="document"><a href="../{{media_file}}">{{media_file}}</a></span>{{/media_document}}
|
{{#media_document}}<span class="document"><a href="../{{media_file}}">{{media_file}}</a></span>{{/media_document}}
|
||||||
|
{{#media_geo}}<span class="geo"><img src="../{{media_file}}" width="300" height="150"></span>{{/media_geo}}
|
||||||
</li>
|
</li>
|
||||||
{{/messages}}
|
{{/messages}}
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user