多租户更改

This commit is contained in:
richjohnrambo
2026-08-11 15:59:57 +08:00
parent 6c800de67d
commit c2076721fc
453 changed files with 102349 additions and 47 deletions
+1
View File
@@ -0,0 +1 @@
.tn-cookie
+119
View File
@@ -0,0 +1,119 @@
# Tinode Chatbot
This is a simple chatbot for Tinode using [gRPC API](../../pbx/). It's written in Python as a demonstration
that the API is language-independent.
The chat bot subscribes to events stream using Plugin API and logs in to Tinode server as a regular user over gRPC interface (see `grpc_listen` in [tinode.conf](../../server/tinode.conf) file). The event stream API is used to listen for creation of new accounts. When a new account is created, the bot initiates a p2p topic with the new user. Then it listens for messages sent to the topic and responds to each with a random quote from `quotes.txt` file.
Generated files are provided for convenience in a [separate folder](../../py_grpc/tinode_grpc). You may re-generate them if needed:
```
python -m pip install grpcio-tools
python -m grpc_tools.protoc -../../pbx --python_out=. --grpc_python_out=. ../../pbx/model.proto
```
Chatbot expects gRPC binding to be provided as `tinode-grpc`. If you want to use them locally, first copy `model_pb2.py` and `model_pb2_grpc.py` to the same folder as `chatbot.py` then find the lines
```
from tinode_grpc import pb
from tinode_grpc import pbx
```
in `chatbot.py` and replace them with
```
import model_pb2 as pb
import model_pb2_grpc as pbx
```
## Installing and running
### Using PIP
#### Prerequisites
[gRPC](https://grpc.io/) requires [python](https://www.python.org/) 2.7 or 3.4 or higher.
Make sure [pip](https://pip.pypa.io/en/stable/installing/) 9.0.1 or higher is installed.
```
$ python -m pip install --upgrade pip
```
If you cannot upgrade pip due to a system-owned installation, you can run install it in a `virtualenv`:
```
$ python -m pip install virtualenv
$ virtualenv venv
$ source venv/bin/activate
$ python -m pip install --upgrade pip
```
#### Install dependencies:
```
$ python -m pip install -r requirements.txt
```
On El Capitan OSX, you may get the following error:
```
$ OSError: [Errno 1] Operation not permitted: '/tmp/pip-qwTLbI-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info'
```
You can work around this using:
```
$ python -m pip install tinode_grpc --ignore-installed
```
### Run the chatbot
Start the [tinode server](../../INSTALL.md) first. Then start the chatbot with credentials of the user you want to be your bot, `alice` in this example:
```
python chatbot.py --login-basic=alice:alice123
```
If you want to run the bot in the background, start it as
```
nohup python chatbot.py --login-basic=alice:alice123 &
```
Run `python chatbot.py -h` for more options.
If you are using python 2, keep in mind that `condition.wait()` [is forever buggy](https://bugs.python.org/issue8844). As a consequence of this bug the bot cannot be terminated with a SIGINT. It has to be stopped with a SIGKILL.
You can use cookie file to store credentials. Sample cookie files are provided as `basic-cookie.sample` and `token-cookie.sample`. Once authenticated the bot will store the token in the cookie file, `.tn-cookie` by default. If you have a cookie file with the desired credentials, you can run the bot with no parameters:
```
python chatbot.py
```
If the server is configured to use TLS, i.e. running as `httpS://my-server.example.com/`, the gRPC endpoint also uses the same SSL certificate. In that case add the `--ssl` option when starting the chatbot. If you want the chatbot to connect to the secure server over a local network or under a different name rather than the `my-server.example.com`, for instance as `localhost`, you must specify the SSL domain name to use, otherwise the server will not be able to find the right SSL certificate:
```
python chatbot.py --host=localhost:16060 --ssl --ssl-host=my-server.example.com
```
Quotes are read from `quotes.txt` by default. The file is plain text with one quote per line.
### Using Docker
**Warning!** Although the chatbot itself is less than 11KB, the chatbot Docker image is 175MB: the `:slim` Python 3 image is about 140MB, gRPC adds another ~30MB.
1. Follow [instructions](../../docker/README.md) to build and run dockerized Tinode chat server up to and including _step 3_.
2. In _step 4_ run the server adding `--env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true` and `--volume botdata:/botdata` to the command line:
1. **RethinkDB**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-rethink:latest
```
2. **MySQL**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-mysql:latest
```
3. **MongoDB**:
```
$ docker run -p 6060:18080 -d --name tinode-srv --env PLUGIN_PYTHON_CHAT_BOT_ENABLED=true --volume botdata:/botdata --network tinode-net tinode/tinode-mongodb:latest
```
3. Run the chatbot
```
$ docker run -d --name tino-chatbot --network tinode-net --volume botdata:/botdata tinode/chatbot:latest
```
4. Test that the bot is functional by pointing your browser to http://localhost:6060/, login and talk to user `Tino`. The user should respond to every message with a random quote.
You may replace the `:latest` with a different tag. See all available tags here:
* [Tinode-MySQL tags](https://hub.docker.com/r/tinode/tinode-mysql/tags/)
* [Tinode-RethinkDB tags](https://hub.docker.com/r/tinode/tinode-rethink/tags/)
* [Tinode-MongoDB tags](https://hub.docker.com/r/tinode/tinode-mongodb/tags/)
* [Chatbot tags](https://hub.docker.com/r/tinode/chatbot/tags/)
In general try to use docker images all with the same tag.
+1
View File
@@ -0,0 +1 @@
{"schema": "basic", "secret": "alice:alice123"}
+419
View File
@@ -0,0 +1,419 @@
"""Python implementation of a Tinode chatbot."""
# For compatibility between python 2 and 3
from __future__ import print_function
import argparse
import base64
from concurrent import futures
from datetime import datetime
import json
import os
try:
from importlib.metadata import version
except ImportError:
# Fallback for Python < 3.8
from importlib_metadata import version
import platform
try:
import Queue as queue
except ImportError:
import queue
import random
import signal
import sys
import time
import grpc
from google.protobuf.json_format import MessageToDict
# Import generated grpc modules
from tinode_grpc import pb
from tinode_grpc import pbx
# For compatibility with python2
if sys.version_info[0] >= 3:
unicode = str
APP_NAME = "Tino-chatbot"
APP_VERSION = "1.2.3"
LIB_VERSION = version("tinode_grpc")
# Maximum length of string to log. Shorten longer strings.
MAX_LOG_LEN = 64
# User ID of the current user
botUID = None
# Dictionary wich contains lambdas to be executed when server response is received
onCompletion = {}
# This is needed for gRPC ssl to work correctly.
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
def log(*args):
print(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], *args)
# Add bundle for future execution
def add_future(tid, bundle):
onCompletion[tid] = bundle
# Shorten long strings for logging.
def clip_long_string(obj):
if isinstance(obj, unicode) or isinstance(obj, str):
if len(obj) > MAX_LOG_LEN:
return '<' + str(len(obj)) + ' bytes: ' + obj[:12] + '...' + obj[-12:] + '>'
return obj
elif isinstance(obj, (list, tuple)):
return [clip_long_string(item) for item in obj]
elif isinstance(obj, dict):
return dict((key, clip_long_string(val)) for key, val in obj.items())
else:
return obj
def to_json(msg):
return json.dumps(clip_long_string(MessageToDict(msg)))
# Resolve or reject the future
def exec_future(tid, code, text, params):
bundle = onCompletion.get(tid)
if bundle != None:
del onCompletion[tid]
try:
if code >= 200 and code < 400:
arg = bundle.get('arg')
bundle.get('onsuccess')(arg, params)
else:
log("Error: {} {} ({})".format(code, text, tid))
onerror = bundle.get('onerror')
if onerror:
onerror(bundle.get('arg'), {'code': code, 'text': text})
except Exception as err:
log("Error handling server response", err)
# List of active subscriptions
subscriptions = {}
def add_subscription(topic):
subscriptions[topic] = True
def del_subscription(topic):
subscriptions.pop(topic, None)
def subscription_failed(topic, errcode):
if topic == 'me':
# Failed 'me' subscription means the bot is disfunctional.
if errcode.get('code') == 502:
# Cluster unreachable. Break the loop and retry in a few seconds.
client_post(None)
else:
exit(1)
def login_error(unused, errcode):
# Check for 409 "already authenticated".
if errcode.get('code') != 409:
exit(1)
def server_version(params):
if params == None:
return
log("Server:", params['build'].decode('ascii'), params['ver'].decode('ascii'))
def next_id():
next_id.tid += 1
return str(next_id.tid)
next_id.tid = 100
# Quotes from the fortune cookie file
quotes = []
def next_quote():
idx = random.randrange(0, len(quotes))
# Make sure quotes are not repeated
while idx == next_quote.idx:
idx = random.randrange(0, len(quotes))
next_quote.idx = idx
return quotes[idx]
next_quote.idx = 0
# This is the class for the server-side gRPC endpoints
class Plugin(pbx.PluginServicer):
def Account(self, acc_event, context):
action = None
if acc_event.action == pb.CREATE:
action = "created"
# TODO: subscribe to the new user.
elif acc_event.action == pb.UPDATE:
action = "updated"
elif acc_event.action == pb.DELETE:
action = "deleted"
else:
action = "unknown"
log("Account", action, ":", acc_event.user_id, acc_event.public)
return pb.Unused()
queue_out = queue.Queue()
def client_generate():
while True:
msg = queue_out.get()
if msg == None:
return
log("out:", to_json(msg))
yield msg
def client_post(msg):
queue_out.put(msg)
def client_reset():
# Drain the queue
try:
while queue_out.get(False) != None:
pass
except queue.Empty:
pass
def hello():
tid = next_id()
add_future(tid, {
'onsuccess': lambda unused, params: server_version(params),
})
return pb.ClientMsg(hi=pb.ClientHi(id=tid, user_agent=APP_NAME + "/" + APP_VERSION + " (" +
platform.system() + "/" + platform.release() + "); gRPC-python/" + LIB_VERSION,
ver=LIB_VERSION, lang="EN"))
def login(cookie_file_name, scheme, secret):
tid = next_id()
add_future(tid, {
'arg': cookie_file_name,
'onsuccess': lambda fname, params: on_login(fname, params),
'onerror': lambda unused, errcode: login_error(unused, errcode),
})
return pb.ClientMsg(login=pb.ClientLogin(id=tid, scheme=scheme, secret=secret))
def subscribe(topic):
tid = next_id()
add_future(tid, {
'arg': topic,
'onsuccess': lambda topicName, unused: add_subscription(topicName),
'onerror': lambda topicName, errcode: subscription_failed(topicName, errcode),
})
return pb.ClientMsg(sub=pb.ClientSub(id=tid, topic=topic))
def leave(topic):
tid = next_id()
add_future(tid, {
'arg': topic,
'onsuccess': lambda topicName, unused: del_subscription(topicName)
})
return pb.ClientMsg(leave=pb.ClientLeave(id=tid, topic=topic))
def publish(topic, text):
tid = next_id()
return pb.ClientMsg(pub=pb.ClientPub(id=tid, topic=topic, no_echo=True,
head={"auto": json.dumps(True).encode('utf-8')}, content=json.dumps(text).encode('utf-8')))
def note_read(topic, seq):
return pb.ClientMsg(note=pb.ClientNote(topic=topic, what=pb.READ, seq_id=seq))
def init_server(listen):
# Launch plugin server: accept connection(s) from the Tinode server.
server = grpc.server(futures.ThreadPoolExecutor(max_workers=16))
pbx.add_PluginServicer_to_server(Plugin(), server)
server.add_insecure_port(listen)
server.start()
log("Plugin server running at '"+listen+"'")
return server
def init_client(addr, schema, secret, cookie_file_name, secure, ssl_host):
log("Connecting to", "secure" if secure else "", "server at", addr,
"SNI="+ssl_host if ssl_host else "")
channel = None
if secure:
opts = (('grpc.ssl_target_name_override', ssl_host),) if ssl_host else None
channel = grpc.secure_channel(addr, grpc.ssl_channel_credentials(), opts)
else:
channel = grpc.insecure_channel(addr)
# Call the server
stream = pbx.NodeStub(channel).MessageLoop(client_generate())
# Session initialization sequence: {hi}, {login}, {sub topic='me'}
client_post(hello())
client_post(login(cookie_file_name, schema, secret))
return stream
def client_message_loop(stream):
try:
# Read server responses
for msg in stream:
log(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], "in:", to_json(msg))
if msg.HasField("ctrl"):
# Run code on command completion
exec_future(msg.ctrl.id, msg.ctrl.code, msg.ctrl.text, msg.ctrl.params)
elif msg.HasField("data"):
# log("message from:", msg.data.from_user_id)
# Protection against the bot talking to self from another session.
if msg.data.from_user_id != botUID:
# Respond to message.
# Mark received message as read
client_post(note_read(msg.data.topic, msg.data.seq_id))
# Insert a small delay to prevent accidental DoS self-attack.
time.sleep(0.1)
# Respond with a witty quote
client_post(publish(msg.data.topic, next_quote()))
elif msg.HasField("pres"):
# log("presence:", msg.pres.topic, msg.pres.what)
# Wait for peers to appear online and subscribe to their topics
if msg.pres.topic == 'me':
if (msg.pres.what == pb.ServerPres.ON or msg.pres.what == pb.ServerPres.MSG) \
and subscriptions.get(msg.pres.src) == None:
client_post(subscribe(msg.pres.src))
elif msg.pres.what == pb.ServerPres.OFF and subscriptions.get(msg.pres.src) != None:
client_post(leave(msg.pres.src))
else:
# Ignore everything else
pass
except grpc._channel._Rendezvous as err:
log("Disconnected:", err)
def read_auth_cookie(cookie_file_name):
"""Read authentication token from a file"""
cookie = open(cookie_file_name, 'r')
params = json.load(cookie)
cookie.close()
schema = params.get("schema")
secret = None
if schema == None:
return None, None
if schema == 'token':
secret = base64.b64decode(params.get('secret').encode('utf-8'))
else:
secret = params.get('secret').encode('utf-8')
return schema, secret
def on_login(cookie_file_name, params):
global botUID
client_post(subscribe('me'))
"""Save authentication token to file"""
if params == None or cookie_file_name == None:
return
if 'user' in params:
botUID = params['user'].decode("ascii")[1:-1]
# Protobuf map 'params' is not a python object or dictionary. Convert it.
nice = {'schema': 'token'}
for key_in in params:
if key_in == 'token':
key_out = 'secret'
else:
key_out = key_in
nice[key_out] = json.loads(params[key_in].decode('utf-8'))
try:
cookie = open(cookie_file_name, 'w')
json.dump(nice, cookie)
cookie.close()
except Exception as err:
log("Failed to save authentication cookie", err)
def load_quotes(file_name):
with open(file_name) as f:
for line in f:
quotes.append(line.strip())
return len(quotes)
def run(args):
schema = None
secret = None
if args.login_token:
"""Use token to login"""
schema = 'token'
secret = args.login_token.encode('ascii')
log("Logging in with token", args.login_token)
elif args.login_basic:
"""Use username:password"""
schema = 'basic'
secret = args.login_basic.encode('utf-8')
log("Logging in with login:password", args.login_basic)
else:
"""Try reading the cookie file"""
try:
schema, secret = read_auth_cookie(args.login_cookie)
log("Logging in with cookie file", args.login_cookie)
except Exception as err:
log("Failed to read authentication cookie", err)
if schema:
# Load random quotes from file
log("Loaded {} quotes".format(load_quotes(args.quotes)))
# Start Plugin server
server = init_server(args.listen)
# Initialize and launch client
client = init_client(args.host, schema, secret, args.login_cookie, args.ssl, args.ssl_host)
# Setup closure for graceful termination
def exit_gracefully(signo, stack_frame):
log("Terminated with signal", signo)
server.stop(0)
client.cancel()
sys.exit(0)
# Add signal handlers
signal.signal(signal.SIGINT, exit_gracefully)
signal.signal(signal.SIGTERM, exit_gracefully)
# Run blocking message loop in a cycle to handle
# server being down.
while True:
client_message_loop(client)
time.sleep(3)
client_reset()
client = init_client(args.host, schema, secret, args.login_cookie, args.ssl, args.ssl_host)
# Close connections gracefully before exiting
server.stop(None)
client.cancel()
else:
log("Error: authentication scheme not defined")
if __name__ == '__main__':
"""Parse command-line arguments. Extract server host name, listen address, authentication scheme"""
random.seed()
purpose = "Tino, Tinode's chatbot."
log(purpose)
parser = argparse.ArgumentParser(description=purpose)
parser.add_argument('--host', default='localhost:16060', help='address of Tinode server gRPC endpoint')
parser.add_argument('--ssl', action='store_true', help='use SSL to connect to the server')
parser.add_argument('--ssl-host', help='SSL host name to use instead of default (useful for connecting to localhost)')
parser.add_argument('--listen', default='0.0.0.0:40051', help='address to listen on for incoming Plugin API calls')
parser.add_argument('--login-basic', help='login using basic authentication username:password')
parser.add_argument('--login-token', help='login using token authentication')
parser.add_argument('--login-cookie', default='.tn-cookie', help='read credentials from the provided cookie file')
parser.add_argument('--quotes', default='quotes.txt', help='file with messages for the chatbot to use, one message per line')
args = parser.parse_args()
run(args)
+322
View File
@@ -0,0 +1,322 @@
login: 
$3,000,000
1 bulls, 3 cows
A Cray is the best machine for simulating the performance of a Cray.
A consistent indentation style is the hobgoblin of little minds.
A gentleman is one who is never rude unintentionally. -Noel Coward
A man must destroy himself before others can destroy him. -Mong Tse
A philosopher does not need a torch to gather glow-worms by at mid-day. --Earnest Bramah
A song in time is worth a dime.
A woman is only a woman, but a good cigar is a smoke. -Rudyard Kipling
Admiration is our polite recognition of another's resemblance to ourselves.
All the good ones are taken.
An atheist is a man with no invisible means of support.
Any country with "democratic" in the title isn't.
Are we not men?
Attend winter sheep meetings. Learning never ends!
Be the sea, and see me be.
Beware: the light at the end of the tunnel may be New Jersey.
Bubble bubble, toil and trouble; cast that float into a double.
C'est dommage, mais c'est vrai.
Caution: Do not view laser light with remaining eye.
Cogito cogito ergo cogito sum.
Crazee Edeee, his prices are INSANE!!!
Death to all fanatics!
Disk crisis, please clean up!
Do not meddle in the mouth.
Don't be overly suspicious where it's not warranted.
Don't let your thoughts get in a rut. The knife which spreads may also cut.
Don't worry if it doesn't work right; if everything did, you'd be out of a job.
E Pluribus Unix.
Either we are alone or we are not. Either way is mind-boggling.
Even these days, it's not as easy to go crazy as you think.
Everybody should believe in something -- I believe I'll have another drink.
Exercise is the Yuppie version of bulemia.
Far too noisy, my dear Mozart. Far too many notes. -Emperor Ferdinand.
First things first. Why not send for the Nazis right now.
Fortran est; non potest legi.
Generalizations are useful. The work contained in them can be reckoned as labor
God does not play dice.
Good day to avoid cops. Crawl to work.
Great shot, kid. That was one in a million.
Have you done your Christmas chopping yet? -anon. White House Advisor 12/24/81
He was al coltissh, ful of ragerye,/And ful of jargon as a flekke pye. -Chaucer
He who listens last is the last one listening.
History is a race between education and catastrophe. -H. G. Wells
How do I love thee? Hand me my calculator...
I am a high-pressure guy, and I didn't take this job to conduct a going-out-of-business sale. - A.A. Penzias
I don't even know what street Canada is on. - Al Capone
I have the most perfect confidence in your indiscretion.
I must have slipped a disk; my pack hurts.
I think that I shall never see a billboard as lovely as a tree. -Ogden Nash
I'd rather have my mail delivered by Lockheed than ride in a plane built by the Post Office.
IOT trap -- core dumped
If I had to choose between System V and 4.2, I'd resign. - Peter Honeyman
If butterflies had teeth like tigers they would never make it out of the hangar.
If it's not broken, don't fix it.
If the shoe fits, buy the other one, too.
If you don't care where you are, then you ain't lost.
If you take the last cup, make a new pot.
If your experiment needs statistics, you ought to have done a better experiment. - E. Rutherford
In challenging a kzin, a simple scream of rage is sufficient.
In this world, truth can wait; she's used to it.
It is better to have loved and lost than just to have lost.
It is useless to put on your brakes when you're upside down. -Paul Newman
It's a small world, but I'd hate to have to paint it.
It's hard to love someone who looks down on you because your hands get bloody protecting him.
It's not the time between the takes that takes the time - it's changing your mind between the takes that takes the time. -Stage Dept.
Join me and I will complete your training.
Lando's not a system, he's a man. He's a gambler, scoundrel. You'd like him.
Let sleeping wraiths lie.
Like winter snow on summer lawn, time past is time gone.
Lose a few, lose a few.
Macro context switch under way, please do not log out!
Man is in doubt to deem himself a god or beast. -Alexander Pope
May you live all the days of your life.
Mind your own business, Spock. I'm sick of your halfbreed interference.
Multilevel standards are like onions. They're smelly and make you cry a lot. -Ron Natalie
Never attribute to malice what can be found in scientific american, under computer recreations.
New career ideas are worth pursuing.
No man's life, liberty or property are safe while the legislature is in session.
Non serviam.
Nothing of interest ever happened on this day.
Oh, so there you are!
One scythe fits all.
Otto's too so-so to toss soot, too sot to toot SOSs. So?
People get it into their heads that this is a democracy. Well it isn't. -gwl
Personality is a flimsy thing on which to build an art. -John Cage
Populus vult decipi.
Promptness is its own reward, if one lives by the clock instead of the sword.
Real programmers can't say `lint' without adding `hbaxcu' -Wm Leler
Remember the Unknown Buffalo.
Rog-O-Matic callidus est.
Say "no" to long-sleeved shirts!! Support your right to bare arms!
Señor, if you hurry from here, you will wait longer there. -Mexico taxi driver
Smoked carp is terrible unless you're out of smoked salmon.
Some men are discovered; others are found out.
Specialization is for insects. -Robt. A. Heinlein
Stop searching. Happiness is right next to you.
System going down for 5 minutes -- back up in barrels.
Technological unemployment is total today -- 300 years ago we were all farmers.
That's the nice thing about standards -- there's so many to choose from. -trb
The Luddites always lose. Always.
The average legislator is somewhere nearly all the time. -Herb Nore
The dawning of the Information Age is bringing about dramatic changes in the fundamental fabric of our civilization. - AA Penzias
The first piece of luggage out of the chute doesn't belong to anyone, ever.
The hippo has no sting, but the wise man would rather be sat upon by the bee.
The meek shall inherit the earth -- they are pronounced "o".
The one interesting fact about the Diplodocus is that the accent is on the second syllable.
The plural of spouse is spice.
The skeletons in the cupboard will all come out in the wash.
The universe is laughing behind your back.
Them that dishes it out need not fall over every time someone blows hard.
There is no fear in love; but perfect love casteth out fear.
There'll always be an England - if not it would be necessary to invent one.
These widows, sir, are the most perverse creatures in the world. - Joseph Addison.
Things will be bright in P.M. A cop will shine a light in your face.
This space available. Call 686-7600 for details.
Those who in quarrels interpose must often wipe a bloody nose.
To be is to be related.
To play billiards well is a sign of an ill-spent youth.
Toto, I've a feeling we're not in any immediate danger of having just committed suicide!
Try a new system or a different approach.
Uneasy lies the head that wears a crown.
Veni, vidi, maeni, mo, cacha tigrem baedas to, iffi hollers, ledem go, veni, vidi, maeni, mo.
Warning: this fortune may change your life.
We don't know half of what we know.
We retard what we cannot repel, we palliate what we cannot cure. -Johnson
We're too close to System Test.
What garlic is to salad, insanity is to art.
When all else fails, read the instructions.
When in trouble or in doubt, run in circles; scream and shout.
Whenever I see his fingernails, I thank God I don't have to look at his feet.
Whom the gods must destroy they first must drive insane.
Without alkaloids, life itself would be impossible.
Yes, the red switch.
You can tell a man by the company that keeps him.
You cannot buy beer; you can only rent it.
You have bills.
You look strong enough to pull the ears off a Gundark!
You should go home.
You will never find a more wretched hive of scum and villainy.
Your computer account is overdrawn. Please reauthorize.
Zero is greater than minus zero, but don't ask by how much. -6600 ref. manual
`is false when preceded by its quotation' is false when preceded by its quotation.
fortune: not found
pic: 5 X 58008 picture shrunk to 0.000603365 X 7
uuxqt cmd (rnews ) status (ucsfcgl!uucp 256)
In days of yore, the crab and the crayfish lived in the forest.
* Method As described above, see details below.
Whatever happens, happens because it must.
Boost, don't knock
pass 2 error:(file ) more than 100 args?
Nepal premier won't resign.
init: /dev/console: getty failing, sleeping
Sentence without verb.
That's the way I got promoted, by eating everything. -pjw
Pittsburgh has become a kind of knowledge aircraft carrier, its "top-guns" scattered regularly around the planet.
When the music stops, the house of cards collapses and the emperor is found to be wearing no clothes.
Never put snow on a frostbitten part.
Put a smoke detector in your vacation cottage.
Draw up a family fire-escape plan.
A mathematician is a machine for turning coffee into theorems. -Paul Erdös
I'm TRYING to be a back end! - A Hume
There are only 26 calls and most of them are trivial.
162 is unimplemented
Incest more common than thought in United States
The product classroom is marked pass-fail.
ISDN is real and implementable.
To dissimulate is to feign not to have what one has. - J Baudrillard
Vacuums are nothings. We only mention them to let them know we know they're there.
Two wrongs don't make a right; three lefts do.
?12 Machine check during machine check.
The downside of having an architecture is wart-for-wart compatibility. - Bob Willard, DEC
It doesn't matter if you don't know how your program works, so long as it's parallel -R. O'Keefe
sendmail[94] AA00493: SYSERR: net hang reading from coma: Connection timed out during greeting wait with coma
Performance doesn't matter if your product is sufficiently feature-rich. --SF system engineer
PLEASE LOG IN TO 3B20'S AT 4800 BAUD.
SQUASH, do not crush (seen on a vegetable crate)
If you get to meet sufficently important people, it's ok to debase yourself. -pjw
The system is ready.
A watermelon will not ripen in your armpit.
Contrary to English and other similar languages, Turkish can be hyphenated with a simple 4 state finite-state machine.
nop...session...attach...clone...walk...open...
Spare me your sorrow's tears.
He is one inch good, one foot evil.
Heaven cannot use two suns or a house two masters.
To give ground is sometimes the best victory.
No word can cut kindness.
Let wisdom and virtue be the two wheels of your cart.
Willow branches never snap under the weight of snow.
Only a monkey tries to catch the full moon in the pond.
Don't lug dirt to a hilltop.
Don't paint on water or carve on ice.
The nail that raises its head is hammered down.
Who can tell the he-crow from his mate?
He is wise who knows what is enough.
His hand was bitten by his own dog.
To kill a general first shoot his horse.
What is left unsaid is rich as flowers.
If you are in a hurry go round-about.
Better be ignorant than mistaught.
You can't judge widows or horses without handling them.
Don't use the ox-cleaver to kill a hen.
Two hearts: and only one body.
Bread is better than blossoms.
Good medicine has often a bitter smack.
First among blossoms the cherry: among men the warrior.
You can't wrap up the wind or tie down the shadow.
You cannot live in the same world with your father's murderer.
Even a starving hawk won't lower himself to eat corn.
Keep your mouth shut, your eyes open.
Some ride in palanquins, some bear palanquins: some weave sandals for palanquin-bearers.
Tuning filesystem for rot 0...
Two things will make you lose your earrings, and one of them's dancing. -Bonnie Raitt.
When the humans are away, the monkeys enter the hut, eat up the maize, and rearrange the furniture.
The problem is not getting ksh to execute any particular command, the problem is recognizing that there might be a problem.
diff: usage diff [whatever] etc.
Intense opportunities for reorganization
Stringent ideas in the upper echelon mind
Leveraged brainpower at the labs
>>>>>>> REMOVE ALL YOUR FILES AND DIRECTORIES NOW! <<<<<<<
UX:lp: ERROR: Can't establish contact with the LP print service.
Awk is one of the world's greatest collections of surprises. -Doug McIlroy
If you think awk is the perfect programming language for the problem, you don't understand the problem yet. -Rob Pike
``Workers of the World, forgive us!'' (a banner in a Moscow counter-rally, Oct 8, 1989)
The first step is to determine what the remaining steps are. -Mark Horton
If you do something stupid on UNIX you generally get strange behavior. -Doug Gwyn
I'd still like you to explain that worm to me - Judge Munson to Robert T. Morris
Like raisins in a bread pudding, the moments lie within the body of Henry.
A real gentleman never takes bases unless he really has to.
There are two proteins involved in DNA synthesis, they are called DNAsynthase 1 and DNAsynthase 3.
Drawing on my fine command of the English language, I said nothing. -Robert Benchley
Hay, be seedy! He-effigy, hate-shy jaky yellow man, oh peek, you are rusty, you've edible, you ex-wise he!
The FSF is not overly concerned about security. - FSF
Make: Don't know how your program works
OSIfy, v.: To make code impenetrable.
Rule 3: If the character is comprised of a container without another radical, then
An economic reality of our time: computerized job deskilling. - a book review in Science
Estne ebriamen de furfure avenaceo factum?
The isomer with the higher dipole moment has the higher physical constants, regardless of the heat content. Van Arkel Rule
Other factors being equal, the metal which is most susceptible to failure is that with the lowest boiling point. Mogro-Campero Rule
The solid particle erosion rate of annealed face-centered cubic metals is inversely related to their hardness. Finnie-Wolak-Kabil Rule
In dichroic crystals, the faster ray is less absorbed. Babinet Rule
If you can't stand the heat, get a pool.
A rolling stone is a singing rock group.
UX:mail: INFO: No mail.
Real software has its own 800 support line. - Stu Feldman
Marine math: 2 beers times 39 Marines is 49 cases.
When times are bad, people feel compelled to overeat.
Of the physical pages in use, 3436738592 pages are permanently allocated to VMS.
If you carry on, head-to-head, on `solution' you don't get anywhere
This is like ignoring both the speed limit and the odometer in your car. It won't get you far. -Kenneth P. Birman
/bin/ls: exec header invalid
My pile of equipment is bigger than your pile of equipment -- philw
Connected to 192.65.218.43.
as1: Error: ../bpvvv.c, line 1324: Too many float literals--compile with "-Wb,-nopool"
The helpful thought for which you look/Is written somewhere in a book.
Is the tool broadly supported or maintained?
I'm pulling *something* here. - Dom Marotta
I'm drawing a line under the sand. - John Major
They bit the wrong chicken's head off with their own teeth and got blood all over their shirt - nls
I just want a bare-boned, straight EMACS. - Rae McLellan
*** Message content is not printable: delete, write or save it to a file ***
1181258 SAVECORE SEGMENTATION FAULT WHILE TRYING TO SAVE CORE
Everything that can ever be invented has been invented -Charles H. Duell,
Dan Quayle addressing the United Negro College Fund
Here in Nuremberg, information hiding is much more popular in the organization than it is in the software...
License Error : The license for this product(SPARCompiler C) has expired
My favorite thing about the Internet is that you get to go into the private world of real creeps without having to smell them. - Penn Jillette
Music is the pleasure the human soul experiences from counting without being aware that it is counting. -Leibnitz
Millenium parties/with loud music, lights and joy;/then, how cold!
warning: Hit heuristic-fence-post without finding enclosing function for address 0xfa2e470
War in the Clinton era is just P.R. by other means. - Michael Hirschorn
Application Developer's Architecture Guide
! TeX capacity exceeded, sorry [main memory size=263001].
His hand was bitten by his own side in an offhand way. - Mark V. Shaney
Sperm bank sued for tossing samples - Headline in Star-Ledger
boot: nop...cfs...session...no physical memory
Computers come in putty-colored boxes and have AUTOEXEC.BAT files and run screen-savers with flying toasters, and brains do not. - Steven Pinker
We have discovered a pervasive nonstationarity.
verb = a > b ? 'a': c > d ? 'd': 'c';
It's not only stupid it's wrong!
Lucky Numbers 12, 14, 19, 24, 36, 43
The goal of the experimental trials with the artificial heart is to "double the life span of these patients" to 60 days, Lederman said.
canlock: corrupted 0xcafebabe
Devil Duckie, when you float, it's like I'm bathing in a flaming moat!
He lost both Ali-pilaf and Vali-pilaf.
He is a man who gives bread.
We cut salt and bread together.
You can't make stew with cheap meat.
Absquotilate it in style, you old skunk,..and show the gentlemen what you can do.
Cookie import from '/usr/dhog/lib/cookies' failed: fil
Fortunately, Mac OS X supports *that* feature! - Brendan Connell
The full documentation for cat is maintained as a Texinfo manual. If the info and cat programs are properly installed at your site, the command "info cat" should give you access to the complete manual.
That's nothing.. RMS sent me a .doc file the other day...
Notegroups! They kill you!
Welcome, rqzgc_mfuv8
Isn't it funny how people say they'll never grow up to be their parents, then one day they look in the mirror and they're moving aircraft carriers into the Gulf region? - the Onion
Where would Christianity be if Jesus got eight to fifteen years, with time off for good behavior? -- New York Senator James H. Donovan on capital punishment.
Few, few the bird make her nest.
To become of bishop miller.
The dress don't make the monk.
He is mad to bind.
He turns as a weath turcocl.
After the paunch comes the dance.
Linux programmer's mind: don't invalidate the D-cache unless it wasn't enabled
Practically noiseless and impossible to explode. - ad for the 1897 Oldsmobile
The essence of XML is this: the problem it solves is not hard, and it does not solve the problem well. - Phil Wadler, POPL 2003
If you are idle for more than 1000 hours, the system will log you out. Please save reviews frequently.
Setting up your SIP account will allow you to call both other SIP users as well as pstn phones. - Wim Sweldens
What's grey? A melted penguin.
Linux: the world's best text adventure game.
i know what jmk did. he added reentrancy for threads. - boyd, about uintptr
I recently visited plan9.bell-labs.com/wiki/plan9 and I would like to offer my web design services. I can help you with your Plan 9 website. - spam
gcc is the holy cow of compilers, not the holy grail. - forsyth
we live in a world of dogmas, probably trying to fight it with a stronger dogma is a bad idea, but trying to fight it with preumpted objectivism clearly doesn't work. - uriel
Lucent is the best place 4 me to work. (Please keep confidential)
Subject: There's More to Nevada Than You Think
/* keep the code below somewhat more readonable; not used elsewhere */
swapon: /dev/disk/by-uuid/928aaabd-5744-4f0a-b9ef-aa101f286514: Invalid argument - Linux
I guess the idea is that ... even though the structures are all different.
rusty: kernel pseudo files are not the place for chit-chat
+4
View File
@@ -0,0 +1,4 @@
futures>=3.2.0; python_version<'3'
grpcio>=1.40.0
tinode-grpc>=0.20.0b3
importlib-metadata>=1.0; python_version<'3.8'
+34
View File
@@ -0,0 +1,34 @@
import setuptools
from subprocess import Popen, PIPE
with open('README.md', 'r') as fh:
long_description = fh.read()
setuptools.setup(
name="tinode-chatbot",
version=git_version(),
author="Tinode Authors",
author_email="info@tinode.co",
description="Tinode demo chatbot.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/tinode/chat",
packages=setuptools.find_packages(),
install_requires=['grpcio>=1.40.0'],
classifiers=(
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache 2.0",
"Operating System :: OS Independent",
),
)
def git_version():
try:
p = Popen(['git', 'describe', '--tags'],
stdout=PIPE, stderr=PIPE)
p.stderr.close()
line = p.stdout.readlines()[0]
return line.strip()
except:
return None
+1
View File
@@ -0,0 +1 @@
{"schema": "token", "secret": "mtXWlt9ERZCKsw9aFAABAFGGCnxinE8ruLE21t6SQfck4uBKCIy44kerjmOh4h1+", "expires": "2017-11-18T04:14:02Z"}